@miller-tech/uap 2.16.7 → 2.19.5
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/config/capacity-policy.json +33 -0
- package/config/model-profiles/qwen38.json +32 -33
- package/dist/.tsbuildinfo +1 -1
- package/dist/bin/cli.js +57 -0
- package/dist/bin/cli.js.map +1 -1
- package/dist/capacity/policy.d.ts +32 -1
- package/dist/capacity/policy.d.ts.map +1 -1
- package/dist/capacity/policy.js +123 -4
- package/dist/capacity/policy.js.map +1 -1
- package/dist/capacity/probe.d.ts +26 -0
- package/dist/capacity/probe.d.ts.map +1 -1
- package/dist/capacity/probe.js +84 -3
- package/dist/capacity/probe.js.map +1 -1
- package/dist/cli/inference.d.ts +4 -0
- package/dist/cli/inference.d.ts.map +1 -1
- package/dist/cli/inference.js +74 -8
- package/dist/cli/inference.js.map +1 -1
- package/dist/cli/merge-gate.d.ts +59 -0
- package/dist/cli/merge-gate.d.ts.map +1 -0
- package/dist/cli/merge-gate.js +238 -0
- package/dist/cli/merge-gate.js.map +1 -0
- package/dist/cli/merge-queue.d.ts +13 -0
- package/dist/cli/merge-queue.d.ts.map +1 -1
- package/dist/cli/merge-queue.js +48 -2
- package/dist/cli/merge-queue.js.map +1 -1
- package/dist/cli/mission.d.ts +25 -0
- package/dist/cli/mission.d.ts.map +1 -0
- package/dist/cli/mission.js +225 -0
- package/dist/cli/mission.js.map +1 -0
- package/dist/cli/orch.d.ts +27 -0
- package/dist/cli/orch.d.ts.map +1 -0
- package/dist/cli/orch.js +70 -0
- package/dist/cli/orch.js.map +1 -0
- package/dist/cli/systemd-services.d.ts.map +1 -1
- package/dist/cli/systemd-services.js +44 -0
- package/dist/cli/systemd-services.js.map +1 -1
- package/dist/delivery/merge-gate.d.ts +168 -0
- package/dist/delivery/merge-gate.d.ts.map +1 -0
- package/dist/delivery/merge-gate.js +309 -0
- package/dist/delivery/merge-gate.js.map +1 -0
- package/dist/inference/analysis.d.ts +69 -3
- package/dist/inference/analysis.d.ts.map +1 -1
- package/dist/inference/analysis.js +123 -7
- package/dist/inference/analysis.js.map +1 -1
- package/dist/inference/probe.d.ts +38 -1
- package/dist/inference/probe.d.ts.map +1 -1
- package/dist/inference/probe.js +303 -30
- package/dist/inference/probe.js.map +1 -1
- package/dist/inference/strata.d.ts +95 -0
- package/dist/inference/strata.d.ts.map +1 -0
- package/dist/inference/strata.js +167 -0
- package/dist/inference/strata.js.map +1 -0
- package/dist/mission/database.d.ts +29 -0
- package/dist/mission/database.d.ts.map +1 -0
- package/dist/mission/database.js +131 -0
- package/dist/mission/database.js.map +1 -0
- package/dist/mission/ledger.d.ts +109 -0
- package/dist/mission/ledger.d.ts.map +1 -0
- package/dist/mission/ledger.js +294 -0
- package/dist/mission/ledger.js.map +1 -0
- package/dist/mission/poll-cycle.d.ts +73 -0
- package/dist/mission/poll-cycle.d.ts.map +1 -0
- package/dist/mission/poll-cycle.js +100 -0
- package/dist/mission/poll-cycle.js.map +1 -0
- package/dist/mission/poll-executor.d.ts +91 -0
- package/dist/mission/poll-executor.d.ts.map +1 -0
- package/dist/mission/poll-executor.js +328 -0
- package/dist/mission/poll-executor.js.map +1 -0
- package/dist/models/types.d.ts.map +1 -1
- package/dist/models/types.js +33 -27
- package/dist/models/types.js.map +1 -1
- package/dist/self-tuning/profiles/qwen38.d.ts +13 -14
- package/dist/self-tuning/profiles/qwen38.d.ts.map +1 -1
- package/dist/self-tuning/profiles/qwen38.js +17 -18
- package/dist/self-tuning/profiles/qwen38.js.map +1 -1
- package/dist/types/mission.d.ts +80 -0
- package/dist/types/mission.d.ts.map +1 -0
- package/dist/types/mission.js +12 -0
- package/dist/types/mission.js.map +1 -0
- package/docs/architecture/adr/0003-mission-orchestrator.md +99 -0
- package/docs/architecture/adr/0004-deterministic-merge-gate.md +127 -0
- package/docs/guides/CAPACITY_POLICY.md +49 -0
- package/docs/guides/INFERENCE_HEALTH.md +65 -8
- package/docs/guides/MISSIONS.md +170 -0
- package/docs/guides/PROXY.md +2 -0
- package/docs/reports/2026-10-02-plane-admin-fail-closed.md +70 -0
- package/docs/reports/2026-10-02-plane-admin-protect-session.md +116 -0
- package/docs/visual/merge-gate.html +404 -0
- package/docs/visual/mission-orchestrator.html +472 -0
- package/package.json +2 -2
- 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/src/policies/enforcers/plane_admin_protect.py +133 -0
- package/src/policies/schemas/policies/plane-admin-protect.md +55 -0
- package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
- package/templates/hooks/uap-policy-gate.sh +33 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +493 -40
- package/tools/agents/tests/test_anthropic_proxy_streaming.py +171 -0
- package/tools/agents/tests/test_gate_failclosed_plane_admin.py +248 -0
- package/tools/agents/tests/test_plane_admin_protect.py +124 -0
- package/tools/agents/tests/test_stream_heartbeat.py +77 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stub markers in ADDED code. Applied to non-test, non-docs files: test
|
|
3
|
+
* suites legitimately stub seams (the house style injects stubs everywhere),
|
|
4
|
+
* and docs/ADRs legitimately quote the markers they describe. A `todo!()`
|
|
5
|
+
* left in production code is exactly the half-delivery this check exists to
|
|
6
|
+
* catch.
|
|
7
|
+
*/
|
|
8
|
+
export const STUB_PATTERNS = [
|
|
9
|
+
{ label: 'todo macro', re: /todo!\s*\(/ },
|
|
10
|
+
{ label: 'NotImplementedError', re: /\bNotImplementedError\b/ },
|
|
11
|
+
{ label: 'unimplemented', re: /\bunimplemented\b|\bnot implemented\b/i },
|
|
12
|
+
// Bounded gap (was `.*`): an adversarial multi-megabyte single line made
|
|
13
|
+
// this pattern quadratic; 120 chars is far more than any real
|
|
14
|
+
// placeholder sentence needs (security review P2).
|
|
15
|
+
{ label: 'placeholder implementation', re: /\bplaceholder\b.{0,120}\b(implementation|for now)\b|\bstub(?:bed)? (?:body|implementation|for now)\b/i },
|
|
16
|
+
{ label: 'TODO marker', re: /\bTODO\b/ },
|
|
17
|
+
{ label: 'FIXME marker', re: /\bFIXME\b/ },
|
|
18
|
+
];
|
|
19
|
+
/** Apology / escape phrasing — the "sorry delta". Scanned in added lines of
|
|
20
|
+
* every changed file and in the run's declared summaries. */
|
|
21
|
+
export const SORRY_PATTERNS = [
|
|
22
|
+
{ label: 'apology', re: /\bsorry\b|\bi apolog(i|y)/i },
|
|
23
|
+
{ label: 'gave up', re: /\bi couldn'?t\b|\bi was unable\b|\bdidn'?t manage to\b|\bnot able to (?:complete|finish|fulfill)\b/i },
|
|
24
|
+
{ label: 'refusal', re: /\bcannot (?:complete|fulfill)\b|\bunable to (?:complete|fulfill|finish)\b/i },
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* Paths that ARE the gate system. A diff touching any of these without
|
|
28
|
+
* `allowGateInfra` fails axiom-honesty — including the merge gate, its
|
|
29
|
+
* evidence gatherer, and the ledger functions the provenance and
|
|
30
|
+
* green-recompute checks consume: a PR may not weaken (or rewrite) the checks
|
|
31
|
+
* that judge it.
|
|
32
|
+
*
|
|
33
|
+
* Deliberately hard-coded in source, not a config file: an externalized list
|
|
34
|
+
* could be edited by the very PR it judges, and any edit to THIS list trips
|
|
35
|
+
* axiom-honesty against the gate itself (the list protects itself by being
|
|
36
|
+
* the gate — architect review F6).
|
|
37
|
+
*/
|
|
38
|
+
export const GATE_INFRA_PREFIXES = [
|
|
39
|
+
'.codex/hooks/',
|
|
40
|
+
'.github/workflows/',
|
|
41
|
+
'src/policies/enforcers/',
|
|
42
|
+
'src/delivery/merge-gate.ts',
|
|
43
|
+
'src/delivery/gate-evidence.ts',
|
|
44
|
+
'src/cli/merge-gate.ts',
|
|
45
|
+
'src/cli/merge-queue.ts',
|
|
46
|
+
'src/mission/ledger.ts',
|
|
47
|
+
'src/mission/database.ts',
|
|
48
|
+
'scripts/version-bump.sh',
|
|
49
|
+
'.uap/quality-baseline.json',
|
|
50
|
+
'config/capacity-policy.json',
|
|
51
|
+
];
|
|
52
|
+
/** Test paths are exempt from stub-marker scanning (seams are stubbed by
|
|
53
|
+
* design in this codebase); docs paths are exempt because they quote the
|
|
54
|
+
* markers they describe. */
|
|
55
|
+
function isTestPath(path) {
|
|
56
|
+
return /(^|\/)test\//.test(path) || /\.(test|spec)\.[cm]?[jt]s$/.test(path);
|
|
57
|
+
}
|
|
58
|
+
function isDocsPath(path) {
|
|
59
|
+
return /\.(md|html|txt)$/.test(path);
|
|
60
|
+
}
|
|
61
|
+
/** Adversarial long lines (minified blobs, lockfiles) make multi-token
|
|
62
|
+
* patterns expensive; cap per-line scanning (security review P2). */
|
|
63
|
+
const SCAN_LINE_MAX = 8192;
|
|
64
|
+
/** First matching pattern label, or null. Long lines are skipped — a real
|
|
65
|
+
* stub marker sits on a human-scale line. */
|
|
66
|
+
function firstMatch(text, patterns) {
|
|
67
|
+
for (const line of text.split('\n')) {
|
|
68
|
+
if (line.length > SCAN_LINE_MAX)
|
|
69
|
+
continue;
|
|
70
|
+
for (const p of patterns) {
|
|
71
|
+
if (p.re.test(line))
|
|
72
|
+
return p.label;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* PURE decision core. Every check runs and reports (a full finding list, not
|
|
79
|
+
* first-fail) so an operator sees every reason at once; `pass` is the AND of
|
|
80
|
+
* all findings.
|
|
81
|
+
*/
|
|
82
|
+
export function evaluateMergeGate(inputs) {
|
|
83
|
+
const findings = [];
|
|
84
|
+
// ── 1. provenance / statement immutability ──
|
|
85
|
+
const markerOk = inputs.ledgerIntact && inputs.markerHash8 !== undefined && inputs.markerHash8 === inputs.acceptanceHash8;
|
|
86
|
+
findings.push({
|
|
87
|
+
check: 'provenance',
|
|
88
|
+
ok: markerOk,
|
|
89
|
+
detail: !inputs.ledgerIntact
|
|
90
|
+
? 'ledger tampered: stored acceptance text no longer hashes to the stored hash'
|
|
91
|
+
: inputs.markerHash8 === undefined
|
|
92
|
+
? `no [mission:#${inputs.missionId}:hash8] marker ties this work to frozen acceptance`
|
|
93
|
+
: inputs.markerHash8 === inputs.acceptanceHash8
|
|
94
|
+
? `marker hash ${inputs.markerHash8} matches frozen acceptance`
|
|
95
|
+
: `marker hash ${inputs.markerHash8} is superseded (current: ${inputs.acceptanceHash8}) — the work was built under replanned-away criteria`,
|
|
96
|
+
});
|
|
97
|
+
// ── 2. green recompute ──
|
|
98
|
+
let greenOk = true;
|
|
99
|
+
const greenDetails = [];
|
|
100
|
+
// Tier 1 — the evidence artifact deliver records on success. Production
|
|
101
|
+
// delivered runs persist WITHOUT a checkpoint (deliver clears it), so the
|
|
102
|
+
// artifact is the seam that survives a real delivery; a present-but-invalid
|
|
103
|
+
// artifact (red gate, hatch, sha drift) is a hard failure.
|
|
104
|
+
if (inputs.evidence !== null) {
|
|
105
|
+
if (!inputs.evidence.present) {
|
|
106
|
+
greenDetails.push(`no gate-evidence artifact for ${inputs.evidence.expectedSha.slice(0, 12)}… (.uap/evidence/)`);
|
|
107
|
+
}
|
|
108
|
+
else if (!inputs.evidence.shaMatches) {
|
|
109
|
+
greenOk = false;
|
|
110
|
+
greenDetails.push('evidence artifact is bound to a different candidate sha');
|
|
111
|
+
}
|
|
112
|
+
else if (!inputs.evidence.hatchesEmpty) {
|
|
113
|
+
greenOk = false;
|
|
114
|
+
greenDetails.push('evidence artifact records gate-affecting environment hatches');
|
|
115
|
+
}
|
|
116
|
+
else if (!inputs.evidence.gatesAllZero) {
|
|
117
|
+
greenOk = false;
|
|
118
|
+
greenDetails.push('evidence artifact records a non-zero gate exit');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Tier 2 — run-state history (present on interrupted/resumed shapes and
|
|
122
|
+
// hand-crafted fixtures; absent on the production delivered shape).
|
|
123
|
+
if (inputs.runState === null) {
|
|
124
|
+
greenOk = false;
|
|
125
|
+
greenDetails.push('no linked run (or run-state unreadable)');
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
if (inputs.runState.status !== 'delivered') {
|
|
129
|
+
greenOk = false;
|
|
130
|
+
greenDetails.push(`run state is '${sanitize(inputs.runState.status)}', not 'delivered'`);
|
|
131
|
+
}
|
|
132
|
+
if (inputs.pidCorroborated === false) {
|
|
133
|
+
greenOk = false;
|
|
134
|
+
greenDetails.push('run pid does not match the pid the orchestrator spawned');
|
|
135
|
+
}
|
|
136
|
+
const history = inputs.runState.history ?? [];
|
|
137
|
+
if (history.length > 0) {
|
|
138
|
+
const last = history[history.length - 1];
|
|
139
|
+
if (!last.passed) {
|
|
140
|
+
greenOk = false;
|
|
141
|
+
greenDetails.push(`last recorded iteration failed its gates`);
|
|
142
|
+
}
|
|
143
|
+
else if (last.acceptanceMet !== undefined && last.acceptanceMet < 1) {
|
|
144
|
+
greenOk = false;
|
|
145
|
+
greenDetails.push(`final acceptance judge met only ${Math.round(last.acceptanceMet * 100)}% of criteria`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else if (inputs.evidence !== null && inputs.evidence.present) {
|
|
149
|
+
// artifact-backed: status + artifact is sufficient.
|
|
150
|
+
}
|
|
151
|
+
else if (inputs.evidence === null) {
|
|
152
|
+
// caller did not check the artifact; history is the only evidence.
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
// No artifact AND no history: the "delivered" status string alone is
|
|
156
|
+
// testimony, not evidence — refuse it (code review P1).
|
|
157
|
+
greenOk = false;
|
|
158
|
+
greenDetails.push('no recorded evidence beyond the delivered status string (deliver clears checkpoints on success; expected .uap/evidence/<sha>.json)');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (inputs.openSalvageCount > 0) {
|
|
162
|
+
greenOk = false;
|
|
163
|
+
greenDetails.push(`${inputs.openSalvageCount} salvage proposal(s) await a ruling (approval-gated — landing would bypass the ruling)`);
|
|
164
|
+
}
|
|
165
|
+
const artifactNote = inputs.evidence !== null && inputs.evidence.present && inputs.evidence.gatesAllZero && inputs.evidence.hatchesEmpty && inputs.evidence.shaMatches
|
|
166
|
+
? ' + evidence artifact green'
|
|
167
|
+
: '';
|
|
168
|
+
findings.push({
|
|
169
|
+
check: 'green-recompute',
|
|
170
|
+
ok: greenOk,
|
|
171
|
+
detail: greenOk
|
|
172
|
+
? `fresh run-state: delivered${artifactNote}, no open salvage`
|
|
173
|
+
: greenDetails.join('; '),
|
|
174
|
+
});
|
|
175
|
+
// ── 3. sorry-delta ──
|
|
176
|
+
const stubHits = [];
|
|
177
|
+
const sorryHits = [];
|
|
178
|
+
for (const f of inputs.changedFiles) {
|
|
179
|
+
if (f.addedText.length === 0)
|
|
180
|
+
continue;
|
|
181
|
+
if (!isTestPath(f.path) && !isDocsPath(f.path)) {
|
|
182
|
+
const stub = firstMatch(f.addedText, STUB_PATTERNS);
|
|
183
|
+
if (stub)
|
|
184
|
+
stubHits.push(`${f.path} (${stub})`);
|
|
185
|
+
}
|
|
186
|
+
const sorry = firstMatch(f.addedText, SORRY_PATTERNS);
|
|
187
|
+
if (sorry)
|
|
188
|
+
sorryHits.push(`${f.path} (${sorry})`);
|
|
189
|
+
}
|
|
190
|
+
const summaries = (inputs.runState?.phaseSummaries ?? []).join('\n');
|
|
191
|
+
if (summaries.length > 0) {
|
|
192
|
+
const sorry = firstMatch(summaries, SORRY_PATTERNS);
|
|
193
|
+
if (sorry)
|
|
194
|
+
sorryHits.push(`run summaries (${sorry})`);
|
|
195
|
+
}
|
|
196
|
+
const sorryOk = stubHits.length === 0 && sorryHits.length === 0;
|
|
197
|
+
findings.push({
|
|
198
|
+
check: 'sorry-delta',
|
|
199
|
+
ok: sorryOk,
|
|
200
|
+
detail: sorryOk
|
|
201
|
+
? `no stub or apology markers in ${inputs.changedFiles.length} changed file(s)`
|
|
202
|
+
: `stub markers: ${stubHits.length ? stubHits.join(', ') : 'none'}; apology markers: ${sorryHits.length ? sorryHits.join(', ') : 'none'}`,
|
|
203
|
+
});
|
|
204
|
+
// ── 4. axiom-honesty (gate-infra tampering) ──
|
|
205
|
+
const infraHits = inputs.changedFiles
|
|
206
|
+
.map((f) => f.path)
|
|
207
|
+
.filter((p) => GATE_INFRA_PREFIXES.some((prefix) => p === prefix || p.startsWith(prefix)));
|
|
208
|
+
const infraOk = infraHits.length === 0 || inputs.allowGateInfra === true;
|
|
209
|
+
findings.push({
|
|
210
|
+
check: 'axiom-honesty',
|
|
211
|
+
ok: infraOk,
|
|
212
|
+
detail: infraHits.length === 0
|
|
213
|
+
? 'diff does not touch gate infrastructure'
|
|
214
|
+
: infraOk
|
|
215
|
+
? `gate-infra changes explicitly acknowledged: ${infraHits.join(', ')}`
|
|
216
|
+
: `diff touches gate infrastructure without acknowledgment (${infraHits.join(', ')}) — pass --allow-gate-infra (or label the PR gate-infra) only if this is deliberate`,
|
|
217
|
+
});
|
|
218
|
+
return { missionId: inputs.missionId, pass: findings.every((f) => f.ok), findings };
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Strip terminal escapes before echoing untrusted strings (run-state status
|
|
222
|
+
* comes from an agent-writable state.json — CWE-117, security review P3).
|
|
223
|
+
*/
|
|
224
|
+
function sanitize(text) {
|
|
225
|
+
// eslint-disable-next-line no-control-regex
|
|
226
|
+
return String(text).replace(/[\u001b\u009b]/g, '');
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Extract the mission provenance marker from free text (a PR title or body).
|
|
230
|
+
* Prefix-pinned like the poll-executor's parser: the bracketed form must match
|
|
231
|
+
* exactly, so argv injection and partial collisions cannot forge linkage.
|
|
232
|
+
*/
|
|
233
|
+
export function missionMarkerOf(text) {
|
|
234
|
+
const m = /\[mission:#(\d+):([0-9a-f]{8})\]/.exec(text ?? '');
|
|
235
|
+
return m ? { missionId: Number(m[1]), hash8: m[2] } : null;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Parse a unified diff into changed files with their ADDED lines. Accepts the
|
|
239
|
+
* output of `git diff --unified=0 <base>...HEAD` and `gh pr diff`. Pure — the
|
|
240
|
+
* command execution lives in the callers, tests exercise only this parser.
|
|
241
|
+
*
|
|
242
|
+
* Path coverage is the security property: DELETED files (`+++ /dev/null`),
|
|
243
|
+
* RENAMED files (no `+++` line at all), and BINARY files (no headers) must all
|
|
244
|
+
* reach the axiom-honesty prefix match — `git rm .codex/hooks/x.sh` is the
|
|
245
|
+
* purest form of weakening a gate, so the deleted path is captured from the
|
|
246
|
+
* `diff --git` / `--- a/` lines (security review P1, architect F1).
|
|
247
|
+
*
|
|
248
|
+
* An ADDED content line that itself starts with `+++` is not misread as a
|
|
249
|
+
* header: `+++` only opens a file when the previous line was its `---` pair.
|
|
250
|
+
*/
|
|
251
|
+
export function parseUnifiedDiff(diff) {
|
|
252
|
+
const byPath = new Map();
|
|
253
|
+
const fileFor = (path) => {
|
|
254
|
+
let f = byPath.get(path);
|
|
255
|
+
if (!f) {
|
|
256
|
+
f = { path, addedText: '' };
|
|
257
|
+
byPath.set(path, f);
|
|
258
|
+
}
|
|
259
|
+
return f;
|
|
260
|
+
};
|
|
261
|
+
let current = null;
|
|
262
|
+
let inHunk = false; // true after a file's first @@ line
|
|
263
|
+
let prevWasHeaderPair = false; // previous line was a `--- ` file header
|
|
264
|
+
for (const line of diff.split('\n')) {
|
|
265
|
+
if (line.startsWith('diff --git ')) {
|
|
266
|
+
// `diff --git a/<path> b/<path>` — authoritative for renames (no +++
|
|
267
|
+
// line follows) and binaries. Register both sides; the b-side becomes
|
|
268
|
+
// the add target if a +++ header never appears.
|
|
269
|
+
const m = /^diff --git a\/(.+?) b\/(.+)$/.exec(line);
|
|
270
|
+
if (m) {
|
|
271
|
+
fileFor(m[1]); // old path (deletions, rename source)
|
|
272
|
+
current = fileFor(m[2]);
|
|
273
|
+
}
|
|
274
|
+
inHunk = false;
|
|
275
|
+
prevWasHeaderPair = false;
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
if (!inHunk && line.startsWith('--- ')) {
|
|
279
|
+
const old = line.slice(4);
|
|
280
|
+
// `--- a/<path>`: register the path (deletion evidence) but do not
|
|
281
|
+
// retarget added lines — only the `+++` header does that.
|
|
282
|
+
if (old.startsWith('a/') && old !== 'a/')
|
|
283
|
+
fileFor(old.slice(2));
|
|
284
|
+
prevWasHeaderPair = true;
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (!inHunk && line.startsWith('+++ ') && prevWasHeaderPair) {
|
|
288
|
+
const target = line.slice(4);
|
|
289
|
+
current =
|
|
290
|
+
target === '/dev/null'
|
|
291
|
+
? null // pure deletion: added lines cannot follow
|
|
292
|
+
: fileFor(target.startsWith('b/') ? target.slice(2) : target);
|
|
293
|
+
prevWasHeaderPair = false;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
prevWasHeaderPair = false;
|
|
297
|
+
if (line.startsWith('@@')) {
|
|
298
|
+
inHunk = true;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (line.startsWith('+') && current) {
|
|
302
|
+
// Inside a hunk every `+`-leading line is content — including one that
|
|
303
|
+
// merely LOOKS like a header (`++ b/x` renders as `+++ b/x`).
|
|
304
|
+
current.addedText += line.slice(1) + '\n';
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return [...byPath.values()];
|
|
308
|
+
}
|
|
309
|
+
//# sourceMappingURL=merge-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge-gate.js","sourceRoot":"","sources":["../../src/delivery/merge-gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -32,6 +32,29 @@ export interface PrefillSample {
|
|
|
32
32
|
/** Reported throughput for that prefill. */
|
|
33
33
|
tokensPerSecond: number;
|
|
34
34
|
}
|
|
35
|
+
/** The shape `analyzeTrend` needs: a throughput reading with the workload
|
|
36
|
+
* quantity that determines how comparable it is (prompt size for prefill,
|
|
37
|
+
* prompt DEPTH for decode). Structural supertype of PrefillSample, so
|
|
38
|
+
* existing callers pass unchanged; strata decode samples reuse it with
|
|
39
|
+
* `tokens` = depth, which is what makes decode buckets compare like-with-like. */
|
|
40
|
+
export interface TrendSample {
|
|
41
|
+
at: number;
|
|
42
|
+
/** The quantity whose size determines the bucket — NOT always "tokens
|
|
43
|
+
* transferred" (see the decode case above). */
|
|
44
|
+
tokens: number;
|
|
45
|
+
tokensPerSecond: number;
|
|
46
|
+
}
|
|
47
|
+
/** A turn's prefix-cache reuse accounting. Used by the strata backend, which
|
|
48
|
+
* reports reuse as a FACT (tokens served from cache), not a counterfactual.
|
|
49
|
+
* `at` is the strata sample clock: line index for log samples, epoch ms for
|
|
50
|
+
* ring samples — comparable within one source, never across sources. */
|
|
51
|
+
export interface ReuseSample {
|
|
52
|
+
at: number;
|
|
53
|
+
/** Prompt tokens the client sent. */
|
|
54
|
+
incoming: number;
|
|
55
|
+
/** Tokens served from the cache. */
|
|
56
|
+
reused: number;
|
|
57
|
+
}
|
|
35
58
|
/** A turn's cache/checkpoint divergence accounting. */
|
|
36
59
|
export interface CheckpointSample {
|
|
37
60
|
/** Prompt tokens the client sent. */
|
|
@@ -44,7 +67,7 @@ export interface CheckpointSample {
|
|
|
44
67
|
export interface InferenceSnapshot {
|
|
45
68
|
/** Seconds the server process has been running, when known. */
|
|
46
69
|
uptimeSeconds?: number;
|
|
47
|
-
/** Parallel rails (`-np`). */
|
|
70
|
+
/** Parallel rails (`-np`), or /slots length on strata. */
|
|
48
71
|
rails?: number;
|
|
49
72
|
/** Shared KV pool size in cells (`-c`). Under --kv-unified this is TOTAL. */
|
|
50
73
|
poolCells?: number;
|
|
@@ -61,6 +84,20 @@ export interface InferenceSnapshot {
|
|
|
61
84
|
generationTimeouts?: number;
|
|
62
85
|
/** Distinct server PIDs seen in the window. >1 means it spans a restart. */
|
|
63
86
|
processCount?: number;
|
|
87
|
+
/**
|
|
88
|
+
* Strata-backend fields. All optional so the llama.cpp path constructs the
|
|
89
|
+
* snapshot exactly as before.
|
|
90
|
+
*/
|
|
91
|
+
/** Which backend produced this snapshot — the report says so out loud. */
|
|
92
|
+
backend?: 'llamacpp' | 'strata';
|
|
93
|
+
/** Model id the backend advertises (strata /metrics engine.model). */
|
|
94
|
+
engineModel?: string;
|
|
95
|
+
/** Decode throughput per request, bucketed by prompt DEPTH. */
|
|
96
|
+
decode?: TrendSample[];
|
|
97
|
+
/** Per-request prefix-cache reuse (strata reports reuse as a fact). */
|
|
98
|
+
reuse?: ReuseSample[];
|
|
99
|
+
/** Backend-reported free VRAM in MiB (strata engine.vram_free_mib). */
|
|
100
|
+
vramFreeMiB?: number;
|
|
64
101
|
}
|
|
65
102
|
export interface Thresholds {
|
|
66
103
|
/** Ratio of recent to early prefill below which we call it degraded. */
|
|
@@ -74,6 +111,15 @@ export interface Thresholds {
|
|
|
74
111
|
kvFloorSlack: number;
|
|
75
112
|
/** Any timeout in the window is at least a WARN. */
|
|
76
113
|
timeoutsWarn: number;
|
|
114
|
+
/** Mean prefix-cache reuse fraction below which the cache is failing. */
|
|
115
|
+
reuseWarn: number;
|
|
116
|
+
/** Backend-reported free VRAM below which we warn (OOM territory). */
|
|
117
|
+
vramHeadroomWarnMiB: number;
|
|
118
|
+
/** How many recent reuse samples the cache-miss check looks at. The cache
|
|
119
|
+
* question is "is it tracking NOW", not "did every conversation since the
|
|
120
|
+
* process start reuse well" — a 23h log legitimately contains dozens of
|
|
121
|
+
* cold-start turns. */
|
|
122
|
+
reuseTailSamples: number;
|
|
77
123
|
}
|
|
78
124
|
export declare const DEFAULT_THRESHOLDS: Thresholds;
|
|
79
125
|
/** Prompt-size buckets. Prefill tok/s is strongly size-dependent, so an
|
|
@@ -102,14 +148,18 @@ export interface TrendResult {
|
|
|
102
148
|
note?: string;
|
|
103
149
|
}
|
|
104
150
|
/**
|
|
105
|
-
* Compare early-life against recent
|
|
151
|
+
* Compare early-life against recent throughput WITHIN a size bucket.
|
|
106
152
|
*
|
|
107
153
|
* Splitting the samples in half and averaging each side would be wrong: if
|
|
108
154
|
* the workload's prompt sizes drift (and they do — conversations grow), the
|
|
109
155
|
* mean moves for reasons that have nothing to do with the server. So pick the
|
|
110
156
|
* bucket that has enough samples on BOTH sides and compare only within it.
|
|
157
|
+
*
|
|
158
|
+
* Generic over TrendSample: prefill samples carry prompt size in `tokens`,
|
|
159
|
+
* strata decode samples carry prompt DEPTH — the quantity that determines
|
|
160
|
+
* comparability is whatever the caller puts there.
|
|
111
161
|
*/
|
|
112
|
-
export declare function analyzeTrend(samples:
|
|
162
|
+
export declare function analyzeTrend(samples: TrendSample[], thresholds?: Thresholds): TrendResult;
|
|
113
163
|
export interface CheckpointResult {
|
|
114
164
|
/** Mean fraction of reusable tokens actually recovered. */
|
|
115
165
|
recovery?: number;
|
|
@@ -119,6 +169,17 @@ export interface CheckpointResult {
|
|
|
119
169
|
}
|
|
120
170
|
/** How much of the reusable prefix the checkpoints actually deliver. */
|
|
121
171
|
export declare function analyzeCheckpoints(samples: CheckpointSample[]): CheckpointResult;
|
|
172
|
+
export interface ReuseResult {
|
|
173
|
+
/** Mean fraction of the sent prompt served from cache. */
|
|
174
|
+
fraction?: number;
|
|
175
|
+
/** Mean tokens re-read per turn that a healthy cache would have supplied. */
|
|
176
|
+
wastedTokens?: number;
|
|
177
|
+
samples: number;
|
|
178
|
+
}
|
|
179
|
+
/** Mean prefix-cache reuse over the strata samples. Strata reports reuse as a
|
|
180
|
+
* fact, so there is no reusable-vs-restored split — the health question is
|
|
181
|
+
* simply "is the cache tracking the conversation". */
|
|
182
|
+
export declare function analyzeReuse(samples: ReuseSample[]): ReuseResult;
|
|
122
183
|
export interface Finding {
|
|
123
184
|
health: Exclude<InferenceHealth, 'UNKNOWN'>;
|
|
124
185
|
/** Stable id so monitors can match on it without parsing prose. */
|
|
@@ -132,6 +193,11 @@ export interface InferenceReport {
|
|
|
132
193
|
findings: Finding[];
|
|
133
194
|
trend: TrendResult;
|
|
134
195
|
checkpoints: CheckpointResult;
|
|
196
|
+
/** Strata-backend additions; undefined for the llama.cpp path so existing
|
|
197
|
+
* monitors keep their parsed shape working. */
|
|
198
|
+
backend?: 'llamacpp' | 'strata';
|
|
199
|
+
decodeTrend?: TrendResult;
|
|
200
|
+
reuse?: ReuseResult;
|
|
135
201
|
}
|
|
136
202
|
/** Roll a snapshot up into a verdict. Pure; no I/O, no clock. */
|
|
137
203
|
export declare function assessInference(snap: InferenceSnapshot, thresholds?: Thresholds): InferenceReport;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analysis.d.ts","sourceRoot":"","sources":["../../src/inference/analysis.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,SAAS,CAAC;AAEnE,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,EAAE,EAAE,MAAM,CAAC;IACX,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,uDAAuD;AACvD,MAAM,WAAW,gBAAgB;IAC/B,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,
|
|
1
|
+
{"version":3,"file":"analysis.d.ts","sourceRoot":"","sources":["../../src/inference/analysis.ts"],"names":[],"mappings":"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"}
|
|
@@ -33,6 +33,22 @@ export const DEFAULT_THRESHOLDS = {
|
|
|
33
33
|
checkpointRecoveryWarn: 0.5,
|
|
34
34
|
kvFloorSlack: 0.15,
|
|
35
35
|
timeoutsWarn: 1,
|
|
36
|
+
// Strata's own healthy conversations reuse 90%+ of the prefix
|
|
37
|
+
// (observed 2026-10-04: 38270/38659 = 99%). Under half means most turns
|
|
38
|
+
// re-read the whole prompt — the compaction/checkpoint starvation analog.
|
|
39
|
+
reuseWarn: 0.5,
|
|
40
|
+
// The strata reserve-aware floor. NOTE this is NOT the capacity policy's
|
|
41
|
+
// gpuMinFreeMiB (200 for the strata entry): that one is HOST-wide free
|
|
42
|
+
// VRAM from nvidia-smi; this one is strata's own /metrics reading, which
|
|
43
|
+
// already respects its --vram-reserve-mib 1200. Both floors being 600 in
|
|
44
|
+
// the first draft was a coincidence, not a coupling — tune them
|
|
45
|
+
// independently.
|
|
46
|
+
vramHeadroomWarnMiB: 600,
|
|
47
|
+
// Observed live 2026-10-04: a whole-process mean (39% over 4400 requests)
|
|
48
|
+
// fired a WARN that was really dozens of legitimate cold-start turns from
|
|
49
|
+
// OTHER sessions; the active session's recent turns reuse 95-99%. The tail
|
|
50
|
+
// is the signal.
|
|
51
|
+
reuseTailSamples: 50,
|
|
36
52
|
};
|
|
37
53
|
/** Prompt-size buckets. Prefill tok/s is strongly size-dependent, so an
|
|
38
54
|
* average across mixed sizes is not comparable over time. */
|
|
@@ -46,16 +62,20 @@ export function sizeBucket(tokens) {
|
|
|
46
62
|
return '>30k';
|
|
47
63
|
}
|
|
48
64
|
/**
|
|
49
|
-
* Compare early-life against recent
|
|
65
|
+
* Compare early-life against recent throughput WITHIN a size bucket.
|
|
50
66
|
*
|
|
51
67
|
* Splitting the samples in half and averaging each side would be wrong: if
|
|
52
68
|
* the workload's prompt sizes drift (and they do — conversations grow), the
|
|
53
69
|
* mean moves for reasons that have nothing to do with the server. So pick the
|
|
54
70
|
* bucket that has enough samples on BOTH sides and compare only within it.
|
|
71
|
+
*
|
|
72
|
+
* Generic over TrendSample: prefill samples carry prompt size in `tokens`,
|
|
73
|
+
* strata decode samples carry prompt DEPTH — the quantity that determines
|
|
74
|
+
* comparability is whatever the caller puts there.
|
|
55
75
|
*/
|
|
56
76
|
export function analyzeTrend(samples, thresholds = DEFAULT_THRESHOLDS) {
|
|
57
77
|
if (samples.length < thresholds.minSamplesPerEra * 2) {
|
|
58
|
-
return { earlyCount: 0, recentCount: 0, note: 'not enough
|
|
78
|
+
return { earlyCount: 0, recentCount: 0, note: 'not enough samples for a trend' };
|
|
59
79
|
}
|
|
60
80
|
// Bucket FIRST, then split each bucket at its OWN median time.
|
|
61
81
|
//
|
|
@@ -133,6 +153,19 @@ export function analyzeCheckpoints(samples) {
|
|
|
133
153
|
samples: usable.length,
|
|
134
154
|
};
|
|
135
155
|
}
|
|
156
|
+
/** Mean prefix-cache reuse over the strata samples. Strata reports reuse as a
|
|
157
|
+
* fact, so there is no reusable-vs-restored split — the health question is
|
|
158
|
+
* simply "is the cache tracking the conversation". */
|
|
159
|
+
export function analyzeReuse(samples) {
|
|
160
|
+
const real = samples.filter((s) => s.incoming > 0);
|
|
161
|
+
if (real.length === 0)
|
|
162
|
+
return { samples: 0 };
|
|
163
|
+
return {
|
|
164
|
+
fraction: real.reduce((a, s) => a + s.reused / s.incoming, 0) / real.length,
|
|
165
|
+
wastedTokens: real.reduce((a, s) => a + Math.max(0, s.incoming - s.reused), 0) / real.length,
|
|
166
|
+
samples: real.length,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
136
169
|
/** Name the other slowed buckets, so a single headline number does not read
|
|
137
170
|
* as though only one prompt size is affected. */
|
|
138
171
|
function alsoAffected(trend) {
|
|
@@ -226,18 +259,101 @@ export function assessInference(snap, thresholds = DEFAULT_THRESHOLDS) {
|
|
|
226
259
|
`(${Math.floor(snap.poolCells / snap.rails).toLocaleString()} each if evenly divided)`,
|
|
227
260
|
});
|
|
228
261
|
}
|
|
229
|
-
|
|
262
|
+
// 7. Decode decay (strata): the same bucketed early/recent comparison over
|
|
263
|
+
// decode samples, with depth as the bucketing quantity. This is the
|
|
264
|
+
// signal llama.cpp journals cannot give — they never log decode rate.
|
|
265
|
+
// "Sampled window" rather than "process's life": the sample set may be
|
|
266
|
+
// the whole serve log (a process lifetime) or the bounded /metrics
|
|
267
|
+
// ring (a dozen recent requests), and the message must not claim more
|
|
268
|
+
// coverage than the samples carry.
|
|
269
|
+
const decodeTrend = snap.decode !== undefined ? analyzeTrend(snap.decode, thresholds) : undefined;
|
|
270
|
+
if (decodeTrend?.ratio !== undefined) {
|
|
271
|
+
const pct = Math.round((1 - decodeTrend.ratio) * 100);
|
|
272
|
+
if (decodeTrend.ratio < thresholds.degradeRatioRed) {
|
|
273
|
+
findings.push({
|
|
274
|
+
health: 'RED',
|
|
275
|
+
code: 'decode-decay',
|
|
276
|
+
message: `decode in the ${decodeTrend.bucket}-depth bucket fell ${pct}% across the sampled window ` +
|
|
277
|
+
`(${Math.round(decodeTrend.earlyMean)} -> ${Math.round(decodeTrend.recentMean)} tok/s)` +
|
|
278
|
+
alsoAffected(decodeTrend),
|
|
279
|
+
remedy: 'restart the strata serve layer; if it returns within days, check the expert cache and KV residency',
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
else if (decodeTrend.ratio < thresholds.degradeRatioWarn) {
|
|
283
|
+
findings.push({
|
|
284
|
+
health: 'WARN',
|
|
285
|
+
code: 'decode-decay',
|
|
286
|
+
message: `decode in the ${decodeTrend.bucket}-depth bucket is down ${pct}% ` +
|
|
287
|
+
`(${Math.round(decodeTrend.earlyMean)} -> ${Math.round(decodeTrend.recentMean)} tok/s)` +
|
|
288
|
+
alsoAffected(decodeTrend),
|
|
289
|
+
remedy: 'watch it; a restart restores throughput if the decay continues',
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// 8. Prefix-cache failure (strata): recent turns re-reading most of the
|
|
294
|
+
// prompt. Only the TAIL counts: the question is whether the cache is
|
|
295
|
+
// tracking the conversation NOW, and a whole-process mean mixes in
|
|
296
|
+
// every legitimate cold start since the process began — measured on the
|
|
297
|
+
// live 2026-10-04 log, that mean (39%) fired on a stack whose active
|
|
298
|
+
// session was reusing 99%. A single 0% turn right after a client
|
|
299
|
+
// compaction must not fire this on its own, hence the mean over the tail.
|
|
300
|
+
// Samples are SORTED by `at` first: the log source is oldest-first, but
|
|
301
|
+
// the /metrics ring is NEWEST-first, and slicing an unsorted array takes
|
|
302
|
+
// the first N by position — the OLDEST half of a ring, inverting exactly
|
|
303
|
+
// the current-vs-cold-start signal the tail exists for.
|
|
304
|
+
const reuse = snap.reuse !== undefined
|
|
305
|
+
? analyzeReuse([...snap.reuse]
|
|
306
|
+
.sort((a, b) => a.at - b.at)
|
|
307
|
+
.slice(Math.max(0, snap.reuse.length - thresholds.reuseTailSamples)))
|
|
308
|
+
: undefined;
|
|
309
|
+
if (reuse?.fraction !== undefined && reuse.fraction < thresholds.reuseWarn) {
|
|
310
|
+
findings.push({
|
|
311
|
+
health: reuse.wastedTokens > 20_000 ? 'RED' : 'WARN',
|
|
312
|
+
code: 'cache-miss',
|
|
313
|
+
message: `the prefix cache is re-reading ${Math.round((1 - reuse.fraction) * 100)}% of prompts ` +
|
|
314
|
+
`(~${Math.round(reuse.wastedTokens).toLocaleString()} tokens re-read per turn)`,
|
|
315
|
+
remedy: 'a fresh or just-compacted conversation legitimately re-reads; sustained misses mean the ' +
|
|
316
|
+
'conversation cache is not tracking — check conversation_cache slots and restart if it persists',
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
// 9. VRAM headroom (strata's own reserve-aware reading). The capacity
|
|
320
|
+
// doctor checks the HOST-wide number; this one catches another process
|
|
321
|
+
// eating the card from under strata.
|
|
322
|
+
if (snap.vramFreeMiB !== undefined && snap.vramFreeMiB < thresholds.vramHeadroomWarnMiB) {
|
|
323
|
+
findings.push({
|
|
324
|
+
health: 'WARN',
|
|
325
|
+
code: 'vram-headroom',
|
|
326
|
+
message: `strata reports only ${snap.vramFreeMiB} MiB free (reserve-aware) — below the ` +
|
|
327
|
+
`${thresholds.vramHeadroomWarnMiB} MiB headroom floor`,
|
|
328
|
+
remedy: 'something is eating the card (another model process, a vision request); free it or lower --max-context',
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
const worst = findings.reduce((acc, f) => (rank[f.health] > rank[acc] ? f.health : acc), 'GREEN');
|
|
230
332
|
// No evidence at all is UNKNOWN, never a fabricated GREEN — the same
|
|
231
333
|
// fail-open posture the capacity doctor uses.
|
|
232
334
|
//
|
|
233
|
-
// But "no evidence" is about the SAMPLED signals (trend, checkpoints, KV
|
|
335
|
+
// But "no evidence" is about the SAMPLED signals (trend, checkpoints, KV,
|
|
336
|
+
// decode, reuse, VRAM). When they ARE present and nothing crossed a
|
|
337
|
+
// threshold, GREEN is the honest verdict: the 2026-10-03 draft left a
|
|
338
|
+
// healthy single-rail stack at UNKNOWN (GREEN previously required a
|
|
339
|
+
// finding to exist, and only multi-rail llama.cpp emitted one — the
|
|
340
|
+
// shared-pool info), which made a working strata backend indistinguishable
|
|
341
|
+
// from a blind probe for every monitor parsing --json.
|
|
342
|
+
//
|
|
234
343
|
// A finding that fired is itself evidence, so an actionable one must never
|
|
235
344
|
// be masked by an UNKNOWN rollup: generation timeouts are counted from the
|
|
236
345
|
// proxy journal and can fire with none of the sampled signals present,
|
|
237
346
|
// which let a RED finding pass --strict with exit 0.
|
|
238
347
|
const actionable = findings.some((f) => f.health !== 'GREEN');
|
|
239
|
-
const noSampledEvidence = trend.ratio === undefined &&
|
|
240
|
-
|
|
241
|
-
|
|
348
|
+
const noSampledEvidence = trend.ratio === undefined &&
|
|
349
|
+
checkpoints.samples === 0 &&
|
|
350
|
+
snap.kvBitsPerValue === undefined &&
|
|
351
|
+
(snap.decode?.length ?? 0) === 0 &&
|
|
352
|
+
(snap.reuse?.length ?? 0) === 0 &&
|
|
353
|
+
snap.vramFreeMiB === undefined;
|
|
354
|
+
const health = noSampledEvidence && !actionable ? 'UNKNOWN' : worst;
|
|
355
|
+
return snap.backend !== undefined
|
|
356
|
+
? { health, findings, trend, checkpoints, backend: snap.backend, decodeTrend, reuse }
|
|
357
|
+
: { health, findings, trend, checkpoints };
|
|
242
358
|
}
|
|
243
359
|
//# sourceMappingURL=analysis.js.map
|