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/scope_source.py ADDED
@@ -0,0 +1,382 @@
1
+ """The scope-source seam — docs/117 Phase 4: distrust the declared EXTENT.
2
+
3
+ Why this exists
4
+ ===============
5
+
6
+ `completion.classify` answers "is the WHOLE declared job verifiably done?" by
7
+ asking `resume` whether the residual (`declared − verified`) is empty. But that
8
+ question trusts ONE thing it should not: the **denominator**. `declared_steps`
9
+ comes from the run's own `INTENT` record — a self-report of *how big the job is*.
10
+ An agent that declares three steps, ships three, and stops has an empty residual
11
+ and looks `COMPLETE` — even if the real job had five. This is the completion
12
+ analogue of the disease the whole kernel is built against (`docs/103`): a verdict
13
+ computed from the judged agent's own narration of its scope.
14
+
15
+ A `ScopeSource` is the rung that distrusts the extent. It cross-checks the
16
+ declared steps against an **external** account of scope — the plan registry's
17
+ phase list, a PR's changed-files, an issue's acceptance criteria — and rules:
18
+ did the run declare the *whole* job, or under-declare it? Its verdict can only
19
+ make completion HARDER (withhold `COMPLETE`, surface `UNDERDECLARED`); it can
20
+ never grant completion. So "done means done against the *real* scope, not the
21
+ scope the agent chose to admit" becomes a kernel-checkable property — without
22
+ baking any host's notion of scope into the kernel.
23
+
24
+ The same seam shape, re-aimed at extent
25
+ =======================================
26
+
27
+ This module is the `overlap_policy` / `judges` apparatus pointed at scope:
28
+
29
+ * a `ScopeSource` **Protocol** (the contract a driver implements),
30
+ * a built-in **null** source `AllDeclaredScope` (always `extent_honest=True` —
31
+ "trust the declared extent," i.e. *today's* behavior; the unshadowable
32
+ baseline, the `PrefixOverlapPolicy` / `AbstainJudge` analogue),
33
+ * `run_scope` (one source, fail-to-strict) and `honest_under_floor` (the
34
+ conjunction over many — `extent_honest ⟺ every source agrees`),
35
+ * a by-name **resolver** (built-ins first, unshadowable, fail-loud on unknown),
36
+ * call-boundary entry-point **discovery** over the `dos.scope_sources` group.
37
+
38
+ The safe direction, by construction
39
+ ====================================
40
+
41
+ The structural guarantee is the inverse of `overlap_policy`'s and *simpler* for
42
+ it. There the plugin returns a verdict that includes ADMIT (the dangerous,
43
+ false-*admit* direction), so a deterministic floor must be ANDed in to stop a
44
+ lying plugin from admitting a collision. Here the dangerous direction is a
45
+ false-*COMPLETE*, and a `ScopeSource` can only ever push toward the SAFE side —
46
+ it withholds completion. So no competing deterministic floor is needed: the
47
+ conjunction alone is the guarantee.
48
+
49
+ > A wired `ScopeSource` may turn a `COMPLETE` into `UNDERDECLARED`. It can
50
+ > never turn an `UNDERDECLARED` (or an `INCOMPLETE`) into `COMPLETE`.
51
+
52
+ `completion.classify` grants `COMPLETE` only as
53
+ ``residual_empty AND honest_under_floor(scope_verdicts).extent_honest``. With no
54
+ source wired, `honest_under_floor(())` is honest, so completion is **byte-for-byte
55
+ today's "all declared verified" floor**. Each wired source can only flip an
56
+ `extent_honest` from True to False — strictly stricter. And `run_scope` converts
57
+ any raise / malformed return to `extent_honest = False` (the judge fail-to-ABSTAIN
58
+ analogue, biased toward *refusing completion*): a buggy or hostile source surfaces
59
+ a decision rather than silently certifying done. The `AllDeclaredScope` baseline is
60
+ the one source a plugin can never displace (the resolver returns built-ins first),
61
+ so the floor — "trust the declared extent" — is always reachable and never forgeable
62
+ away.
63
+
64
+ Purity & layering
65
+ =================
66
+
67
+ Pure stdlib — a Protocol, a built-in null source, the conjunction helpers, a
68
+ resolver. No host names, no I/O inside a verdict. It sits in the kernel beside
69
+ `overlap_policy` / `judges` (which likewise hold a pure protocol + resolver while
70
+ real *implementations* live outside). A source MAY do I/O *inside* `scope_verdict`
71
+ (read the plan registry, shell `git`, call an API) IFF it lives in a **driver** —
72
+ the JUDGE-rung allowance — but the kernel's own `AllDeclaredScope` does not.
73
+ Entry-point discovery (the one bit of I/O) happens at the call boundary in
74
+ `active_scope_sources`, exactly as `active_overlap_policy` / `active_judges` /
75
+ `active_predicates` do. The `dos.drivers` litmus (no `src/dos/*` except `drivers/`
76
+ imports `dos.drivers`) covers the real sources.
77
+ """
78
+
79
+ from __future__ import annotations
80
+
81
+ import sys
82
+ from dataclasses import dataclass
83
+ from typing import Protocol, runtime_checkable
84
+
85
+ from dos.intent_ledger import LedgerState
86
+
87
+
88
+ # ───────────────────────────── the scope verdict ──────────────────────────────
89
+ @dataclass(frozen=True)
90
+ class ScopeVerdict:
91
+ """One `ScopeSource`'s ruling on a run's DECLARED extent.
92
+
93
+ ``extent_honest`` is the load-bearing boolean: True iff the source believes the
94
+ run declared the *whole* job (the residual's denominator was not understated).
95
+ False means the run under-declared — there is real scope it never put on the
96
+ books, so an empty residual does NOT mean done. ``reason`` is the operator-facing
97
+ one-liner. ``missing`` is the optional, legibility-only list of scope the source
98
+ found beyond the declared steps (e.g. plan phases not in `declared_steps`) — it
99
+ is carried into the `UNDERDECLARED` reason so a human sees *what* was omitted,
100
+ but the verdict turns on ``extent_honest`` alone. ``source`` names the ruling
101
+ source (for the surfaced reason / the decisions queue).
102
+
103
+ The asymmetry is the point and mirrors the seam: an honest verdict permits
104
+ `COMPLETE` (subject to every other source also agreeing); a dishonest one
105
+ withholds it. A source can move the verdict only toward `UNDERDECLARED`.
106
+ """
107
+
108
+ extent_honest: bool
109
+ reason: str
110
+ source: str = ""
111
+ missing: tuple[str, ...] = ()
112
+
113
+ def to_dict(self) -> dict:
114
+ return {
115
+ "extent_honest": self.extent_honest,
116
+ "reason": self.reason,
117
+ "source": self.source,
118
+ "missing": list(self.missing),
119
+ }
120
+
121
+
122
+ @runtime_checkable
123
+ class ScopeSource(Protocol):
124
+ """The contract a driver implements to distrust a run's declared extent.
125
+
126
+ ``name`` is the token `dos doctor` lists and a `--scope-source <name>` selects.
127
+ ``scope_verdict`` is handed the run's `LedgerState` (it reads `declared_steps`
128
+ and `goal`/`plan`/`phase` — the *declared* extent) + the active ``config``
129
+ (read-only — it reads policy / locates the external account of scope, but the
130
+ type gives it nothing to mutate) and returns a `ScopeVerdict`.
131
+
132
+ A source MAY do I/O *inside* ``scope_verdict`` (read the plan registry, shell
133
+ out to `git`, call an issue tracker) — unlike a predicate or a renderer, which
134
+ are pure — IFF it lives in a driver, the same reason a ruling judge does. The
135
+ discipline that keeps it honest is NOT purity; it is the conjunction
136
+ (`honest_under_floor`) + fail-to-strict (`run_scope`): whatever a source
137
+ returns, `COMPLETE` requires EVERY source to vote honest, and a raise / bad
138
+ return is read as *dishonest*, so a source is structurally unable to grant
139
+ completion — only to withhold it.
140
+ """
141
+
142
+ name: str
143
+
144
+ def scope_verdict(self, state: LedgerState, config: object) -> ScopeVerdict:
145
+ ...
146
+
147
+
148
+ class AllDeclaredScope:
149
+ """The built-in null source: trust the declared extent (today's behavior).
150
+
151
+ Always returns ``extent_honest=True`` — it asserts that whatever the run
152
+ declared IS the whole job. With only this source (or none) wired, completion is
153
+ **byte-for-byte identical to before the seam**: `honest_under_floor` is honest,
154
+ so `classify` grants `COMPLETE` purely on the empty residual, exactly as it did
155
+ in Phase 1. It is the unshadowable baseline a plugin can never displace
156
+ (`resolve_scope_source` resolves built-ins first) — the scope analogue of the
157
+ unshadowable `prefix` policy / `abstain` judge / `text` renderer.
158
+
159
+ It does no I/O and reads nothing external — it is the *absence* of a scope
160
+ check, made explicit as an object so "no source wired" and "the null source"
161
+ are the same code path.
162
+ """
163
+
164
+ name = "all-declared"
165
+
166
+ def scope_verdict(self, state: LedgerState, config: object) -> ScopeVerdict:
167
+ return ScopeVerdict(
168
+ extent_honest=True,
169
+ reason="declared extent trusted (no external scope check wired)",
170
+ source=self.name,
171
+ )
172
+
173
+
174
+ # The unshadowable baseline instance — pure, stateless, reused.
175
+ _NULL_SOURCE = AllDeclaredScope()
176
+
177
+
178
+ def run_scope(source: ScopeSource, state: LedgerState, config: object) -> ScopeVerdict:
179
+ """Run ONE `ScopeSource`, converting any misbehavior to a DISHONEST verdict.
180
+
181
+ The fail-to-strict boundary (the judge `run_judge` analogue, biased toward
182
+ *refusing completion*). A source that raises, or returns something that is not a
183
+ `ScopeVerdict`, is mapped to ``extent_honest=False`` — we withhold `COMPLETE`
184
+ and surface `UNDERDECLARED` rather than risk certifying done on a broken scope
185
+ check. This is the conservative direction for "are we done": a source failing
186
+ open (→ honest → COMPLETE) would let a crashing scope check silently grant
187
+ completion, exactly the unannounced trust the kernel refuses.
188
+
189
+ A source that returns a well-formed honest/dishonest verdict is passed through
190
+ verbatim (its ``reason``/``missing`` carried for the operator).
191
+ """
192
+ name = getattr(source, "name", type(source).__name__)
193
+ try:
194
+ verdict = source.scope_verdict(state, config)
195
+ except Exception as e: # fail-to-strict: a raising source withholds COMPLETE
196
+ return ScopeVerdict(
197
+ extent_honest=False,
198
+ reason=(f"scope source {name!r} raised ({e!r}) — withholding COMPLETE "
199
+ f"(failing to UNDERDECLARED, the conservative direction)"),
200
+ source=name,
201
+ )
202
+ if not isinstance(verdict, ScopeVerdict):
203
+ # Never read a foreign object's `.extent_honest` — a wrong return type cannot
204
+ # be trusted to grant completion. Treat as dishonest (withhold COMPLETE).
205
+ return ScopeVerdict(
206
+ extent_honest=False,
207
+ reason=(f"scope source {name!r} returned a {type(verdict).__name__}, not "
208
+ f"a ScopeVerdict — withholding COMPLETE (conservative)"),
209
+ source=name,
210
+ )
211
+ return verdict
212
+
213
+
214
+ @dataclass(frozen=True)
215
+ class ScopeConjunction:
216
+ """The combined extent ruling over many sources — `extent_honest` + the why.
217
+
218
+ ``extent_honest`` is the AND over every source's vote. ``verdicts`` are the
219
+ individual rulings (so a reader sees who voted what); ``dishonest`` is the
220
+ subset that withheld completion (empty iff honest). ``missing`` is the union of
221
+ all flagged-missing scope (deduped, order-preserving) — what `classify` folds
222
+ into the `UNDERDECLARED` reason."""
223
+
224
+ extent_honest: bool
225
+ verdicts: tuple[ScopeVerdict, ...] = ()
226
+ dishonest: tuple[ScopeVerdict, ...] = ()
227
+ missing: tuple[str, ...] = ()
228
+
229
+ @property
230
+ def reason(self) -> str:
231
+ if self.extent_honest:
232
+ n = len(self.verdicts)
233
+ return (f"declared extent confirmed honest by {n} scope source(s)"
234
+ if n else "declared extent trusted (no scope source wired)")
235
+ names = ", ".join(sorted({v.source for v in self.dishonest if v.source}))
236
+ miss = f" (missing: {', '.join(self.missing)})" if self.missing else ""
237
+ return (f"declared extent under-declared per scope source(s) "
238
+ f"[{names or 'unnamed'}]{miss}")
239
+
240
+
241
+ def honest_under_floor(verdicts: tuple[ScopeVerdict, ...]) -> ScopeConjunction:
242
+ """Combine scope verdicts: ``extent_honest`` iff EVERY source votes honest.
243
+
244
+ The structural soundness guarantee in one function — the inverse of
245
+ `overlap_policy.admissible_under_floor` and simpler, because here the dangerous
246
+ direction (false-COMPLETE) is the one a source *cannot reach*:
247
+
248
+ * no verdicts → honest (the floor: with nothing wired, the declared extent is
249
+ trusted — today's behavior, the `AllDeclaredScope` baseline).
250
+ * all honest → honest (every source agrees the extent was the whole job).
251
+ * ANY dishonest → DISHONEST (one source flagging under-declaration withholds
252
+ `COMPLETE`; the others cannot out-vote it — a source can only push toward
253
+ `UNDERDECLARED`, never away from it).
254
+
255
+ So a wired source can only ever move completion toward `UNDERDECLARED`. There is
256
+ no AND-with-a-floor as in `overlap_policy` because withholding is already the
257
+ safe side: the conjunction itself IS the guarantee. (`run_scope` should be
258
+ applied to each source BEFORE this, so a raising source is already a dishonest
259
+ verdict here — fail-to-strict composes with the conjunction.)"""
260
+ vs = tuple(verdicts)
261
+ dishonest = tuple(v for v in vs if not v.extent_honest)
262
+ # Union of flagged-missing scope across dishonest sources, order-preserving + deduped.
263
+ seen: set[str] = set()
264
+ missing: list[str] = []
265
+ for v in dishonest:
266
+ for m in v.missing:
267
+ if m not in seen:
268
+ seen.add(m)
269
+ missing.append(m)
270
+ return ScopeConjunction(
271
+ extent_honest=not dishonest,
272
+ verdicts=vs,
273
+ dishonest=dishonest,
274
+ missing=tuple(missing),
275
+ )
276
+
277
+
278
+ # ---------------------------------------------------------------------------
279
+ # Resolution — built-in first, then the `dos.scope_sources` entry-point group.
280
+ # ---------------------------------------------------------------------------
281
+
282
+ # The entry-point group a workspace/researcher registers a scope source under.
283
+ SCOPE_SOURCE_ENTRY_POINT_GROUP = "dos.scope_sources"
284
+
285
+ # The built-in sources, resolvable by name and UNSHADOWABLE by a plugin (a plugin
286
+ # registering `all-declared` cannot displace this one — built-ins resolve first).
287
+ # Only the null baseline ships in the kernel; every real source (plan-registry,
288
+ # changed-files, acceptance-criteria) lives in a driver/plugin.
289
+ _BUILT_IN_SOURCES: dict[str, type] = {
290
+ AllDeclaredScope.name: AllDeclaredScope,
291
+ }
292
+
293
+
294
+ def _discover_entry_point_sources(*, _stderr=None) -> list[tuple[str, ScopeSource]]:
295
+ """Find scope sources registered under the `dos.scope_sources` group.
296
+
297
+ A source plugin registers ``name = "pkg.module:SourceClass"`` in its
298
+ ``[project.entry-points."dos.scope_sources"]``. We load each, instantiate it if
299
+ it is a class, and return ``(entry_point_name, source)`` pairs sorted by name
300
+ (stable, so `dos doctor` order is deterministic). A plugin that fails to load is
301
+ skipped with a one-line stderr note rather than crashing completion — the same
302
+ posture overlap-policy / judge / predicate discovery take."""
303
+ stderr = _stderr if _stderr is not None else sys.stderr
304
+ out: list[tuple[str, ScopeSource]] = []
305
+ try:
306
+ from importlib.metadata import entry_points
307
+ except Exception: # pragma: no cover - importlib.metadata always present py3.11+
308
+ return out
309
+ try:
310
+ eps = entry_points(group=SCOPE_SOURCE_ENTRY_POINT_GROUP)
311
+ except TypeError: # pragma: no cover - py<3.10 selectable-API fallback
312
+ eps = entry_points().get(SCOPE_SOURCE_ENTRY_POINT_GROUP, []) # type: ignore[attr-defined]
313
+ except Exception: # pragma: no cover - defensive: never let discovery crash a call
314
+ return out
315
+ for ep in sorted(eps, key=lambda e: e.name):
316
+ try:
317
+ obj = ep.load()
318
+ source = obj() if isinstance(obj, type) else obj
319
+ except Exception as e: # pragma: no cover - depends on third-party plugin
320
+ print(
321
+ f"warning: scope source plugin {ep.name!r} failed to load ({e}); "
322
+ f"skipping",
323
+ file=stderr,
324
+ )
325
+ continue
326
+ out.append((ep.name, source))
327
+ return out
328
+
329
+
330
+ def resolve_scope_source(name: str, *, _stderr=None) -> ScopeSource:
331
+ """Resolve a scope source by name: built-ins first, then plugins.
332
+
333
+ Built-ins (`all-declared`) resolve FIRST and cannot be shadowed by a plugin of
334
+ the same name — the trusted-baseline guarantee, identical to
335
+ `resolve_overlap_policy` / `resolve_judge`. An unknown name fails LOUD with the
336
+ known list (it never silently degrades to `all-declared`, which would hide a
337
+ typo'd source name): the caller asked for a specific scope check and getting a
338
+ different one silently is the unannounced substitution the kernel refuses."""
339
+ if name in _BUILT_IN_SOURCES:
340
+ return _BUILT_IN_SOURCES[name]()
341
+ discovered = dict(_discover_entry_point_sources(_stderr=_stderr))
342
+ if name in discovered:
343
+ return discovered[name]
344
+ known = sorted(set(_BUILT_IN_SOURCES) | set(discovered))
345
+ raise ValueError(
346
+ f"unknown scope source {name!r}; known: {', '.join(known)}"
347
+ )
348
+
349
+
350
+ def active_scope_sources(*, config: object = None, _stderr=None) -> list[ScopeSource]:
351
+ """The scope sources a CALLER threads into `completion.classify`.
352
+
353
+ Resolution: a workspace may name its sources in ``config.scope_source_names``
354
+ (a list — the `dos.toml [completion] scope_sources` data field); absent that, an
355
+ EMPTY list (NOT the null source) so the default path runs no source and is
356
+ byte-identical to today (`honest_under_floor(())` is honest). Does ENTRY-POINT
357
+ DISCOVERY (I/O) when names are configured, so it is a CALL-BOUNDARY helper (the
358
+ CLI's `cmd_complete`, `dos doctor`), never called inside the pure `classify`. The
359
+ pure default — no config — returns `[]` with no discovery, so completion's hot
360
+ path stays I/O-free, exactly as `built_in_predicates` / `active_overlap_policy`.
361
+
362
+ Returning `[]` (not `[AllDeclaredScope()]`) by default is deliberate: an empty
363
+ list and the null source produce the SAME verdict (honest), and `[]` keeps the
364
+ default truly side-effect-free (no discovery, no per-source call). The null
365
+ source exists for an operator who wants to name it explicitly / for the resolver
366
+ floor, not as the implicit default population."""
367
+ names = getattr(config, "scope_source_names", None) if config is not None else None
368
+ if not names:
369
+ return []
370
+ out: list[ScopeSource] = []
371
+ for nm in names:
372
+ out.append(resolve_scope_source(str(nm), _stderr=_stderr))
373
+ return out
374
+
375
+
376
+ def active_scope_source_names(*, _stderr=None) -> list[str]:
377
+ """The names of every resolvable scope source (built-in + discovered) — what
378
+ `dos doctor` lists so an operator can see which extent checks completion could
379
+ use (the scope analogue of "see the active predicates / judges / policies")."""
380
+ built = list(_BUILT_IN_SOURCES)
381
+ discovered = [n for n, _s in _discover_entry_point_sources(_stderr=_stderr)]
382
+ return built + discovered