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.
Files changed (178) hide show
  1. dos/__init__.py +261 -0
  2. dos/_bin/dos-hook.exe +0 -0
  3. dos/_filelock.py +255 -0
  4. dos/_job_policy.py +97 -0
  5. dos/_tree.py +145 -0
  6. dos/admission.py +433 -0
  7. dos/answer_shape.py +299 -0
  8. dos/arbiter.py +859 -0
  9. dos/archive_lock.py +266 -0
  10. dos/arg_provenance.py +814 -0
  11. dos/attest.py +472 -0
  12. dos/breaker.py +311 -0
  13. dos/churn.py +226 -0
  14. dos/claim_extract.py +229 -0
  15. dos/claim_ttl.py +150 -0
  16. dos/cli.py +8721 -0
  17. dos/commit_audit.py +666 -0
  18. dos/completion.py +466 -0
  19. dos/concurrency_class.py +154 -0
  20. dos/config.py +1380 -0
  21. dos/config_lint.py +464 -0
  22. dos/cooldown.py +390 -0
  23. dos/coverage.py +387 -0
  24. dos/dangling_intent.py +287 -0
  25. dos/data_class.py +397 -0
  26. dos/decisions.py +1274 -0
  27. dos/decisions_tui.py +251 -0
  28. dos/dispatch_top.py +740 -0
  29. dos/dispatch_top_tui.py +116 -0
  30. dos/drivers/__init__.py +40 -0
  31. dos/drivers/ci_status.py +630 -0
  32. dos/drivers/citation_resolve.py +703 -0
  33. dos/drivers/decision_stop.py +98 -0
  34. dos/drivers/export_file.py +173 -0
  35. dos/drivers/export_otlp.py +275 -0
  36. dos/drivers/export_statsd.py +242 -0
  37. dos/drivers/hook_dialects.py +391 -0
  38. dos/drivers/job.py +47 -0
  39. dos/drivers/llm_judge.py +360 -0
  40. dos/drivers/memory_recall.py +1231 -0
  41. dos/drivers/notify_slack.py +373 -0
  42. dos/drivers/notify_webhook.py +251 -0
  43. dos/drivers/operator_judge.py +114 -0
  44. dos/drivers/os_acceptance.py +228 -0
  45. dos/drivers/paste_log.py +132 -0
  46. dos/drivers/plan_scope.py +133 -0
  47. dos/drivers/self_improve.py +375 -0
  48. dos/drivers/similarity_judge.py +249 -0
  49. dos/drivers/state_diff.py +274 -0
  50. dos/drivers/supervisor.py +347 -0
  51. dos/drivers/watchdog.py +363 -0
  52. dos/drivers/workshop.py +160 -0
  53. dos/durable_schema.py +344 -0
  54. dos/effect_witness.py +393 -0
  55. dos/efficiency.py +318 -0
  56. dos/enforce.py +414 -0
  57. dos/enumerate.py +776 -0
  58. dos/env_print.py +378 -0
  59. dos/event_severity.py +258 -0
  60. dos/evidence.py +692 -0
  61. dos/exec_capability.py +256 -0
  62. dos/export_cursor.py +143 -0
  63. dos/exporter.py +320 -0
  64. dos/firing_label.py +353 -0
  65. dos/fleet_roll.py +226 -0
  66. dos/gate_classify.py +827 -0
  67. dos/gh4_coverage.py +179 -0
  68. dos/git_delta.py +122 -0
  69. dos/guard.py +215 -0
  70. dos/health.py +552 -0
  71. dos/help_summary.py +519 -0
  72. dos/home.py +934 -0
  73. dos/hook_binary.py +194 -0
  74. dos/hook_dialect.py +271 -0
  75. dos/hook_exit.py +191 -0
  76. dos/hook_install.py +437 -0
  77. dos/id_alloc.py +304 -0
  78. dos/improve.py +499 -0
  79. dos/intent_ledger.py +635 -0
  80. dos/interpret.py +176 -0
  81. dos/intervention.py +769 -0
  82. dos/intervention_eval.py +371 -0
  83. dos/journal_delta.py +308 -0
  84. dos/judge_eval.py +328 -0
  85. dos/judges.py +366 -0
  86. dos/lane_infer.py +127 -0
  87. dos/lane_journal.py +1001 -0
  88. dos/lane_lease.py +952 -0
  89. dos/lane_overlap.py +228 -0
  90. dos/lease_health.py +282 -0
  91. dos/lifecycle.py +211 -0
  92. dos/liveness.py +352 -0
  93. dos/lock_modes.py +185 -0
  94. dos/log_source.py +395 -0
  95. dos/loop_decide.py +1746 -0
  96. dos/marker_gate.py +254 -0
  97. dos/marker_sensor.py +396 -0
  98. dos/noop_streak.py +280 -0
  99. dos/notify.py +479 -0
  100. dos/observe.py +175 -0
  101. dos/oracle.py +1661 -0
  102. dos/overlap_eval.py +214 -0
  103. dos/overlap_policy.py +342 -0
  104. dos/packet_sidecar.py +267 -0
  105. dos/phase_shipped.py +1985 -0
  106. dos/pick_priority.py +225 -0
  107. dos/pickable.py +369 -0
  108. dos/picker_oracle.py +1037 -0
  109. dos/plan_board.py +513 -0
  110. dos/plan_board_tui.py +113 -0
  111. dos/plan_source.py +455 -0
  112. dos/posttool_sensor.py +528 -0
  113. dos/precursor_gate.py +499 -0
  114. dos/precursor_gate_eval.py +239 -0
  115. dos/preflight.py +825 -0
  116. dos/pretool_sensor.py +490 -0
  117. dos/proc_delta.py +181 -0
  118. dos/productivity.py +296 -0
  119. dos/provider_limit.py +242 -0
  120. dos/py.typed +4 -0
  121. dos/reason_morphology.py +299 -0
  122. dos/reasons.py +449 -0
  123. dos/reconcile.py +173 -0
  124. dos/recurring_wedge.py +206 -0
  125. dos/render.py +393 -0
  126. dos/result_state.py +468 -0
  127. dos/resume.py +578 -0
  128. dos/resume_evidence.py +293 -0
  129. dos/retention.py +344 -0
  130. dos/reward.py +372 -0
  131. dos/rewind.py +587 -0
  132. dos/rewind_evidence.py +168 -0
  133. dos/rewind_tokens.py +252 -0
  134. dos/run_id.py +342 -0
  135. dos/scope.py +520 -0
  136. dos/scope_source.py +382 -0
  137. dos/scout.py +982 -0
  138. dos/self_modify.py +209 -0
  139. dos/sibling_scan.py +569 -0
  140. dos/skills/EXAMPLES.md +584 -0
  141. dos/skills/dos-class-cycle/SKILL.md +107 -0
  142. dos/skills/dos-dispatch/SKILL.md +177 -0
  143. dos/skills/dos-dispatch-loop/SKILL.md +254 -0
  144. dos/skills/dos-goal-gate/SKILL.md +269 -0
  145. dos/skills/dos-next-up/SKILL.md +231 -0
  146. dos/skills/dos-promote/SKILL.md +114 -0
  147. dos/skills/dos-replan/SKILL.md +159 -0
  148. dos/skills/dos-replan-loop/SKILL.md +114 -0
  149. dos/skills/dos-self-improve/SKILL.md +213 -0
  150. dos/skills/dos-supervise-loop/SKILL.md +180 -0
  151. dos/skills/dos-unstick/SKILL.md +108 -0
  152. dos/skills/dos-witness-claim/SKILL.md +251 -0
  153. dos/stamp.py +1002 -0
  154. dos/state_health.py +387 -0
  155. dos/status.py +114 -0
  156. dos/stop_policy.py +334 -0
  157. dos/supervise.py +1014 -0
  158. dos/testwitness.py +392 -0
  159. dos/timeline.py +1027 -0
  160. dos/tokens.py +485 -0
  161. dos/tool_stream.py +393 -0
  162. dos/tool_stream_eval.py +226 -0
  163. dos/trace.py +524 -0
  164. dos/verdict.py +140 -0
  165. dos/verdict_cli.py +189 -0
  166. dos/verdict_journal.py +497 -0
  167. dos/verdict_rollup.py +217 -0
  168. dos/verdicts.py +181 -0
  169. dos/wedge_reason.py +282 -0
  170. dos_kernel-0.22.0.dist-info/METADATA +859 -0
  171. dos_kernel-0.22.0.dist-info/RECORD +178 -0
  172. dos_kernel-0.22.0.dist-info/WHEEL +5 -0
  173. dos_kernel-0.22.0.dist-info/entry_points.txt +39 -0
  174. dos_kernel-0.22.0.dist-info/licenses/LICENSE +21 -0
  175. dos_kernel-0.22.0.dist-info/top_level.txt +2 -0
  176. dos_mcp/__init__.py +52 -0
  177. dos_mcp/py.typed +2 -0
  178. 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)