@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
|
@@ -5,21 +5,20 @@
|
|
|
5
5
|
* Carried over from QWEN36_PROFILE unchanged EXCEPT for concurrency, which is
|
|
6
6
|
* not a preference here but a property of the server.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* as backpressure and throttles a server that was never saturated.
|
|
8
|
+
* HISTORY: corrected 2026-09-21 (ninfer-serve -> buun-llama-cpp, 1 -> 2
|
|
9
|
+
* rails), then SUPERSEDED 2026-10-04 — the backend is now the strata serve
|
|
10
|
+
* layer on :8080 (flash-next IQ3_XXS pack), which runs ONE rail over a
|
|
11
|
+
* 131072-cell pool. Slots drop 2 -> 1 for the same reason they rose 1 -> 2:
|
|
12
|
+
* slots must track what the server and proxy will actually run in parallel.
|
|
13
|
+
* Declaring MORE than that does not get more in flight — it gets one running
|
|
14
|
+
* and the rest queued, the extra slots buy latency and a longer wedge window
|
|
15
|
+
* rather than throughput, and the adaptive controller then reads the
|
|
16
|
+
* queueing delay as backpressure and throttles a server that was never
|
|
17
|
+
* saturated.
|
|
19
18
|
*
|
|
20
|
-
* Whether
|
|
21
|
-
* it exists:
|
|
22
|
-
*
|
|
19
|
+
* Whether a future second strata rail EARNS anything is a separate question
|
|
20
|
+
* from whether it exists: `uap inference health` (backend strata) answers it
|
|
21
|
+
* once one does.
|
|
23
22
|
*
|
|
24
23
|
* Everything else is deliberately identical to the qwen36 seed: nothing has
|
|
25
24
|
* been re-measured on 3.8 yet, and the tuning loop's job is to beat this seed,
|
|
@@ -38,10 +37,10 @@ export const QWEN38_PROFILE = {
|
|
|
38
37
|
'handsfree.enabled': true,
|
|
39
38
|
'handsfree.intensity': 'aggressive',
|
|
40
39
|
UAP_HANDSFREE_STAGNATION_LIMIT: 6,
|
|
41
|
-
// Concurrency:
|
|
42
|
-
// and the proxy's concurrency/admission limits. Adaptive stays
|
|
43
|
-
// genuinely overloaded server still backs off.
|
|
44
|
-
'modelConcurrency.slots':
|
|
40
|
+
// Concurrency: ONE rail (see the header) — matches the strata server's
|
|
41
|
+
// single slot and the proxy's concurrency/admission limits. Adaptive stays
|
|
42
|
+
// on so a genuinely overloaded server still backs off.
|
|
43
|
+
'modelConcurrency.slots': 1,
|
|
45
44
|
'modelConcurrency.adaptive': true,
|
|
46
45
|
// Memory: bigger short-term window + pattern RAG compensate for weak planning.
|
|
47
46
|
'memory.shortTerm.maxEntries': 80,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qwen38.js","sourceRoot":"","sources":["../../../src/self-tuning/profiles/qwen38.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"qwen38.js","sourceRoot":"","sources":["../../../src/self-tuning/profiles/qwen38.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,MAAM,CAAC,MAAM,cAAc,GAAe;IACxC,yEAAyE;IACzE,iBAAiB,EAAE,IAAI;IACvB,gBAAgB,EAAE,QAAQ;IAC1B,6BAA6B,EAAE,GAAG;IAClC,iBAAiB,EAAE,CAAC;IACpB,wBAAwB,EAAE,KAAK;IAC/B,yEAAyE;IACzE,mBAAmB,EAAE,IAAI;IACzB,qBAAqB,EAAE,YAAY;IACnC,8BAA8B,EAAE,CAAC;IACjC,uEAAuE;IACvE,2EAA2E;IAC3E,uDAAuD;IACvD,wBAAwB,EAAE,CAAC;IAC3B,2BAA2B,EAAE,IAAI;IACjC,+EAA+E;IAC/E,6BAA6B,EAAE,EAAE;IACjC,2BAA2B,EAAE,IAAI;IACjC,uEAAuE;IACvE,wBAAwB,EAAE,IAAI;IAC9B,kEAAkE;IAClE,iCAAiC,EAAE,EAAE;IACrC,kBAAkB,EAAE,IAAI;IACxB,iBAAiB,EAAE,IAAI;CACxB,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mission ledger types — the durable goal layer above deliver runs.
|
|
3
|
+
*
|
|
4
|
+
* A mission is a user-level goal with FROZEN acceptance criteria (the
|
|
5
|
+
* statement-immutability analog): the hash is taken at creation and every
|
|
6
|
+
* later change is an explicit, recorded replan. Missions link to durable
|
|
7
|
+
* deliver runs and survive reboots, crashes, and session boundaries — the
|
|
8
|
+
* orchestrator daemon polls this ledger to keep incomplete missions moving
|
|
9
|
+
* until their acceptance criteria are verified met.
|
|
10
|
+
*/
|
|
11
|
+
export type MissionStatus = 'active' | 'paused' | 'delivered' | 'failed';
|
|
12
|
+
/** What one launch/execution attempt of (part of) a mission ended as. */
|
|
13
|
+
export type MissionAttemptOutcome = 'launched' | 'delivered' | 'failed' | 'interrupted';
|
|
14
|
+
export type SalvageStatus = 'proposed' | 'approved' | 'rejected';
|
|
15
|
+
export interface Mission {
|
|
16
|
+
id: number;
|
|
17
|
+
goal: string;
|
|
18
|
+
/** Acceptance criteria text, frozen at creation (see acceptanceHash). */
|
|
19
|
+
acceptance: string;
|
|
20
|
+
/** sha256 of the trimmed acceptance text at freeze time. */
|
|
21
|
+
acceptanceHash: string;
|
|
22
|
+
status: MissionStatus;
|
|
23
|
+
/** Model preset the mission's runs should use (optional). */
|
|
24
|
+
presetId?: string;
|
|
25
|
+
/** Durable deliver run most recently attached to this mission. */
|
|
26
|
+
latestRunId?: string;
|
|
27
|
+
/** Total launches (fresh + resume). Drives the relaunch backoff. */
|
|
28
|
+
launchCount: number;
|
|
29
|
+
/** Resume launches after an interruption. Drives the relaunch backoff. */
|
|
30
|
+
relaunchCount: number;
|
|
31
|
+
/** ISO time of the last launch — the backoff's anchor. */
|
|
32
|
+
lastLaunchAt?: string;
|
|
33
|
+
/** pid of the launch we spawned — the provenance anchor for run linkage. */
|
|
34
|
+
launchPid?: number;
|
|
35
|
+
createdAt: string;
|
|
36
|
+
updatedAt: string;
|
|
37
|
+
}
|
|
38
|
+
export interface MissionAttempt {
|
|
39
|
+
id: number;
|
|
40
|
+
missionId: number;
|
|
41
|
+
/** Deliver run id this attempt executed (absent for a whole-mission note). */
|
|
42
|
+
runId?: string;
|
|
43
|
+
/** Epic/phase/task ref the attempt targeted (absent = the whole run). */
|
|
44
|
+
taskRef?: string;
|
|
45
|
+
outcome: MissionAttemptOutcome;
|
|
46
|
+
/** One-line harness-owned summary (what happened / what remains). */
|
|
47
|
+
summary?: string;
|
|
48
|
+
/**
|
|
49
|
+
* What this observation saw (run.updatedAt / exit.at). Dedupes identical
|
|
50
|
+
* RE-observations while letting genuinely new failures of the same run
|
|
51
|
+
* accumulate into the struggle metric.
|
|
52
|
+
*/
|
|
53
|
+
observedAt?: string;
|
|
54
|
+
createdAt: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Choir-style salvage: a failed attempt's residue — what it learned, what
|
|
58
|
+
* partial work exists — seeded as a follow-up. Proposals are APPROVAL-GATED:
|
|
59
|
+
* nothing is scheduled until a human or the overseer approves.
|
|
60
|
+
*/
|
|
61
|
+
export interface SalvageProposal {
|
|
62
|
+
id: number;
|
|
63
|
+
missionId: number;
|
|
64
|
+
fromAttemptId?: number;
|
|
65
|
+
taskRef?: string;
|
|
66
|
+
proposal: string;
|
|
67
|
+
status: SalvageStatus;
|
|
68
|
+
createdAt: string;
|
|
69
|
+
decidedAt?: string;
|
|
70
|
+
}
|
|
71
|
+
/** An explicit supersede of a mission's frozen acceptance criteria. */
|
|
72
|
+
export interface AcceptanceRevision {
|
|
73
|
+
id: number;
|
|
74
|
+
missionId: number;
|
|
75
|
+
priorAcceptance: string;
|
|
76
|
+
priorHash: string;
|
|
77
|
+
reason: string;
|
|
78
|
+
createdAt: string;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=mission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mission.d.ts","sourceRoot":"","sources":["../../src/types/mission.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAC;AAEzE,yEAAyE;AACzE,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,aAAa,CAAC;AAExF,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;AAEjE,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,aAAa,CAAC;IACtB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,WAAW,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,aAAa,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,qBAAqB,CAAC;IAC/B,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,uEAAuE;AACvE,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mission ledger types — the durable goal layer above deliver runs.
|
|
3
|
+
*
|
|
4
|
+
* A mission is a user-level goal with FROZEN acceptance criteria (the
|
|
5
|
+
* statement-immutability analog): the hash is taken at creation and every
|
|
6
|
+
* later change is an explicit, recorded replan. Missions link to durable
|
|
7
|
+
* deliver runs and survive reboots, crashes, and session boundaries — the
|
|
8
|
+
* orchestrator daemon polls this ledger to keep incomplete missions moving
|
|
9
|
+
* until their acceptance criteria are verified met.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=mission.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mission.js","sourceRoot":"","sources":["../../src/types/mission.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# ADR 0003: The mission orchestrator — a durable goal layer above deliver runs
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2026-10-03 (branch `feature/331-mission-orchestrator`, Choir-uplift PR 1 of 2)
|
|
5
|
+
- Guide: `docs/guides/MISSIONS.md` · Visual: `docs/visual/mission-orchestrator.html`
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
`uap deliver` runs converge against real gates and survive their tool call
|
|
10
|
+
(detached missions), but nothing owns the **goal** across process lifetimes:
|
|
11
|
+
a reboot, a crashed service, or a run that quietly died leaves the work
|
|
12
|
+
incomplete with nothing to resume it. The Choir protocol (Weber-GeoML,
|
|
13
|
+
Apache-2.0; arXiv 2609.31903) contributes the key insight: the loop clock and
|
|
14
|
+
the state must be durable and external to any agent process.
|
|
15
|
+
|
|
16
|
+
UAP already had every inner mechanism (durable run state, resume cursors, a
|
|
17
|
+
watch-don't-drive supervisor, convergence loops, an in-deliver blackboard
|
|
18
|
+
orchestrator, epic controller). What was missing was the outer loop and its
|
|
19
|
+
durable state: a mission ledger plus a poll daemon that keeps incomplete
|
|
20
|
+
missions moving until their frozen acceptance criteria are met.
|
|
21
|
+
|
|
22
|
+
## Decision
|
|
23
|
+
|
|
24
|
+
1. **A mission ledger, separate from the coordination DB.** Missions are
|
|
25
|
+
operator-owned durable goals (months), coordination state is per-session
|
|
26
|
+
agent registry (minutes). Different lifecycles, different owners; merging
|
|
27
|
+
them would couple a long-lived artifact to session hygiene. Stored at
|
|
28
|
+
`.uap/missions.db` (SQLite, WAL, `PRAGMA user_version = 1`, `foreign_keys ON`).
|
|
29
|
+
2. **Frozen acceptance criteria (statement immutability).** sha256 of the
|
|
30
|
+
trimmed acceptance text at creation; every change is an explicit
|
|
31
|
+
`uap mission replan` that records the prior text, hash, and reason in
|
|
32
|
+
`acceptance_revisions`. A replan RESETS the run link and backoff counters:
|
|
33
|
+
the prior run embeds the old criteria and must not deliver against
|
|
34
|
+
superseded acceptance. The acceptance travels inside every run
|
|
35
|
+
instruction with `--acceptance`, so the behavioral judge grades the frozen
|
|
36
|
+
text. PR 2's merge gate will verify this same hash before any merge.
|
|
37
|
+
3. **The run marker embeds the acceptance hash.** `[mission:#N:<hash8>]` in
|
|
38
|
+
the instruction links a deliver run back to its mission. Because the
|
|
39
|
+
hash slice is replan-scoped, an old-criteria run can never be relinked
|
|
40
|
+
after a replan, and goal text mentioning another mission's marker cannot
|
|
41
|
+
collide. The marker prefix also pins the argv injection posture (the
|
|
42
|
+
positional token can never begin with `-`).
|
|
43
|
+
4. **Linkage is provenance-checked.** Run-state files are untrusted input
|
|
44
|
+
(the loader sanitizes format, not intent). A discovered run links only
|
|
45
|
+
when its instruction carries the mission's marker AND its pid matches the
|
|
46
|
+
pid the orchestrator itself spawned (recorded by `claimLaunch`/
|
|
47
|
+
`setLaunchPid`). A planted state file cannot know a future child pid, so
|
|
48
|
+
it can never steer the daemon into resuming it or closing the mission.
|
|
49
|
+
5. **A pure decision table + a seam-injected executor.** `decideMissionActions`
|
|
50
|
+
(`poll-cycle.ts`) is PURE and unit-tested; `runPollCycle`
|
|
51
|
+
(`poll-executor.ts`) applies its actions through injected seams
|
|
52
|
+
(supervisor, spawner, board), matching the project's house style
|
|
53
|
+
(`plan.ts`, `deliver.ts`). A cycle never throws — a bad state produces a
|
|
54
|
+
report note, not a dead daemon.
|
|
55
|
+
6. **Launches are claimed atomically.** `claimLaunch` is a single-statement
|
|
56
|
+
conditional UPDATE (60-second window), so the daemon poll and
|
|
57
|
+
`uap mission create --launch` cannot both spawn the same mission.
|
|
58
|
+
7. **Backoff: first relaunch immediate, then exponential (15·2^n, cap 1 day).**
|
|
59
|
+
A clean interruption is the healthy case; repeated failures back off.
|
|
60
|
+
Struggle (failed/interrupted attempts per mission) is deduped per
|
|
61
|
+
(mission, run, outcome, taskRef, observedAt) — identical re-observations
|
|
62
|
+
dedupe, genuinely new failures accumulate — and 3+ struggle turns salvage
|
|
63
|
+
proposals into escalation asks. Salvage is APPROVAL-GATED (the Choir
|
|
64
|
+
overseer split): the orchestrator proposes, the human rules
|
|
65
|
+
(`uap mission salvage approve|reject`).
|
|
66
|
+
8. **The daemon is systemd-owned.** `uap-orchestrator.service` runs
|
|
67
|
+
`uap mission poll --loop --interval 300` (hidden alias: `uap orch poll` —
|
|
68
|
+
deliberately distinct from `uap orchestrator on|off`, which toggles the
|
|
69
|
+
in-deliver blackboard orchestrator). Node and the CLI entrypoint are
|
|
70
|
+
captured absolute at install time; `uap doctor` probes the unit against
|
|
71
|
+
its declared RSS budget in `config/capacity-policy.json`.
|
|
72
|
+
|
|
73
|
+
## Consequences
|
|
74
|
+
|
|
75
|
+
- A mission survives reboots, crashes, session teardown, and context
|
|
76
|
+
exhaustion: the daemon re-decides every active mission every 300 s.
|
|
77
|
+
- The launch grace window (10 min) prevents double-launching before
|
|
78
|
+
`uap deliver` registers its run state; discovery then links the run.
|
|
79
|
+
- fd hygiene matters in daemon mode (the parent closes the spawn log fd;
|
|
80
|
+
the child holds a dup) and log files use 0600 like supervisor state.
|
|
81
|
+
- The attempt ledger is the struggle metric; whole-mission notes (no runId)
|
|
82
|
+
are never deduped so launch-loops escalate instead of retrying silently.
|
|
83
|
+
- PR 2 (merge gate) consumes exactly: `verifyAcceptanceHash`,
|
|
84
|
+
`hashAcceptance`, the revision history, and the mission↔run linkage — it
|
|
85
|
+
must never parse acceptance text out of the instruction string.
|
|
86
|
+
|
|
87
|
+
## Alternatives considered
|
|
88
|
+
|
|
89
|
+
- **Fold missions into the coordination DB** — rejected: lifecycle mismatch
|
|
90
|
+
(see Decision 1).
|
|
91
|
+
- **Let deliver own the goal loop** — rejected: deliver already owns
|
|
92
|
+
in-run orchestration (blackboard, epics); the goal layer must survive the
|
|
93
|
+
run process, so it lives above it.
|
|
94
|
+
- **Adopt Choir itself as the backend** — deferred: single-machine today;
|
|
95
|
+
the protocol's distributed-worker surface (GitHub issues, forks) is a
|
|
96
|
+
follow-up if distribution becomes a requirement.
|
|
97
|
+
- **Auto-execute approved escalations** — rejected: salvage and escalation
|
|
98
|
+
are approval-gated by user decision (2026-10-03); the daemon proposes,
|
|
99
|
+
never schedules.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# ADR 0004: The deterministic merge gate — evidence, not testimony
|
|
2
|
+
|
|
3
|
+
- Status: Accepted
|
|
4
|
+
- Date: 2026-10-03 (branch `feature/332-merge-gate`, Choir-uplift PR 2 of 2)
|
|
5
|
+
- Guide: `docs/guides/MISSIONS.md` (Merge gate section) · Visual: `docs/visual/merge-gate.html`
|
|
6
|
+
- Builds on: ADR-0003 (mission orchestrator; this gate is the landing half of the seam ADR-0003 documented)
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
ADR-0003's orchestrator keeps a mission moving until a deliver run reports DONE
|
|
11
|
+
against frozen acceptance. But a DONE report is **testimony**, not evidence:
|
|
12
|
+
a run can claim delivery with a stub still in the diff, with its final
|
|
13
|
+
iteration actually failing, under acceptance criteria a later replan has
|
|
14
|
+
superseded, or — worst case — in a diff that also rewrites the very gates that
|
|
15
|
+
would judge it. Choir's merge discipline contributes the closing insight: the
|
|
16
|
+
merge is the moment to recompute the verdict deterministically from recorded
|
|
17
|
+
evidence, before the work becomes irreversible.
|
|
18
|
+
|
|
19
|
+
The merge queue (`uap merge queue`) already serializes landings and re-syncs
|
|
20
|
+
impacted PRs, but its pre-merge check was only `gh pr checks` — CI greenness.
|
|
21
|
+
CI cannot see mission semantics at all.
|
|
22
|
+
|
|
23
|
+
## Decision
|
|
24
|
+
|
|
25
|
+
Four deterministic checks run between a mission-linked PR and master. All four
|
|
26
|
+
are recomputed from recorded evidence at gate time — no model calls (the
|
|
27
|
+
acceptance judge already ran inside deliver; the gate verifies its recorded
|
|
28
|
+
outcome, it does not re-judge), and no parsing of acceptance text out of
|
|
29
|
+
instruction strings (the ADR-0003 seam: `verifyAcceptanceHash`, the marker
|
|
30
|
+
hash, the attempt ledger).
|
|
31
|
+
|
|
32
|
+
1. **Provenance / statement immutability.** The PR's `[mission:#N:<hash8>]`
|
|
33
|
+
marker must match the mission's CURRENT frozen acceptance hash. A PR cut
|
|
34
|
+
before a replan was built to superseded criteria — it fails, even though
|
|
35
|
+
its CI is green. Independently, the ledger itself must be intact: the stored
|
|
36
|
+
acceptance text must still hash to the stored hash (`verifyAcceptanceHash`),
|
|
37
|
+
so a hand-edited `.uap/missions.db` cannot launder acceptance.
|
|
38
|
+
2. **Green recompute.** Tier 1: the gate-evidence artifact deliver records on
|
|
39
|
+
success (`.uap/evidence/<candidateSha>.json`, uplift 1.4) — every gate's
|
|
40
|
+
exit code bound to the exact HEAD it proved. Production delivered runs
|
|
41
|
+
persist WITHOUT a run checkpoint (deliver clears it on success), so the
|
|
42
|
+
artifact, not run-state history, is the seam that survives a real
|
|
43
|
+
delivery; a present-but-invalid artifact (red gate exit, environment
|
|
44
|
+
hatch, candidate-sha drift, malformed JSON) is a hard failure. Tier 2:
|
|
45
|
+
when no artifact was checked, run-state history must show a passing final
|
|
46
|
+
iteration (and 100% acceptance when the judge ran). Tier 3 is refused:
|
|
47
|
+
a bare `delivered` status with no artifact and no history is testimony,
|
|
48
|
+
not evidence. Status is read FRESH from disk, and when both pids are
|
|
49
|
+
known the run-state pid must match the pid the orchestrator spawned
|
|
50
|
+
(PR-1 provenance discipline). No salvage proposal may await a ruling —
|
|
51
|
+
landing past an open proposal would bypass the approval gate. Note: a
|
|
52
|
+
`history`-tier run whose `acceptanceMet` is undefined also covers the
|
|
53
|
+
case where the acceptance judge itself errored mid-deliver (the
|
|
54
|
+
convergence loop fails open and records no fraction) — closing that
|
|
55
|
+
ambiguity is PR-1 follow-up, tracked there.
|
|
56
|
+
3. **Sorry-delta.** The ADDED lines of the diff (never removed lines —
|
|
57
|
+
deleting a TODO is progress) are scanned for stub markers (`todo!()`,
|
|
58
|
+
`NotImplementedError`, TODO/FIXME, placeholder implementations) in
|
|
59
|
+
non-test, non-docs files (tests stub seams by design; docs quote the
|
|
60
|
+
markers they describe), and for apology/escape phrasing ("sorry",
|
|
61
|
+
"I couldn't", "unable to complete") in added lines and the run's
|
|
62
|
+
declared summaries. Lines longer than 8 KB are skipped — a bounded-gap
|
|
63
|
+
placeholder pattern was quadratic on adversarial multi-megabyte lines
|
|
64
|
+
(security review).
|
|
65
|
+
4. **Axiom-honesty.** The diff may not touch gate infrastructure — enforcer
|
|
66
|
+
hooks, CI workflows, policy enforcers, the quality baseline, the
|
|
67
|
+
version-bump script, capacity policy, the merge gate and queue
|
|
68
|
+
themselves, their evidence gatherer, and the mission ledger functions the
|
|
69
|
+
first two checks consume — without explicit acknowledgment. DELETED,
|
|
70
|
+
RENAMED, and BINARY gate-infra files are captured too (`git rm` on a
|
|
71
|
+
hook is the purest form of weakening it). The prefix list is hard-coded
|
|
72
|
+
in the gate's own source, not a config file: an externalized list could
|
|
73
|
+
be edited by the very PR it judges, while any edit to the list trips
|
|
74
|
+
axiom-honesty against the gate itself — the list protects itself by
|
|
75
|
+
being the gate. A PR may not weaken (or rewrite) the checks that judge
|
|
76
|
+
it. Acknowledgment is operator-visible: `--allow-gate-infra` on the CLI,
|
|
77
|
+
or a `gate-infra` PR label in the queue. There is deliberately NO
|
|
78
|
+
environment escape hatch (`UAP_*_OFF` is not consulted), and the queue's
|
|
79
|
+
`--force` flag skips stale CI checks but NOT this gate — a force flag is
|
|
80
|
+
exactly the gate-off escape this check exists to refuse.
|
|
81
|
+
|
|
82
|
+
The pure decision core is `evaluateMergeGate` (`src/delivery/merge-gate.ts`);
|
|
83
|
+
evidence gathering (`gatherGateEvidence`) and PR wiring (`gatePr`) live in
|
|
84
|
+
`src/cli/merge-gate.ts`. The queue gates only PRs carrying a mission marker;
|
|
85
|
+
unmarked PRs proceed as before (the gate is opt-in by provenance, not a tax
|
|
86
|
+
on every PR).
|
|
87
|
+
|
|
88
|
+
## Consequences
|
|
89
|
+
|
|
90
|
+
- `uap merge gate <missionId>` is the operator surface (also `--json` for CI).
|
|
91
|
+
- The queue skips (never force-merges) a PR whose gate fails, listing every
|
|
92
|
+
failed check so an operator sees all reasons at once — the core reports a
|
|
93
|
+
full finding list, not first-fail. A transient `gh pr diff` failure maps
|
|
94
|
+
to a per-PR skip (`gate: error`), never a crash of the whole batch; the
|
|
95
|
+
diff is fetched lazily, so marker-less PRs cost no extra `gh` calls.
|
|
96
|
+
- A delivered run's recorded history is trusted only in aggregate: status,
|
|
97
|
+
final-iteration pass, acceptance fraction. The gate never trusts summaries
|
|
98
|
+
as evidence of completion — summaries are scanned for apologies, which is
|
|
99
|
+
the opposite direction of trust.
|
|
100
|
+
- Honest limits, recorded so the messages are not oversold: `verifyAcceptanceHash`
|
|
101
|
+
detects only INCONSISTENT hand-edits (text edited without recomputing the
|
|
102
|
+
stored hash); an attacker who edits both passes — it is a consistency
|
|
103
|
+
check, not a signature. The PR marker is author-asserted: any collaborator
|
|
104
|
+
can paste a green mission's marker into an unrelated PR, though doing so
|
|
105
|
+
only ever ADDS checks (an omitted marker faces none), and the queue warns
|
|
106
|
+
loudly about unmarked agent-style PRs on worktree-pattern branches. The
|
|
107
|
+
residual trust bottom is the agent-writable `state.json`/evidence files
|
|
108
|
+
themselves; the pid corroboration and the ledger-side attempt history
|
|
109
|
+
narrow, but do not eliminate, that surface.
|
|
110
|
+
- A single marker gates only the mission it names; multi-mission PRs are
|
|
111
|
+
follow-up work (extract all markers, require all linked missions to pass).
|
|
112
|
+
|
|
113
|
+
## Alternatives considered
|
|
114
|
+
|
|
115
|
+
- **Re-run the acceptance judge at merge time** — rejected: a model call at
|
|
116
|
+
the gate makes the gate non-deterministic and re-bills the most expensive
|
|
117
|
+
step to guard against a claim that was already judged once. The recorded
|
|
118
|
+
fraction plus the stub/infra scans close the honest-failure modes
|
|
119
|
+
deterministically.
|
|
120
|
+
- **Gate every PR through mission semantics** — rejected: PRs without
|
|
121
|
+
missions (docs, chores) have no acceptance to verify; forcing a marker
|
|
122
|
+
would create the exact kind of ceremony agents learn to game. Provenance
|
|
123
|
+
opts a PR in.
|
|
124
|
+
- **Block gate-infra changes outright** — rejected: legitimate work sometimes
|
|
125
|
+
IS the gate system (this PR). The requirement is acknowledgment, not
|
|
126
|
+
prohibition — and the acknowledgment is recorded in the PR's labels where
|
|
127
|
+
a reviewer sees it.
|
|
@@ -32,6 +32,55 @@ the project, then `~/.config/uap/capacity-policy.json`.
|
|
|
32
32
|
}
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
## HTTP-probed services (no systemd unit)
|
|
36
|
+
|
|
37
|
+
Since 2026-10-04 a service may declare an **`http`** probe instead of
|
|
38
|
+
`systemd` — exactly one of the two, never both (which one would be the
|
|
39
|
+
truth?). This exists for the strata serve layer, the local Qwen3.8 backend:
|
|
40
|
+
launched by a plain script and reparented to `systemd --user`, it has no
|
|
41
|
+
unit to show, no `NRestarts`, no `MemoryCurrent` — before this, `uap doctor`
|
|
42
|
+
simply could not see the backend at all.
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"name": "strata-server",
|
|
47
|
+
"http": {
|
|
48
|
+
"url": "http://127.0.0.1:8080",
|
|
49
|
+
"kind": "strata",
|
|
50
|
+
"metricsMustMatch": { "kv": "int8", "max_context": 131072 }
|
|
51
|
+
},
|
|
52
|
+
"budget": { "note": "…which flags run-iq3_xxs.sh launches it with…" },
|
|
53
|
+
"headroom": { "gpuMinFreeMiB": 600 }
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- **Liveness** is "it answers `/metrics` as the declared kind". Not answering
|
|
58
|
+
at all is DARK (absent, not degraded); the probe tool failing (no curl,
|
|
59
|
+
timeout) is UNKNOWN — the same fail-open posture as a missing `systemctl`.
|
|
60
|
+
A server that answers but is *not* the declared document (a llama.cpp
|
|
61
|
+
Prometheus body behind the port, say) is DARK too: that is exactly the
|
|
62
|
+
wrong-engine misroute this check exists to catch.
|
|
63
|
+
- **`http.metricsMustMatch`** is `execStartMustContain` for a service with no
|
|
64
|
+
ExecStart: `engine.<key>` values that must equal the live document. A
|
|
65
|
+
missing key is RED (unverified, not quietly compliant), a mismatch is RED
|
|
66
|
+
(configuration drift). Put the load-bearing numbers from the `note` here —
|
|
67
|
+
kv kind and pool size — or they will drift the way `note` did.
|
|
68
|
+
- **`headroom`** works exactly as for unit services (one shared
|
|
69
|
+
nvidia-smi probe per run). There is no RSS budget or restartBudget: with
|
|
70
|
+
no unit there is no `MemoryCurrent` and no `NRestarts`; liveness IS the
|
|
71
|
+
probe. Declaring `budget.rssMiB` or `restartBudget` on an http service is
|
|
72
|
+
now a **parse error**, not a silently-ignored field — a budget that cannot
|
|
73
|
+
be verified is the anti-pattern this policy exists to prevent.
|
|
74
|
+
- **URL validation is strict** — http(s) base URL only, no userinfo, query,
|
|
75
|
+
fragment, or path — because the probe passes it to curl as a positional
|
|
76
|
+
argument. That is the same first-wall doctrine as the unit-name charset.
|
|
77
|
+
The URL must also be **canonical**: a raw spelling that only *looks*
|
|
78
|
+
parseable (percent-encoded dot-segments, `/a/..`, a bare `?`, leading
|
|
79
|
+
whitespace) is rejected, because curl must fetch exactly the form that
|
|
80
|
+
was validated.
|
|
81
|
+
|
|
82
|
+
## Fields
|
|
83
|
+
|
|
35
84
|
- **budget** — what the service is configured to stay inside (free-text
|
|
36
85
|
`note` records the provenance: which incident, which flag).
|
|
37
86
|
- `budget.vramMiB` declares the service's *own* allocator limit (e.g.
|
|
@@ -16,13 +16,55 @@ uap inference health --json # for monitors and dashboards
|
|
|
16
16
|
uap inference health --strict # exit 1 on WARN or RED
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
## Two backends, auto-detected
|
|
20
|
+
|
|
21
|
+
Since 2026-10-04 the local Qwen3.8 backend is the **strata serve layer**
|
|
22
|
+
(one rail, a 131072-cell int8 pool, MTP speculative decoding, no systemd
|
|
23
|
+
unit). The command fetches `/metrics` once and classifies the backend: a
|
|
24
|
+
JSON document with an `engine` block is strata; Prometheus text with
|
|
25
|
+
`llamacpp:` counters is a llama.cpp server. `--backend llamacpp|strata`
|
|
26
|
+
forces the choice.
|
|
27
|
+
|
|
28
|
+
The strata path reads two sources:
|
|
29
|
+
|
|
30
|
+
- **`/metrics` (JSON)** — the `engine` block gives pool size, KV kind and
|
|
31
|
+
reserve-aware free VRAM; `live` gives the in-flight request; `requests` is
|
|
32
|
+
a bounded ring of completed requests (observed: 12) with real timestamps.
|
|
33
|
+
- **the serve log** (`--strata-log` or `$UAP_STRATA_LOG`, e.g.
|
|
34
|
+
`/home/cogtek/dev/strata/strata-iq3_xxs.log`) — one `strata serve: prompt
|
|
35
|
+
…` line per completed request, for the process's whole life. These lines
|
|
36
|
+
carry **no timestamps**, so samples use a monotonic line-index clock: good
|
|
37
|
+
enough for the early/recent trend split (the log is one process's life),
|
|
38
|
+
never windowable by `--since`/`--until`. A replay therefore uses the
|
|
39
|
+
timestamped ring only, **filtered to the requested window** — an unfiltered
|
|
40
|
+
ring would analyse the current process's last few requests under the
|
|
41
|
+
REPLAY banner — and honestly reports thin samples.
|
|
42
|
+
|
|
43
|
+
Strata adds checks the llama.cpp path cannot have (its journal never logs
|
|
44
|
+
decode rate):
|
|
45
|
+
|
|
46
|
+
| Check | Code | Why it exists |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| Decode throughput decay | `decode-decay` | Decode speed depends on prompt DEPTH, so samples bucket by depth — the same compare-like-with-like rule prefill uses. |
|
|
49
|
+
| Prefix-cache failure | `cache-miss` | Strata reports reuse as a fact (`R reused`); under half the prompt served from cache means the conversation cache is not tracking. One honest 0% turn right after a client compaction does not fire it — the mean over the window must be low. |
|
|
50
|
+
| VRAM headroom | `vram-headroom` | The backend's own reserve-aware free-VRAM reading, catching another process eating the card from under it. |
|
|
51
|
+
|
|
52
|
+
The 2026-10-04 bug worth remembering: an *inactive* systemd unit still
|
|
53
|
+
reports its `ExecStart` to `systemctl`, and the old probe trusted it — the
|
|
54
|
+
dead gsq-rco unit's `-c 229376` printed as the live pool while strata served
|
|
55
|
+
131072. Unit flags now count only when the unit is ACTIVE; `/slots` is the
|
|
56
|
+
live geometry otherwise.
|
|
57
|
+
|
|
19
58
|
## What it looks at
|
|
20
59
|
|
|
21
60
|
| Check | Code | Why it exists |
|
|
22
61
|
| --- | --- | --- |
|
|
23
62
|
| Prefill throughput decay | `prefill-decay` | A long-lived process can get slower without failing. Only visible when samples are bucketed by prompt size. |
|
|
24
|
-
| Checkpoint starvation | `checkpoint-starved` | `--ctx-checkpoints` is **per slot**. Too few, and the checkpoint cannot track a growing conversation, so the same prefix is re-prefilled every turn. |
|
|
25
|
-
| KV pinned at the VBR floor | `kv-at-floor` | The pool is saturated and the cache has silently dropped to its lowest quality tier. |
|
|
63
|
+
| Checkpoint starvation | `checkpoint-starved` | `--ctx-checkpoints` is **per slot**. Too few, and the checkpoint cannot track a growing conversation, so the same prefix is re-prefilled every turn. (llama.cpp only — strata has no checkpoint allowance.) |
|
|
64
|
+
| KV pinned at the VBR floor | `kv-at-floor` | The pool is saturated and the cache has silently dropped to its lowest quality tier. (llama.cpp VBR only — strata's int8 KV is a fixed tier, no floor to pin at.) |
|
|
65
|
+
| Decode throughput decay | `decode-decay` | Strata: same decay question for decode, bucketed by depth. |
|
|
66
|
+
| Prefix-cache failure | `cache-miss` | Strata: the conversation cache re-reading most of the prompt. |
|
|
67
|
+
| VRAM headroom | `vram-headroom` | Strata: the backend's own free-VRAM reading under the floor. |
|
|
26
68
|
| Client-visible timeouts | `generation-timeouts` | The symptom users actually feel, counted from the proxy journal. |
|
|
27
69
|
| Window spans a restart | `spans-restart` | A trend across a restart compares two process lifetimes, not decay within one. |
|
|
28
70
|
| Rail/pool geometry | `shared-pool` | Informational (never a fault). Under `--kv-unified` the pool is *shared*, so N rails each believing they own it is an overcommit waiting to happen. |
|
|
@@ -32,12 +74,18 @@ A fabricated GREEN is precisely what let the original incident run all day, so
|
|
|
32
74
|
every probe fails open and any that could not run is listed explicitly as
|
|
33
75
|
`unverified (probe unavailable)`.
|
|
34
76
|
|
|
35
|
-
`
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
77
|
+
`GREEN` means the sampled evidence is **present and nothing crossed a
|
|
78
|
+
threshold**. An early draft required a finding to exist before the rollup
|
|
79
|
+
could say GREEN — and since only multi-rail llama.cpp emits an info finding
|
|
80
|
+
(`shared-pool`), a healthy single-rail or strata stack read UNKNOWN,
|
|
81
|
+
indistinguishable from a blind probe for monitors parsing `--json`.
|
|
82
|
+
|
|
83
|
+
`UNKNOWN` requires *all* sampled signals to be missing — no usable throughput
|
|
84
|
+
trend, no checkpoint or reuse samples, no KV or VRAM reading. Losing one of
|
|
85
|
+
them does not suppress the rest. An **actionable finding always wins**:
|
|
86
|
+
timeouts are counted from the proxy journal and can fire with none of the
|
|
87
|
+
sampled signals present, and a RED finding must never be masked by an
|
|
88
|
+
`UNKNOWN` rollup — which would let it pass `--strict` with exit 0.
|
|
41
89
|
|
|
42
90
|
## Why prompt-size bucketing matters
|
|
43
91
|
|
|
@@ -99,6 +147,15 @@ one in the window. An early version did attribute the current
|
|
|
99
147
|
`--ctx-checkpoints 4` to a window in which it had been `1`, and recommended
|
|
100
148
|
the wrong remedy as a result.
|
|
101
149
|
|
|
150
|
+
Strata replays have one extra rule: the ring is the only timestamped source,
|
|
151
|
+
so it is **filtered to the window** — an early version analysed the current
|
|
152
|
+
process's last dozen requests under the REPLAY banner. Draft records carry no
|
|
153
|
+
timestamp, so a strata replay shows no acceptance rate, and an unparseable
|
|
154
|
+
window expression is disclosed in `unverified (probe unavailable)` rather
|
|
155
|
+
than silently widening the window. A live (non-replay) run against the serve
|
|
156
|
+
log cannot apply `--since` (the log has no timestamps) and says so in the
|
|
157
|
+
same list.
|
|
158
|
+
|
|
102
159
|
## Reading the output
|
|
103
160
|
|
|
104
161
|
```
|