dos-kernel 0.22.0__py3-none-win_amd64.whl
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.
- dos/__init__.py +261 -0
- dos/_bin/dos-hook.exe +0 -0
- dos/_filelock.py +255 -0
- dos/_job_policy.py +97 -0
- dos/_tree.py +145 -0
- dos/admission.py +433 -0
- dos/answer_shape.py +299 -0
- dos/arbiter.py +859 -0
- dos/archive_lock.py +266 -0
- dos/arg_provenance.py +814 -0
- dos/attest.py +472 -0
- dos/breaker.py +311 -0
- dos/churn.py +226 -0
- dos/claim_extract.py +229 -0
- dos/claim_ttl.py +150 -0
- dos/cli.py +8721 -0
- dos/commit_audit.py +666 -0
- dos/completion.py +466 -0
- dos/concurrency_class.py +154 -0
- dos/config.py +1380 -0
- dos/config_lint.py +464 -0
- dos/cooldown.py +390 -0
- dos/coverage.py +387 -0
- dos/dangling_intent.py +287 -0
- dos/data_class.py +397 -0
- dos/decisions.py +1274 -0
- dos/decisions_tui.py +251 -0
- dos/dispatch_top.py +740 -0
- dos/dispatch_top_tui.py +116 -0
- dos/drivers/__init__.py +40 -0
- dos/drivers/ci_status.py +630 -0
- dos/drivers/citation_resolve.py +703 -0
- dos/drivers/decision_stop.py +98 -0
- dos/drivers/export_file.py +173 -0
- dos/drivers/export_otlp.py +275 -0
- dos/drivers/export_statsd.py +242 -0
- dos/drivers/hook_dialects.py +391 -0
- dos/drivers/job.py +47 -0
- dos/drivers/llm_judge.py +360 -0
- dos/drivers/memory_recall.py +1231 -0
- dos/drivers/notify_slack.py +373 -0
- dos/drivers/notify_webhook.py +251 -0
- dos/drivers/operator_judge.py +114 -0
- dos/drivers/os_acceptance.py +228 -0
- dos/drivers/paste_log.py +132 -0
- dos/drivers/plan_scope.py +133 -0
- dos/drivers/self_improve.py +375 -0
- dos/drivers/similarity_judge.py +249 -0
- dos/drivers/state_diff.py +274 -0
- dos/drivers/supervisor.py +347 -0
- dos/drivers/watchdog.py +363 -0
- dos/drivers/workshop.py +160 -0
- dos/durable_schema.py +344 -0
- dos/effect_witness.py +393 -0
- dos/efficiency.py +318 -0
- dos/enforce.py +414 -0
- dos/enumerate.py +776 -0
- dos/env_print.py +378 -0
- dos/event_severity.py +258 -0
- dos/evidence.py +692 -0
- dos/exec_capability.py +256 -0
- dos/export_cursor.py +143 -0
- dos/exporter.py +320 -0
- dos/firing_label.py +353 -0
- dos/fleet_roll.py +226 -0
- dos/gate_classify.py +827 -0
- dos/gh4_coverage.py +179 -0
- dos/git_delta.py +122 -0
- dos/guard.py +215 -0
- dos/health.py +552 -0
- dos/help_summary.py +519 -0
- dos/home.py +934 -0
- dos/hook_binary.py +194 -0
- dos/hook_dialect.py +271 -0
- dos/hook_exit.py +191 -0
- dos/hook_install.py +437 -0
- dos/id_alloc.py +304 -0
- dos/improve.py +499 -0
- dos/intent_ledger.py +635 -0
- dos/interpret.py +176 -0
- dos/intervention.py +769 -0
- dos/intervention_eval.py +371 -0
- dos/journal_delta.py +308 -0
- dos/judge_eval.py +328 -0
- dos/judges.py +366 -0
- dos/lane_infer.py +127 -0
- dos/lane_journal.py +1001 -0
- dos/lane_lease.py +952 -0
- dos/lane_overlap.py +228 -0
- dos/lease_health.py +282 -0
- dos/lifecycle.py +211 -0
- dos/liveness.py +352 -0
- dos/lock_modes.py +185 -0
- dos/log_source.py +395 -0
- dos/loop_decide.py +1746 -0
- dos/marker_gate.py +254 -0
- dos/marker_sensor.py +396 -0
- dos/noop_streak.py +280 -0
- dos/notify.py +479 -0
- dos/observe.py +175 -0
- dos/oracle.py +1661 -0
- dos/overlap_eval.py +214 -0
- dos/overlap_policy.py +342 -0
- dos/packet_sidecar.py +267 -0
- dos/phase_shipped.py +1985 -0
- dos/pick_priority.py +225 -0
- dos/pickable.py +369 -0
- dos/picker_oracle.py +1037 -0
- dos/plan_board.py +513 -0
- dos/plan_board_tui.py +113 -0
- dos/plan_source.py +455 -0
- dos/posttool_sensor.py +528 -0
- dos/precursor_gate.py +499 -0
- dos/precursor_gate_eval.py +239 -0
- dos/preflight.py +825 -0
- dos/pretool_sensor.py +490 -0
- dos/proc_delta.py +181 -0
- dos/productivity.py +296 -0
- dos/provider_limit.py +242 -0
- dos/py.typed +4 -0
- dos/reason_morphology.py +299 -0
- dos/reasons.py +449 -0
- dos/reconcile.py +173 -0
- dos/recurring_wedge.py +206 -0
- dos/render.py +393 -0
- dos/result_state.py +468 -0
- dos/resume.py +578 -0
- dos/resume_evidence.py +293 -0
- dos/retention.py +344 -0
- dos/reward.py +372 -0
- dos/rewind.py +587 -0
- dos/rewind_evidence.py +168 -0
- dos/rewind_tokens.py +252 -0
- dos/run_id.py +342 -0
- dos/scope.py +520 -0
- dos/scope_source.py +382 -0
- dos/scout.py +982 -0
- dos/self_modify.py +209 -0
- dos/sibling_scan.py +569 -0
- dos/skills/EXAMPLES.md +584 -0
- dos/skills/dos-class-cycle/SKILL.md +107 -0
- dos/skills/dos-dispatch/SKILL.md +177 -0
- dos/skills/dos-dispatch-loop/SKILL.md +254 -0
- dos/skills/dos-goal-gate/SKILL.md +269 -0
- dos/skills/dos-next-up/SKILL.md +231 -0
- dos/skills/dos-promote/SKILL.md +114 -0
- dos/skills/dos-replan/SKILL.md +159 -0
- dos/skills/dos-replan-loop/SKILL.md +114 -0
- dos/skills/dos-self-improve/SKILL.md +213 -0
- dos/skills/dos-supervise-loop/SKILL.md +180 -0
- dos/skills/dos-unstick/SKILL.md +108 -0
- dos/skills/dos-witness-claim/SKILL.md +251 -0
- dos/stamp.py +1002 -0
- dos/state_health.py +387 -0
- dos/status.py +114 -0
- dos/stop_policy.py +334 -0
- dos/supervise.py +1014 -0
- dos/testwitness.py +392 -0
- dos/timeline.py +1027 -0
- dos/tokens.py +485 -0
- dos/tool_stream.py +393 -0
- dos/tool_stream_eval.py +226 -0
- dos/trace.py +524 -0
- dos/verdict.py +140 -0
- dos/verdict_cli.py +189 -0
- dos/verdict_journal.py +497 -0
- dos/verdict_rollup.py +217 -0
- dos/verdicts.py +181 -0
- dos/wedge_reason.py +282 -0
- dos_kernel-0.22.0.dist-info/METADATA +859 -0
- dos_kernel-0.22.0.dist-info/RECORD +178 -0
- dos_kernel-0.22.0.dist-info/WHEEL +5 -0
- dos_kernel-0.22.0.dist-info/entry_points.txt +39 -0
- dos_kernel-0.22.0.dist-info/licenses/LICENSE +21 -0
- dos_kernel-0.22.0.dist-info/top_level.txt +2 -0
- dos_mcp/__init__.py +52 -0
- dos_mcp/py.typed +2 -0
- dos_mcp/server.py +779 -0
dos/stop_policy.py
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"""The pluggable loop-STOP policy seam — a host's "should a pending decision halt the loop?" call.
|
|
2
|
+
|
|
3
|
+
The dispatch scout (`dos.scout.choose`) has exactly ONE hardcoded STOP: rule 0,
|
|
4
|
+
`resource_blocked` — a measured can't-launch wall. Every other signal (an open
|
|
5
|
+
operator decision, a noisy scoreboard, a saturated `/unstick`) is **evidence
|
|
6
|
+
only, never a STOP** (the 2026-06-03 operator directive). That default is right
|
|
7
|
+
for the reference host, but it is a *policy*, and a different host may legitimately
|
|
8
|
+
want a different one: a `LIVENESS`/`OP_HALT` decision (a run hung RIGHT NOW,
|
|
9
|
+
burning budget) *should* stop its loop, while a `WEDGE`/soak decision should only
|
|
10
|
+
surface. Freezing that choice in the kernel is the wrong layer.
|
|
11
|
+
|
|
12
|
+
So the STOP-vs-surface call is a **pluggable seam**, exactly like `dos.judges`
|
|
13
|
+
(the adjudicator rung) and `dos.overlap_policies` (the disjointness scorer):
|
|
14
|
+
|
|
15
|
+
* `StopPolicy` — the Protocol a host/driver implements (`name` + `decide`).
|
|
16
|
+
* `StopVerdict` — a three-valued advisory ruling (STOP / DEFER / NEVER). DEFER
|
|
17
|
+
= "no opinion, use the kernel default"; NEVER = "explicitly do not stop";
|
|
18
|
+
STOP = "halt the loop." It mutates nothing — acting on it is the scout's job.
|
|
19
|
+
* `NeverStopPolicy` — the built-in, unshadowable baseline: it DEFERs on
|
|
20
|
+
everything, so with no policy wired the scout's behavior is byte-identical to
|
|
21
|
+
today (evidence-only). The honest zero of the seam, like `AbstainJudge`.
|
|
22
|
+
* `run_stop_policy` — the **fail-to-DEFER** wrapper: any raise / non-`StopVerdict`
|
|
23
|
+
return becomes DEFER, never STOP. A buggy or hostile host policy can therefore
|
|
24
|
+
never *manufacture* a halt by failing. The STOP analogue of `run_judge`'s
|
|
25
|
+
fail-to-abstain (the safe direction for a halt-gate is "don't halt").
|
|
26
|
+
* `stop_under_resource_floor` — the **AND-floor** guarantee: the kernel's
|
|
27
|
+
`resource_blocked` STOP is the unforgeable floor. A policy is consulted ONLY
|
|
28
|
+
when the floor does not already STOP, and a policy can only *add* a STOP on top
|
|
29
|
+
of the floor — it can never suppress the `resource_blocked` halt. So a
|
|
30
|
+
swappable STOP policy can only ever halt MORE than the kernel, never fewer
|
|
31
|
+
(the dangerous direction — a hostile policy keeping a doomed loop alive past a
|
|
32
|
+
measured resource wall — is structurally unreachable). Mirrors
|
|
33
|
+
`overlap_policy.admissible_under_floor`.
|
|
34
|
+
|
|
35
|
+
Pure kernel seam (mechanism). The Protocol + verdict + wrappers + resolver live
|
|
36
|
+
here; every *ruling* policy with host/provider surface (one that reads the live
|
|
37
|
+
decision queue, calls a model, shells out) lives in a **driver**
|
|
38
|
+
(`dos.drivers.…`), discovered BY NAME at the call boundary — the kernel imports
|
|
39
|
+
no policy implementation (the `dos.drivers` litmus in CLAUDE.md). The scout takes
|
|
40
|
+
the resolved policy as an OPTIONAL input field and never resolves one itself, so
|
|
41
|
+
its pure default path is untouched.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
from __future__ import annotations
|
|
45
|
+
|
|
46
|
+
import enum
|
|
47
|
+
import sys
|
|
48
|
+
from dataclasses import dataclass, field
|
|
49
|
+
from typing import Protocol, runtime_checkable
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
# The three-valued ruling.
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class StopStance(str, enum.Enum):
|
|
58
|
+
"""A STOP policy's three-valued ruling on whether the loop should halt.
|
|
59
|
+
|
|
60
|
+
Three-valued by design (like `judges.Stance`): a binary stop/go would force a
|
|
61
|
+
policy to take a side even when it has no opinion, and "no opinion" is exactly
|
|
62
|
+
the common case (most iterations, the policy has nothing to say and the kernel
|
|
63
|
+
default should apply). `DEFER` is that honest third answer.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
STOP = "STOP" # halt the loop now
|
|
67
|
+
DEFER = "DEFER" # no opinion — fall through to the kernel default (today: don't stop)
|
|
68
|
+
NEVER = "NEVER" # explicitly do NOT stop (a policy actively vetoing a halt-on-this)
|
|
69
|
+
|
|
70
|
+
def __str__(self) -> str: # pragma: no cover - trivial
|
|
71
|
+
return self.value
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(frozen=True)
|
|
75
|
+
class StopVerdict:
|
|
76
|
+
"""A STOP policy's frozen, advisory ruling. Three constructors, no other build path.
|
|
77
|
+
|
|
78
|
+
Carries nothing that could mutate state (advisory-only by shape, like
|
|
79
|
+
`JudgeVerdict`): the scout READS `should_stop` and decides; the policy never
|
|
80
|
+
acts. `cause_key` is the closed token the scout stamps on a resulting STOP
|
|
81
|
+
decision (so a test/operator sees which policy halted); `reason` is the
|
|
82
|
+
one-line operator-facing prose.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
_stance: StopStance
|
|
86
|
+
reason: str = ""
|
|
87
|
+
cause_key: str = "stop_policy"
|
|
88
|
+
evidence: tuple[str, ...] = field(default_factory=tuple)
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def stance(self) -> StopStance:
|
|
92
|
+
return self._stance
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def should_stop(self) -> bool:
|
|
96
|
+
"""True iff this verdict halts the loop. DEFER and NEVER are both go."""
|
|
97
|
+
return self._stance is StopStance.STOP
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def deferred(self) -> bool:
|
|
101
|
+
"""True iff the policy had no opinion (use the kernel default)."""
|
|
102
|
+
return self._stance is StopStance.DEFER
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def stop(cls, reason: str = "", *, cause_key: str = "stop_policy",
|
|
106
|
+
evidence: tuple[str, ...] = ()) -> "StopVerdict":
|
|
107
|
+
"""Halt the loop. The one verdict a policy can NEVER reach by failing
|
|
108
|
+
(see `run_stop_policy` — a failure degrades to DEFER, never STOP)."""
|
|
109
|
+
return cls(_stance=StopStance.STOP, reason=reason, cause_key=cause_key,
|
|
110
|
+
evidence=tuple(evidence))
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def defer(cls, reason: str = "", *, evidence: tuple[str, ...] = ()) -> "StopVerdict":
|
|
114
|
+
"""No opinion — use the kernel default. The conservative value every
|
|
115
|
+
failure (raise / bad return) degrades to."""
|
|
116
|
+
return cls(_stance=StopStance.DEFER, reason=reason, evidence=tuple(evidence))
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def never(cls, reason: str = "", *, evidence: tuple[str, ...] = ()) -> "StopVerdict":
|
|
120
|
+
"""Explicitly do not stop on this (a policy vetoing a halt). Distinct from
|
|
121
|
+
DEFER only in intent; both are 'go'. Cannot override the resource floor —
|
|
122
|
+
the floor is checked BEFORE the policy is consulted."""
|
|
123
|
+
return cls(_stance=StopStance.NEVER, reason=reason, evidence=tuple(evidence))
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
# The policy contract + the built-in baseline.
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@runtime_checkable
|
|
132
|
+
class StopPolicy(Protocol):
|
|
133
|
+
"""The contract a host/driver implements to decide loop-STOP from scout state.
|
|
134
|
+
|
|
135
|
+
``name`` is the token `dos.toml`/`dos doctor` selects/lists. ``decide`` is
|
|
136
|
+
handed the scout `state` (an opaque object — the policy reads the attributes it
|
|
137
|
+
needs, e.g. `open_escalated_decisions`, off it; the type gives it nothing to
|
|
138
|
+
mutate) and the active `config`, and returns a `StopVerdict`.
|
|
139
|
+
|
|
140
|
+
A policy MAY do I/O *inside* ``decide`` (read the live `dos.decisions` queue,
|
|
141
|
+
call a model, shell out) IFF it lives in a driver — the same allowance a ruling
|
|
142
|
+
judge has, and the same reason it lives outside the kernel. The disciplines
|
|
143
|
+
that keep it honest are NOT purity: they are fail-to-DEFER (`run_stop_policy`)
|
|
144
|
+
and the resource floor (`stop_under_resource_floor`) — a failing policy can't
|
|
145
|
+
halt, and no policy can suppress the measured `resource_blocked` STOP.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
name: str
|
|
149
|
+
|
|
150
|
+
def decide(self, state: object, config: object) -> StopVerdict:
|
|
151
|
+
...
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class NeverStopPolicy:
|
|
155
|
+
"""The built-in, always-available policy: it DEFERs on everything.
|
|
156
|
+
|
|
157
|
+
The STOP analogue of `AbstainJudge` / `PrefixOverlapPolicy` — a trusted
|
|
158
|
+
baseline a plugin can never shadow (`resolve_stop_policy` resolves built-ins
|
|
159
|
+
first). It is the honest zero of the seam: a workspace with NO policy wired
|
|
160
|
+
still has a resolvable one, and it never halts the loop, so the scout's
|
|
161
|
+
behavior is **byte-identical to before the seam** (the load-bearing default).
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
name = "never"
|
|
165
|
+
|
|
166
|
+
def decide(self, state: object, config: object) -> StopVerdict:
|
|
167
|
+
return StopVerdict.defer(
|
|
168
|
+
"no STOP policy wired — the built-in policy defers, so an open "
|
|
169
|
+
"decision is evidence-only (the kernel default)."
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# ---------------------------------------------------------------------------
|
|
174
|
+
# The fail-to-DEFER wrapper + the resource-floor AND.
|
|
175
|
+
# ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def run_stop_policy(policy: StopPolicy, state: object, config: object) -> StopVerdict:
|
|
179
|
+
"""Run one STOP policy, enforcing **fail-to-DEFER**.
|
|
180
|
+
|
|
181
|
+
The wrapper EVERY consumer calls instead of `policy.decide(...)` directly — it
|
|
182
|
+
is what makes "a policy can never manufacture a halt by failing" structural:
|
|
183
|
+
|
|
184
|
+
* a policy that **raises** (a bug, a model timeout, a torn queue read) →
|
|
185
|
+
`DEFER`, naming the failure. Never propagates; never STOP.
|
|
186
|
+
* a policy that returns **anything that is not a `StopVerdict`** (None, a
|
|
187
|
+
dict, a duck-typed look-alike) → `DEFER`. We never read a foreign object's
|
|
188
|
+
`should_stop`, so no halt sneaks through a wrong return type.
|
|
189
|
+
|
|
190
|
+
The deliberate asymmetry with `admission.run_predicates` (which fails to
|
|
191
|
+
*refuse*) and the symmetry with `judges.run_judge` (which fails to *abstain*):
|
|
192
|
+
each role's safe failure is the one that takes NO consequential action. A
|
|
193
|
+
STOP policy gates a halt, so its safe failure is "don't halt — defer."
|
|
194
|
+
"""
|
|
195
|
+
name = getattr(policy, "name", type(policy).__name__)
|
|
196
|
+
try:
|
|
197
|
+
verdict = policy.decide(state, config)
|
|
198
|
+
except Exception as e: # fail-to-DEFER: a policy that raises cannot halt
|
|
199
|
+
return StopVerdict.defer(
|
|
200
|
+
f"stop policy {name!r} raised ({e!r}) — deferring (a halt-gate that "
|
|
201
|
+
f"cannot answer never halts; it falls through to the kernel default)."
|
|
202
|
+
)
|
|
203
|
+
if not isinstance(verdict, StopVerdict):
|
|
204
|
+
return StopVerdict.defer(
|
|
205
|
+
f"stop policy {name!r} returned a {type(verdict).__name__}, not a "
|
|
206
|
+
f"StopVerdict — deferring (a policy that does not return the verdict "
|
|
207
|
+
f"type cannot be trusted to halt the loop)."
|
|
208
|
+
)
|
|
209
|
+
return verdict
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def stop_under_resource_floor(
|
|
213
|
+
policy: StopPolicy, state: object, config: object,
|
|
214
|
+
) -> StopVerdict:
|
|
215
|
+
"""Decide loop-STOP, AND-ed under the unforgeable `resource_blocked` floor.
|
|
216
|
+
|
|
217
|
+
The structural soundness guarantee in one function (mirrors
|
|
218
|
+
`overlap_policy.admissible_under_floor`). The kernel's `resource_blocked` STOP
|
|
219
|
+
is the floor; a policy can only ever move toward MORE halting relative to it:
|
|
220
|
+
|
|
221
|
+
* floor STOP (`state.resource_blocked` True) → the floor verdict is returned
|
|
222
|
+
regardless of the policy. A hostile/buggy policy cannot even express a
|
|
223
|
+
verdict that keeps a doomed loop alive past a measured resource wall — the
|
|
224
|
+
dangerous cell is unreachable.
|
|
225
|
+
* floor go, policy STOP → the policy's STOP (a host opted a decision class
|
|
226
|
+
into halting — the safe, more-halting direction).
|
|
227
|
+
* floor go, policy DEFER/NEVER → DEFER/NEVER (fall through to the scout's
|
|
228
|
+
evidence-only default — today's behavior).
|
|
229
|
+
* policy raises / wrong type → DEFER (via `run_stop_policy`; fail toward the
|
|
230
|
+
default, never a spurious halt).
|
|
231
|
+
|
|
232
|
+
`state` is read for `resource_blocked` / `resource_block_reason` by attribute
|
|
233
|
+
(it is the scout `ScoutState`; we keep this module free of a scout import to
|
|
234
|
+
avoid a cycle — scout imports stop_policy, not the reverse).
|
|
235
|
+
"""
|
|
236
|
+
if getattr(state, "resource_blocked", False):
|
|
237
|
+
why = getattr(state, "resource_block_reason", "") or "host cannot launch more work"
|
|
238
|
+
return StopVerdict.stop(
|
|
239
|
+
f"resource_blocked floor: {why}", cause_key="resource_blocked",
|
|
240
|
+
evidence=(f"resource_blocked: {why}",),
|
|
241
|
+
)
|
|
242
|
+
return run_stop_policy(policy, state, config)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# ---------------------------------------------------------------------------
|
|
246
|
+
# Resolution — built-in first, then the `dos.stop_policies` entry-point group.
|
|
247
|
+
# ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
# The entry-point group a workspace/driver registers a STOP policy under.
|
|
250
|
+
STOP_POLICY_ENTRY_POINT_GROUP = "dos.stop_policies"
|
|
251
|
+
|
|
252
|
+
# The built-in policies, resolvable by name and UNSHADOWABLE by a plugin (a plugin
|
|
253
|
+
# registering `never` cannot displace this one — built-ins resolve first). Only the
|
|
254
|
+
# DEFER-everything baseline ships in the kernel; a queue-reading or model-backed
|
|
255
|
+
# policy lives in a driver/plugin.
|
|
256
|
+
_BUILT_IN_POLICIES: dict[str, type] = {
|
|
257
|
+
NeverStopPolicy.name: NeverStopPolicy,
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _discover_entry_point_policies(*, _stderr=None) -> list[tuple[str, StopPolicy]]:
|
|
262
|
+
"""Find STOP policies registered under the `dos.stop_policies` group.
|
|
263
|
+
|
|
264
|
+
A plugin registers ``name = "pkg.module:PolicyClass"`` in its
|
|
265
|
+
``[project.entry-points."dos.stop_policies"]``. We load each, instantiate it if
|
|
266
|
+
it is a class, and return ``(name, policy)`` pairs sorted by name. A plugin
|
|
267
|
+
that fails to load is skipped with a one-line stderr note rather than crashing
|
|
268
|
+
a scout call — the same posture `_discover_entry_point_policies` (overlap) /
|
|
269
|
+
judge discovery take."""
|
|
270
|
+
stderr = _stderr if _stderr is not None else sys.stderr
|
|
271
|
+
out: list[tuple[str, StopPolicy]] = []
|
|
272
|
+
try:
|
|
273
|
+
from importlib.metadata import entry_points
|
|
274
|
+
except Exception: # pragma: no cover - importlib.metadata always present py3.11+
|
|
275
|
+
return out
|
|
276
|
+
try:
|
|
277
|
+
eps = entry_points(group=STOP_POLICY_ENTRY_POINT_GROUP)
|
|
278
|
+
except TypeError: # pragma: no cover - py<3.10 selectable-API fallback
|
|
279
|
+
eps = entry_points().get(STOP_POLICY_ENTRY_POINT_GROUP, []) # type: ignore[attr-defined]
|
|
280
|
+
except Exception: # pragma: no cover - defensive: never let discovery crash a call
|
|
281
|
+
return out
|
|
282
|
+
for ep in sorted(eps, key=lambda e: e.name):
|
|
283
|
+
try:
|
|
284
|
+
obj = ep.load()
|
|
285
|
+
policy = obj() if isinstance(obj, type) else obj
|
|
286
|
+
except Exception as e: # pragma: no cover - depends on third-party plugin
|
|
287
|
+
print(
|
|
288
|
+
f"warning: stop policy plugin {ep.name!r} failed to load ({e}); "
|
|
289
|
+
f"skipping",
|
|
290
|
+
file=stderr,
|
|
291
|
+
)
|
|
292
|
+
continue
|
|
293
|
+
out.append((ep.name, policy))
|
|
294
|
+
return out
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def resolve_stop_policy(name: str, *, _stderr=None) -> StopPolicy:
|
|
298
|
+
"""Resolve a STOP policy by name: built-ins first, then `dos.stop_policies` plugins.
|
|
299
|
+
|
|
300
|
+
Built-ins (`never`) resolve FIRST and cannot be shadowed by a plugin of the
|
|
301
|
+
same name — the trusted-baseline guarantee, identical to `resolve_judge` /
|
|
302
|
+
`resolve_overlap_policy`. An unknown name fails LOUD with the known list (it
|
|
303
|
+
never silently degrades to `never`, which would hide a typo'd policy name): the
|
|
304
|
+
caller asked for a specific policy and getting a different one silently is
|
|
305
|
+
exactly the unannounced substitution the kernel refuses."""
|
|
306
|
+
if name in _BUILT_IN_POLICIES:
|
|
307
|
+
return _BUILT_IN_POLICIES[name]()
|
|
308
|
+
discovered = dict(_discover_entry_point_policies(_stderr=_stderr))
|
|
309
|
+
if name in discovered:
|
|
310
|
+
return discovered[name]
|
|
311
|
+
known = sorted(set(_BUILT_IN_POLICIES) | set(discovered))
|
|
312
|
+
raise ValueError(
|
|
313
|
+
f"unknown stop policy {name!r}; known: {', '.join(known)}"
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def active_stop_policy(*, config: object = None, _stderr=None) -> StopPolicy:
|
|
318
|
+
"""The STOP policy a CALLER threads into the scout (or None to use the default).
|
|
319
|
+
|
|
320
|
+
Resolution: a workspace may name its policy in ``config.stop_policy_name`` (a
|
|
321
|
+
`dos.toml [scout] stop_policy` data field); absent that, the built-in `never`
|
|
322
|
+
baseline. Does ENTRY-POINT DISCOVERY (I/O) only when a non-`never` name is
|
|
323
|
+
configured, so it is a CALL-BOUNDARY helper (the adapter that builds
|
|
324
|
+
`ScoutState`), never called inside the pure `choose`. The pure default — no
|
|
325
|
+
config, or `never` — returns the built-in with no discovery, so the hot path
|
|
326
|
+
stays I/O-free, exactly as `active_overlap_policy` does.
|
|
327
|
+
|
|
328
|
+
The pure `choose` never calls this; the adapter resolves the policy and passes
|
|
329
|
+
it in as `ScoutState.stop_policy` (or leaves it None → the scout skips the rung
|
|
330
|
+
entirely, byte-identical to before the seam)."""
|
|
331
|
+
name = getattr(config, "stop_policy_name", None) if config is not None else None
|
|
332
|
+
if not name or name == NeverStopPolicy.name:
|
|
333
|
+
return NeverStopPolicy()
|
|
334
|
+
return resolve_stop_policy(str(name), _stderr=_stderr)
|