@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,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strata backend collectors for `uap inference health`.
|
|
3
|
+
*
|
|
4
|
+
* The local Qwen3.8 backend moved (2026-10-04) from the buun llama.cpp fork
|
|
5
|
+
* behind uap-gsq-rco-server.service to the Strata serve layer
|
|
6
|
+
* (`strata serve/server.py`, engine build 0.1.37, flash-next IQ3_XXS pack)
|
|
7
|
+
* on :8080. Strata deliberately mimics the llama.cpp HTTP surface (/v1,
|
|
8
|
+
* /slots, /props) but its /metrics is a JSON document, not Prometheus text —
|
|
9
|
+
* the llamacpp: counter names the existing probe scrapes do not exist there.
|
|
10
|
+
*
|
|
11
|
+
* Two sources, both parsed purely here:
|
|
12
|
+
*
|
|
13
|
+
* - `/metrics` (JSON): `engine` (pool size, KV kind, VRAM free), `live`
|
|
14
|
+
* (in-flight request), and `requests` — a bounded ring of COMPLETED
|
|
15
|
+
* requests with per-request prompt/decode timing and cache reuse. The
|
|
16
|
+
* ring is small (observed: 12), so it gives recent detail but cannot
|
|
17
|
+
* carry a life-of-process trend on its own.
|
|
18
|
+
* - the serve log (text): one `strata serve: prompt ...` summary line per
|
|
19
|
+
* completed request, appended for the process's whole life (observed:
|
|
20
|
+
* 4400+ lines over ~23h). These lines carry NO timestamps, so the
|
|
21
|
+
* parser assigns each sample a monotonically increasing index as its
|
|
22
|
+
* `at` clock — `analyzeTrend` only requires a monotonic clock, and
|
|
23
|
+
* early-vs-recent split by log position is exactly the process-lifetime
|
|
24
|
+
* comparison it wants. The price: strata log trends cannot be windowed
|
|
25
|
+
* with --since/--until; windowed analysis uses the /metrics ring, which
|
|
26
|
+
* does carry epoch timestamps, and honestly reports "not enough samples"
|
|
27
|
+
* when the ring is short.
|
|
28
|
+
*/
|
|
29
|
+
import type { PrefillSample, ReuseSample, TrendSample } from './analysis.js';
|
|
30
|
+
export interface DraftSample {
|
|
31
|
+
accepted: number;
|
|
32
|
+
offered: number;
|
|
33
|
+
}
|
|
34
|
+
/** Everything the health analysis consumes, from either source. */
|
|
35
|
+
export interface StrataSamples {
|
|
36
|
+
/** Prefill throughput per request (tokens read, not reused). */
|
|
37
|
+
prefill: PrefillSample[];
|
|
38
|
+
/** Decode throughput per request. `tokens` carries the prompt DEPTH —
|
|
39
|
+
* decode speed depends on context depth, so depth is what the trend
|
|
40
|
+
* buckets must compare like-for-like. */
|
|
41
|
+
decode: TrendSample[];
|
|
42
|
+
reuse: ReuseSample[];
|
|
43
|
+
drafts: DraftSample[];
|
|
44
|
+
}
|
|
45
|
+
/** Parsed `engine` block of /metrics. */
|
|
46
|
+
export interface StrataEngine {
|
|
47
|
+
model?: string;
|
|
48
|
+
maxContext?: number;
|
|
49
|
+
/** KV kind as strata spells it, e.g. 'int8'. */
|
|
50
|
+
kv?: string;
|
|
51
|
+
/** Strata's own reserve-aware view of free VRAM (MiB). */
|
|
52
|
+
vramFreeMiB?: number;
|
|
53
|
+
version?: string;
|
|
54
|
+
spec?: number;
|
|
55
|
+
kvResident?: number;
|
|
56
|
+
}
|
|
57
|
+
export interface StrataLive {
|
|
58
|
+
state?: string;
|
|
59
|
+
phase?: string;
|
|
60
|
+
promptTokens?: number;
|
|
61
|
+
}
|
|
62
|
+
/** Parse a strata serve log (or any slice of one) into samples. `at` values
|
|
63
|
+
* are line indices — monotonic, comparable, and deliberately NOT wall time
|
|
64
|
+
* (the log has no timestamps). */
|
|
65
|
+
export declare function parseStrataLog(text: string): StrataSamples;
|
|
66
|
+
/** Structural guard for the /metrics document: it must have an `engine`
|
|
67
|
+
* object with a `model` string. Anything else (Prometheus text from a
|
|
68
|
+
* llama.cpp server, an HTML error page) is "not strata", not a parse error. */
|
|
69
|
+
export declare function looksLikeStrataMetrics(parsed: unknown): parsed is {
|
|
70
|
+
engine: Record<string, unknown>;
|
|
71
|
+
} & {
|
|
72
|
+
live?: Record<string, unknown>;
|
|
73
|
+
requests?: unknown[];
|
|
74
|
+
};
|
|
75
|
+
/** Parse the /metrics `engine` + `live` blocks into typed facts. */
|
|
76
|
+
export declare function parseStrataEngine(doc: {
|
|
77
|
+
engine: Record<string, unknown>;
|
|
78
|
+
live?: Record<string, unknown>;
|
|
79
|
+
}): {
|
|
80
|
+
engine: StrataEngine;
|
|
81
|
+
live: StrataLive;
|
|
82
|
+
};
|
|
83
|
+
/** Parse the /metrics `requests` ring into samples. Ring entries carry real
|
|
84
|
+
* epoch seconds in `time`, so these samples CAN be windowed — unlike
|
|
85
|
+
* log-derived samples. An entry without a finite `time` is DROPPED, not
|
|
86
|
+
* given a fallback index clock: mixing a small line-index with epoch-ms
|
|
87
|
+
* inside one array would scramble analyzeTrend's era split, and the repo
|
|
88
|
+
* doctrine is UNKNOWN over a guess. */
|
|
89
|
+
export declare function parseStrataRequests(requests: unknown[] | undefined): StrataSamples;
|
|
90
|
+
/** Strata's KV kind string → bits per value, for the pool-quality signal.
|
|
91
|
+
* Unknown kinds yield undefined (UNKNOWN, never a guess). */
|
|
92
|
+
export declare function strataKvBitsPerValue(kv: string | undefined): number | undefined;
|
|
93
|
+
/** Mean draft-acceptance rate over the samples, when any exist. */
|
|
94
|
+
export declare function draftAcceptanceRate(drafts: DraftSample[]): number | undefined;
|
|
95
|
+
//# sourceMappingURL=strata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strata.d.ts","sourceRoot":"","sources":["../../src/inference/strata.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE7E,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,mEAAmE;AACnE,MAAM,WAAW,aAAa;IAC5B,gEAAgE;IAChE,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB;;6CAEyC;IACzC,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AAED,yCAAyC;AACzC,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAiBD;;kCAEkC;AAClC,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CA0B1D;AAED;;+EAE+E;AAC/E,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,IAAI;IAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAAG;IACvG,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;CACtB,CAQA;AAKD,oEAAoE;AACpE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE;IACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,GAAG;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAkB7C;AAED;;;;;uCAKuC;AACvC,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,SAAS,GAAG,aAAa,CA4BlF;AAED;6DAC6D;AAC7D,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAW/E;AAED,mEAAmE;AACnE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,GAAG,SAAS,CAI7E"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strata backend collectors for `uap inference health`.
|
|
3
|
+
*
|
|
4
|
+
* The local Qwen3.8 backend moved (2026-10-04) from the buun llama.cpp fork
|
|
5
|
+
* behind uap-gsq-rco-server.service to the Strata serve layer
|
|
6
|
+
* (`strata serve/server.py`, engine build 0.1.37, flash-next IQ3_XXS pack)
|
|
7
|
+
* on :8080. Strata deliberately mimics the llama.cpp HTTP surface (/v1,
|
|
8
|
+
* /slots, /props) but its /metrics is a JSON document, not Prometheus text —
|
|
9
|
+
* the llamacpp: counter names the existing probe scrapes do not exist there.
|
|
10
|
+
*
|
|
11
|
+
* Two sources, both parsed purely here:
|
|
12
|
+
*
|
|
13
|
+
* - `/metrics` (JSON): `engine` (pool size, KV kind, VRAM free), `live`
|
|
14
|
+
* (in-flight request), and `requests` — a bounded ring of COMPLETED
|
|
15
|
+
* requests with per-request prompt/decode timing and cache reuse. The
|
|
16
|
+
* ring is small (observed: 12), so it gives recent detail but cannot
|
|
17
|
+
* carry a life-of-process trend on its own.
|
|
18
|
+
* - the serve log (text): one `strata serve: prompt ...` summary line per
|
|
19
|
+
* completed request, appended for the process's whole life (observed:
|
|
20
|
+
* 4400+ lines over ~23h). These lines carry NO timestamps, so the
|
|
21
|
+
* parser assigns each sample a monotonically increasing index as its
|
|
22
|
+
* `at` clock — `analyzeTrend` only requires a monotonic clock, and
|
|
23
|
+
* early-vs-recent split by log position is exactly the process-lifetime
|
|
24
|
+
* comparison it wants. The price: strata log trends cannot be windowed
|
|
25
|
+
* with --since/--until; windowed analysis uses the /metrics ring, which
|
|
26
|
+
* does carry epoch timestamps, and honestly reports "not enough samples"
|
|
27
|
+
* when the ring is short.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* One completed-request summary line from the strata serve log:
|
|
31
|
+
*
|
|
32
|
+
* strata serve: prompt 19307 tokens = 15536 reused + 3771 read in 4189 ms
|
|
33
|
+
* (900.2 tok/s), 117 generated in 2471 ms (47.4 tok/s), drafts accepted
|
|
34
|
+
* 80 of 93, 2 checkpoints
|
|
35
|
+
*
|
|
36
|
+
* The trailing segments (drafts, checkpoints) are optional so a strata
|
|
37
|
+
* release that drops one keeps the rest of the sample intact; the core
|
|
38
|
+
* segment order is fixed.
|
|
39
|
+
*/
|
|
40
|
+
const REQUEST_LINE_RE = /prompt (\d+) tokens = (\d+) reused \+ (\d+) read in (\d+(?:\.\d+)?) ms \((\d+(?:\.\d+)?) tok\/s\), (\d+) generated in (\d+(?:\.\d+)?) ms \((\d+(?:\.\d+)?) tok\/s\)/;
|
|
41
|
+
const DRAFTS_RE = /drafts accepted (\d+) of (\d+)/;
|
|
42
|
+
/** Parse a strata serve log (or any slice of one) into samples. `at` values
|
|
43
|
+
* are line indices — monotonic, comparable, and deliberately NOT wall time
|
|
44
|
+
* (the log has no timestamps). */
|
|
45
|
+
export function parseStrataLog(text) {
|
|
46
|
+
const out = { prefill: [], decode: [], reuse: [], drafts: [] };
|
|
47
|
+
let at = 0;
|
|
48
|
+
for (const line of text.split('\n')) {
|
|
49
|
+
at += 1;
|
|
50
|
+
const m = REQUEST_LINE_RE.exec(line);
|
|
51
|
+
if (!m)
|
|
52
|
+
continue;
|
|
53
|
+
const promptTokens = Number(m[1]);
|
|
54
|
+
const reused = Number(m[2]);
|
|
55
|
+
const read = Number(m[3]);
|
|
56
|
+
const prefillTps = Number(m[5]);
|
|
57
|
+
const generated = Number(m[6]);
|
|
58
|
+
const decodeTps = Number(m[8]);
|
|
59
|
+
if (![promptTokens, reused, read, prefillTps, generated, decodeTps].every(Number.isFinite))
|
|
60
|
+
continue;
|
|
61
|
+
if (prefillTps < 0 || decodeTps < 0)
|
|
62
|
+
continue; // parse artefact, not a measurement
|
|
63
|
+
out.prefill.push({ at, tokens: read, tokensPerSecond: prefillTps });
|
|
64
|
+
out.decode.push({ at, tokens: promptTokens, tokensPerSecond: decodeTps });
|
|
65
|
+
out.reuse.push({ at, incoming: promptTokens, reused });
|
|
66
|
+
const d = DRAFTS_RE.exec(line);
|
|
67
|
+
if (d) {
|
|
68
|
+
const accepted = Number(d[1]);
|
|
69
|
+
const offered = Number(d[2]);
|
|
70
|
+
if (Number.isFinite(accepted) && Number.isFinite(offered))
|
|
71
|
+
out.drafts.push({ accepted, offered });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
/** Structural guard for the /metrics document: it must have an `engine`
|
|
77
|
+
* object with a `model` string. Anything else (Prometheus text from a
|
|
78
|
+
* llama.cpp server, an HTML error page) is "not strata", not a parse error. */
|
|
79
|
+
export function looksLikeStrataMetrics(parsed) {
|
|
80
|
+
return (typeof parsed === 'object' &&
|
|
81
|
+
parsed !== null &&
|
|
82
|
+
typeof parsed.engine === 'object' &&
|
|
83
|
+
parsed.engine !== null &&
|
|
84
|
+
typeof parsed.engine.model === 'string');
|
|
85
|
+
}
|
|
86
|
+
const finiteNumber = (v) => typeof v === 'number' && Number.isFinite(v) ? v : undefined;
|
|
87
|
+
/** Parse the /metrics `engine` + `live` blocks into typed facts. */
|
|
88
|
+
export function parseStrataEngine(doc) {
|
|
89
|
+
const e = doc.engine;
|
|
90
|
+
return {
|
|
91
|
+
engine: {
|
|
92
|
+
model: typeof e.model === 'string' ? e.model : undefined,
|
|
93
|
+
maxContext: finiteNumber(e.max_context) ?? finiteNumber(e.context),
|
|
94
|
+
kv: typeof e.kv === 'string' ? e.kv : undefined,
|
|
95
|
+
vramFreeMiB: finiteNumber(e.vram_free_mib),
|
|
96
|
+
version: typeof e.version === 'string' ? e.version : undefined,
|
|
97
|
+
spec: finiteNumber(e.spec),
|
|
98
|
+
kvResident: finiteNumber(e.kv_resident),
|
|
99
|
+
},
|
|
100
|
+
live: {
|
|
101
|
+
state: typeof doc.live?.state === 'string' ? doc.live.state : undefined,
|
|
102
|
+
phase: typeof doc.live?.phase === 'string' ? doc.live.phase : undefined,
|
|
103
|
+
promptTokens: finiteNumber(doc.live?.prompt_tokens),
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/** Parse the /metrics `requests` ring into samples. Ring entries carry real
|
|
108
|
+
* epoch seconds in `time`, so these samples CAN be windowed — unlike
|
|
109
|
+
* log-derived samples. An entry without a finite `time` is DROPPED, not
|
|
110
|
+
* given a fallback index clock: mixing a small line-index with epoch-ms
|
|
111
|
+
* inside one array would scramble analyzeTrend's era split, and the repo
|
|
112
|
+
* doctrine is UNKNOWN over a guess. */
|
|
113
|
+
export function parseStrataRequests(requests) {
|
|
114
|
+
const out = { prefill: [], decode: [], reuse: [], drafts: [] };
|
|
115
|
+
for (const r of requests ?? []) {
|
|
116
|
+
if (typeof r !== 'object' || r === null)
|
|
117
|
+
continue;
|
|
118
|
+
const req = r;
|
|
119
|
+
const time = finiteNumber(req.time);
|
|
120
|
+
if (time === undefined)
|
|
121
|
+
continue; // no clock, no sample — never a guess
|
|
122
|
+
const at = time * 1000;
|
|
123
|
+
if (!Number.isFinite(at))
|
|
124
|
+
continue; // overflow guard, same doctrine
|
|
125
|
+
const read = finiteNumber(req.prompt_read);
|
|
126
|
+
const promptMs = finiteNumber(req.prompt_ms);
|
|
127
|
+
const promptTokens = finiteNumber(req.prompt_tokens);
|
|
128
|
+
const decodeTps = finiteNumber(req.decode_tok_s);
|
|
129
|
+
if (read !== undefined && promptMs !== undefined && promptMs > 0) {
|
|
130
|
+
out.prefill.push({ at, tokens: read, tokensPerSecond: read / (promptMs / 1000) });
|
|
131
|
+
}
|
|
132
|
+
if (decodeTps !== undefined && promptTokens !== undefined) {
|
|
133
|
+
out.decode.push({ at, tokens: promptTokens, tokensPerSecond: decodeTps });
|
|
134
|
+
}
|
|
135
|
+
const reused = finiteNumber(req.reused);
|
|
136
|
+
if (promptTokens !== undefined && reused !== undefined) {
|
|
137
|
+
out.reuse.push({ at, incoming: promptTokens, reused });
|
|
138
|
+
}
|
|
139
|
+
const accepted = finiteNumber(req.drafts_accepted);
|
|
140
|
+
const offered = finiteNumber(req.drafts_offered);
|
|
141
|
+
if (accepted !== undefined && offered !== undefined)
|
|
142
|
+
out.drafts.push({ accepted, offered });
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
/** Strata's KV kind string → bits per value, for the pool-quality signal.
|
|
147
|
+
* Unknown kinds yield undefined (UNKNOWN, never a guess). */
|
|
148
|
+
export function strataKvBitsPerValue(kv) {
|
|
149
|
+
switch (kv) {
|
|
150
|
+
case 'int8':
|
|
151
|
+
return 8;
|
|
152
|
+
case 'int4':
|
|
153
|
+
return 4;
|
|
154
|
+
case 'fp16':
|
|
155
|
+
return 16;
|
|
156
|
+
default:
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/** Mean draft-acceptance rate over the samples, when any exist. */
|
|
161
|
+
export function draftAcceptanceRate(drafts) {
|
|
162
|
+
const offered = drafts.reduce((a, d) => a + d.offered, 0);
|
|
163
|
+
if (offered <= 0)
|
|
164
|
+
return undefined;
|
|
165
|
+
return drafts.reduce((a, d) => a + d.accepted, 0) / offered;
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=strata.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strata.js","sourceRoot":"","sources":["../../src/inference/strata.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
/**
|
|
3
|
+
* Singleton SQLite store for the mission ledger, mirroring the coordination
|
|
4
|
+
* database's concurrency posture (WAL + busy timeout) since the orchestrator
|
|
5
|
+
* daemon and CLI invocations read and write it concurrently.
|
|
6
|
+
*/
|
|
7
|
+
export declare class MissionDatabase {
|
|
8
|
+
private db;
|
|
9
|
+
private static instance;
|
|
10
|
+
private constructor();
|
|
11
|
+
static getInstance(dbPath: string): MissionDatabase;
|
|
12
|
+
static resetInstance(): void;
|
|
13
|
+
getDatabase(): Database.Database;
|
|
14
|
+
close(): void;
|
|
15
|
+
private initSchema;
|
|
16
|
+
/** Ledger schema version. Bump + add a case in `migrate` when it changes. */
|
|
17
|
+
private static readonly SCHEMA_VERSION;
|
|
18
|
+
/**
|
|
19
|
+
* Stamp `PRAGMA user_version` and run any pending migrations. Without the
|
|
20
|
+
* stamp, the first post-release schema change becomes a breaking migration
|
|
21
|
+
* (architect review P2-3).
|
|
22
|
+
*/
|
|
23
|
+
private stampSchemaVersion;
|
|
24
|
+
/** Placeholder for future versioned migrations (v1 is the baseline). */
|
|
25
|
+
private migrate;
|
|
26
|
+
}
|
|
27
|
+
/** Default ledger location: `<projectRoot>/.uap/missions.db`. */
|
|
28
|
+
export declare function getMissionDbPath(projectRoot: string): string;
|
|
29
|
+
//# sourceMappingURL=database.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/mission/database.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAItC;;;;GAIG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAgC;IAEvD,OAAO;IAeP,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe;IAOnD,MAAM,CAAC,aAAa,IAAI,IAAI;IAO5B,WAAW,IAAI,QAAQ,CAAC,QAAQ;IAIhC,KAAK,IAAI,IAAI;IAIb,OAAO,CAAC,UAAU;IA+DlB,6EAA6E;IAC7E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAK;IAE3C;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAS1B,wEAAwE;IACxE,OAAO,CAAC,OAAO;CAGhB;AAED,iEAAiE;AACjE,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE5D"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
/**
|
|
5
|
+
* Singleton SQLite store for the mission ledger, mirroring the coordination
|
|
6
|
+
* database's concurrency posture (WAL + busy timeout) since the orchestrator
|
|
7
|
+
* daemon and CLI invocations read and write it concurrently.
|
|
8
|
+
*/
|
|
9
|
+
export class MissionDatabase {
|
|
10
|
+
db;
|
|
11
|
+
static instance = null;
|
|
12
|
+
constructor(dbPath) {
|
|
13
|
+
const dir = dirname(dbPath);
|
|
14
|
+
if (!existsSync(dir)) {
|
|
15
|
+
mkdirSync(dir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
this.db = new Database(dbPath);
|
|
18
|
+
this.db.pragma('journal_mode = WAL');
|
|
19
|
+
this.db.pragma('synchronous = NORMAL');
|
|
20
|
+
this.db.pragma('busy_timeout = 10000');
|
|
21
|
+
// The REFERENCES clauses below are real: SQLite defaults foreign_keys OFF,
|
|
22
|
+
// which would leave them decorative (code review P3).
|
|
23
|
+
this.db.pragma('foreign_keys = ON');
|
|
24
|
+
this.initSchema();
|
|
25
|
+
}
|
|
26
|
+
static getInstance(dbPath) {
|
|
27
|
+
if (!MissionDatabase.instance) {
|
|
28
|
+
MissionDatabase.instance = new MissionDatabase(dbPath);
|
|
29
|
+
}
|
|
30
|
+
return MissionDatabase.instance;
|
|
31
|
+
}
|
|
32
|
+
static resetInstance() {
|
|
33
|
+
if (MissionDatabase.instance) {
|
|
34
|
+
MissionDatabase.instance.close();
|
|
35
|
+
MissionDatabase.instance = null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
getDatabase() {
|
|
39
|
+
return this.db;
|
|
40
|
+
}
|
|
41
|
+
close() {
|
|
42
|
+
this.db.close();
|
|
43
|
+
}
|
|
44
|
+
initSchema() {
|
|
45
|
+
this.db.exec(`
|
|
46
|
+
-- Missions: durable goals above deliver runs.
|
|
47
|
+
CREATE TABLE IF NOT EXISTS missions (
|
|
48
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
49
|
+
goal TEXT NOT NULL,
|
|
50
|
+
acceptance TEXT NOT NULL,
|
|
51
|
+
acceptance_hash TEXT NOT NULL,
|
|
52
|
+
status TEXT NOT NULL CHECK(status IN ('active', 'paused', 'delivered', 'failed')),
|
|
53
|
+
preset_id TEXT,
|
|
54
|
+
latest_run_id TEXT,
|
|
55
|
+
launch_count INTEGER NOT NULL DEFAULT 0,
|
|
56
|
+
relaunch_count INTEGER NOT NULL DEFAULT 0,
|
|
57
|
+
last_launch_at TEXT,
|
|
58
|
+
launch_pid INTEGER,
|
|
59
|
+
created_at TEXT NOT NULL,
|
|
60
|
+
updated_at TEXT NOT NULL
|
|
61
|
+
);
|
|
62
|
+
CREATE INDEX IF NOT EXISTS idx_missions_status ON missions(status);
|
|
63
|
+
|
|
64
|
+
-- Attempt history: one row per launch/outcome. The failed rows are the
|
|
65
|
+
-- struggle metric — repeated failures per task ref trigger escalation.
|
|
66
|
+
CREATE TABLE IF NOT EXISTS mission_attempts (
|
|
67
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
68
|
+
mission_id INTEGER NOT NULL REFERENCES missions(id),
|
|
69
|
+
run_id TEXT,
|
|
70
|
+
task_ref TEXT,
|
|
71
|
+
outcome TEXT NOT NULL CHECK(outcome IN ('launched', 'delivered', 'failed', 'interrupted')),
|
|
72
|
+
summary TEXT,
|
|
73
|
+
observed_at TEXT,
|
|
74
|
+
created_at TEXT NOT NULL
|
|
75
|
+
);
|
|
76
|
+
CREATE INDEX IF NOT EXISTS idx_attempts_mission ON mission_attempts(mission_id);
|
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_attempts_run ON mission_attempts(run_id);
|
|
78
|
+
|
|
79
|
+
-- Approval-gated salvage proposals seeded from failed attempts.
|
|
80
|
+
CREATE TABLE IF NOT EXISTS salvage_proposals (
|
|
81
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
82
|
+
mission_id INTEGER NOT NULL REFERENCES missions(id),
|
|
83
|
+
from_attempt_id INTEGER,
|
|
84
|
+
task_ref TEXT,
|
|
85
|
+
proposal TEXT NOT NULL,
|
|
86
|
+
status TEXT NOT NULL CHECK(status IN ('proposed', 'approved', 'rejected')),
|
|
87
|
+
created_at TEXT NOT NULL,
|
|
88
|
+
decided_at TEXT
|
|
89
|
+
);
|
|
90
|
+
CREATE INDEX IF NOT EXISTS idx_salvage_mission ON salvage_proposals(mission_id);
|
|
91
|
+
CREATE INDEX IF NOT EXISTS idx_salvage_status ON salvage_proposals(status);
|
|
92
|
+
|
|
93
|
+
-- Explicit supersede history for frozen acceptance criteria.
|
|
94
|
+
CREATE TABLE IF NOT EXISTS acceptance_revisions (
|
|
95
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
96
|
+
mission_id INTEGER NOT NULL REFERENCES missions(id),
|
|
97
|
+
prior_acceptance TEXT NOT NULL,
|
|
98
|
+
prior_hash TEXT NOT NULL,
|
|
99
|
+
reason TEXT NOT NULL,
|
|
100
|
+
created_at TEXT NOT NULL
|
|
101
|
+
);
|
|
102
|
+
CREATE INDEX IF NOT EXISTS idx_acceptance_revisions_mission ON acceptance_revisions(mission_id);
|
|
103
|
+
`);
|
|
104
|
+
this.stampSchemaVersion();
|
|
105
|
+
}
|
|
106
|
+
/** Ledger schema version. Bump + add a case in `migrate` when it changes. */
|
|
107
|
+
static SCHEMA_VERSION = 1;
|
|
108
|
+
/**
|
|
109
|
+
* Stamp `PRAGMA user_version` and run any pending migrations. Without the
|
|
110
|
+
* stamp, the first post-release schema change becomes a breaking migration
|
|
111
|
+
* (architect review P2-3).
|
|
112
|
+
*/
|
|
113
|
+
stampSchemaVersion() {
|
|
114
|
+
const current = this.db.pragma('user_version', { simple: true });
|
|
115
|
+
if (current === MissionDatabase.SCHEMA_VERSION)
|
|
116
|
+
return;
|
|
117
|
+
if (current > 0 && current < MissionDatabase.SCHEMA_VERSION) {
|
|
118
|
+
this.migrate(current);
|
|
119
|
+
}
|
|
120
|
+
this.db.pragma(`user_version = ${MissionDatabase.SCHEMA_VERSION}`);
|
|
121
|
+
}
|
|
122
|
+
/** Placeholder for future versioned migrations (v1 is the baseline). */
|
|
123
|
+
migrate(_fromVersion) {
|
|
124
|
+
/* no migrations yet — schema v1 is the first released shape */
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Default ledger location: `<projectRoot>/.uap/missions.db`. */
|
|
128
|
+
export function getMissionDbPath(projectRoot) {
|
|
129
|
+
return join(projectRoot, '.uap', 'missions.db');
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=database.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/mission/database.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAErC;;;;GAIG;AACH,MAAM,OAAO,eAAe;IAClB,EAAE,CAAoB;IACtB,MAAM,CAAC,QAAQ,GAA2B,IAAI,CAAC;IAEvD,YAAoB,MAAc;QAChC,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QAC5B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtC,CAAC;QACD,IAAI,CAAC,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC/B,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;QACrC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;QACvC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;QACvC,2EAA2E;QAC3E,sDAAsD;QACtD,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;QACpC,IAAI,CAAC,UAAU,EAAE,CAAC;IACpB,CAAC;IAED,MAAM,CAAC,WAAW,CAAC,MAAc;QAC/B,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,CAAC;YAC9B,eAAe,CAAC,QAAQ,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC;QACzD,CAAC;QACD,OAAO,eAAe,CAAC,QAAQ,CAAC;IAClC,CAAC;IAED,MAAM,CAAC,aAAa;QAClB,IAAI,eAAe,CAAC,QAAQ,EAAE,CAAC;YAC7B,eAAe,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACjC,eAAe,CAAC,QAAQ,GAAG,IAAI,CAAC;QAClC,CAAC;IACH,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;IAED,KAAK;QACH,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC;IAEO,UAAU;QAChB,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA0DZ,CAAC,CAAC;QACH,IAAI,CAAC,kBAAkB,EAAE,CAAC;IAC5B,CAAC;IAED,6EAA6E;IACrE,MAAM,CAAU,cAAc,GAAG,CAAC,CAAC;IAE3C;;;;OAIG;IACK,kBAAkB;QACxB,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,cAAc,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAW,CAAC;QAC3E,IAAI,OAAO,KAAK,eAAe,CAAC,cAAc;YAAE,OAAO;QACvD,IAAI,OAAO,GAAG,CAAC,IAAI,OAAO,GAAG,eAAe,CAAC,cAAc,EAAE,CAAC;YAC5D,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;QACD,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,kBAAkB,eAAe,CAAC,cAAc,EAAE,CAAC,CAAC;IACrE,CAAC;IAED,wEAAwE;IAChE,OAAO,CAAC,YAAoB;QAClC,+DAA+D;IACjE,CAAC;;AAGH,iEAAiE;AACjE,MAAM,UAAU,gBAAgB,CAAC,WAAmB;IAClD,OAAO,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mission ledger service — typed operations over the mission database.
|
|
3
|
+
*
|
|
4
|
+
* The ledger is the durable goal layer: a mission's acceptance criteria are
|
|
5
|
+
* FROZEN at creation (sha256) and only change through an explicit, recorded
|
|
6
|
+
* replan. Attempt history feeds the struggle metric; failed attempts seed
|
|
7
|
+
* approval-gated salvage proposals.
|
|
8
|
+
*/
|
|
9
|
+
import type Database from 'better-sqlite3';
|
|
10
|
+
import type { Mission, MissionAttempt, MissionAttemptOutcome, MissionStatus, SalvageProposal, SalvageStatus, AcceptanceRevision } from '../types/mission.js';
|
|
11
|
+
/** sha256 of the trimmed acceptance text — the freeze-time hash. */
|
|
12
|
+
export declare function hashAcceptance(acceptance: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Exponential relaunch backoff (minutes): 15, 30, 60, ... capped at one day.
|
|
15
|
+
* A crash-looping mission must not burn cycles every poll — the same lesson
|
|
16
|
+
* as the systemd start-rate limiter: back off, don't hammer.
|
|
17
|
+
*/
|
|
18
|
+
export declare function relaunchBackoffMinutes(relaunchCount: number): number;
|
|
19
|
+
/** Open (or reuse) the mission database for a project root. PURE-ish: no writes. */
|
|
20
|
+
export declare function openMissionDb(projectRoot: string): Database.Database;
|
|
21
|
+
/** Test seam: reset the singleton so a new path is honored. */
|
|
22
|
+
export declare function resetMissionDb(): void;
|
|
23
|
+
/** Create a mission with frozen acceptance criteria. */
|
|
24
|
+
export declare function createMission(db: Database.Database, input: {
|
|
25
|
+
goal: string;
|
|
26
|
+
acceptance: string;
|
|
27
|
+
presetId?: string;
|
|
28
|
+
}): Mission;
|
|
29
|
+
export declare function getMission(db: Database.Database, id: number): Mission;
|
|
30
|
+
export declare function listMissions(db: Database.Database, opts?: {
|
|
31
|
+
status?: MissionStatus;
|
|
32
|
+
limit?: number;
|
|
33
|
+
}): Mission[];
|
|
34
|
+
export declare function setMissionStatus(db: Database.Database, id: number, status: MissionStatus): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Explicit replan: supersede the frozen acceptance criteria. The prior text
|
|
37
|
+
* and hash move to the revisions history — never a silent change. The run
|
|
38
|
+
* link and backoff counters RESET: the prior run embeds the OLD criteria, so
|
|
39
|
+
* resuming it could deliver against superseded acceptance. The next poll
|
|
40
|
+
* launches a fresh run under the new criteria (new marker, new pid).
|
|
41
|
+
*/
|
|
42
|
+
export declare function replanAcceptance(db: Database.Database, id: number, newAcceptance: string, reason: string): Mission;
|
|
43
|
+
/** Does `acceptance` match the mission's frozen criteria? (merge-gate hook). */
|
|
44
|
+
export declare function verifyAcceptanceHash(db: Database.Database, id: number, acceptance: string): boolean;
|
|
45
|
+
export declare function listAcceptanceRevisions(db: Database.Database, missionId: number): AcceptanceRevision[];
|
|
46
|
+
/**
|
|
47
|
+
* How recent a claim blocks another: two concurrent cycles (daemon poll +
|
|
48
|
+
* `uap mission create --launch`) must not both spawn. The conditional UPDATE
|
|
49
|
+
* below makes the claim atomic; this window (not the 10-min decision grace)
|
|
50
|
+
* is the anti-race guard.
|
|
51
|
+
*/
|
|
52
|
+
export declare const LAUNCH_CLAIM_MS = 60000;
|
|
53
|
+
/**
|
|
54
|
+
* Atomically claim the right to launch/resume. Returns the launch stamp when
|
|
55
|
+
* this cycle WON the claim, null when another cycle launched within the
|
|
56
|
+
* window (the caller must NOT spawn). Single-statement conditional update —
|
|
57
|
+
* no read-then-write race. `nowMs` is the poll cycle's clock (injected for
|
|
58
|
+
* tests; decisions and claims must share one timeline).
|
|
59
|
+
*/
|
|
60
|
+
export declare function claimLaunch(db: Database.Database, id: number, kind: 'fresh' | 'resume', nowMs?: number): string | null;
|
|
61
|
+
/**
|
|
62
|
+
* Record the pid of the launch we spawned, guarded by OUR claim stamp so a
|
|
63
|
+
* concurrent winner's stamp is never overwritten.
|
|
64
|
+
*/
|
|
65
|
+
export declare function setLaunchPid(db: Database.Database, id: number, stamp: string, pid: number): void;
|
|
66
|
+
/**
|
|
67
|
+
* Link a discovered deliver run to the mission WITHOUT counting a launch:
|
|
68
|
+
* the poll cycle finds the run the mission's own launch created (by its
|
|
69
|
+
* embedded `mission:#<id>` marker) and records the linkage.
|
|
70
|
+
*/
|
|
71
|
+
export declare function linkRun(db: Database.Database, id: number, runId: string): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Record an attempt outcome.
|
|
74
|
+
*
|
|
75
|
+
* Rows tied to a RUN dedupe on (mission, run, outcome, taskRef, observedAt):
|
|
76
|
+
* a poll cycle that re-observes the SAME terminal run state (same
|
|
77
|
+
* updatedAt/exit time) cannot double-count it, while a genuinely NEW event
|
|
78
|
+
* for the same run (a resumed run failing again, a later exit) accumulates
|
|
79
|
+
* into the struggle metric. Without observedAt in the key, a crash-looping
|
|
80
|
+
* run could contribute at most one 'failed' row ever and never reach the
|
|
81
|
+
* struggle threshold (code review P1). Whole-mission notes (no runId) are
|
|
82
|
+
* never deduped: each occurrence is a distinct event.
|
|
83
|
+
*/
|
|
84
|
+
export declare function recordAttempt(db: Database.Database, input: {
|
|
85
|
+
missionId: number;
|
|
86
|
+
runId?: string;
|
|
87
|
+
taskRef?: string;
|
|
88
|
+
outcome: MissionAttemptOutcome;
|
|
89
|
+
summary?: string;
|
|
90
|
+
/** What this observation saw (run.updatedAt / exit.at). */
|
|
91
|
+
observedAt?: string;
|
|
92
|
+
}): void;
|
|
93
|
+
export declare function listAttempts(db: Database.Database, missionId: number): MissionAttempt[];
|
|
94
|
+
/** Struggle metric: failed attempts, whole-mission (no taskRef) or per ref. */
|
|
95
|
+
export declare function failedAttemptCount(db: Database.Database, missionId: number, taskRef?: string): number;
|
|
96
|
+
export declare function proposeSalvage(db: Database.Database, input: {
|
|
97
|
+
missionId: number;
|
|
98
|
+
fromAttemptId?: number;
|
|
99
|
+
taskRef?: string;
|
|
100
|
+
proposal: string;
|
|
101
|
+
}): SalvageProposal;
|
|
102
|
+
/** True when an OPEN proposal already cites the same attempt (dedupe). */
|
|
103
|
+
export declare function hasOpenSalvageForAttempt(db: Database.Database, missionId: number, attemptId?: number): boolean;
|
|
104
|
+
export declare function decideSalvage(db: Database.Database, id: number, status: 'approved' | 'rejected'): SalvageProposal;
|
|
105
|
+
export declare function listSalvage(db: Database.Database, opts?: {
|
|
106
|
+
missionId?: number;
|
|
107
|
+
status?: SalvageStatus;
|
|
108
|
+
}): SalvageProposal[];
|
|
109
|
+
//# sourceMappingURL=ledger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../../src/mission/ledger.ts"],"names":[],"mappings":"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"}
|