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/overlap_eval.py ADDED
@@ -0,0 +1,214 @@
1
+ """The overlap-evaluation harness — score a disjointness scorer against ground truth.
2
+
3
+ An `overlap_policy.OverlapPolicy` is a *hook*; this module is the *instrument* that
4
+ makes the hook produce a number — the admission twin of `dos.judge_eval`, and the
5
+ direct realization of `docs/90 §2`'s "backtest study against a labeled corpus of
6
+ concurrent runs, scored on detonations *missed* vs safe concurrency *forgone*."
7
+
8
+ The friendliness thesis (`docs/113 §4`/§7): a seam is only research-grade if it ships
9
+ the instrument that scores a contribution to it. Bring your own overlap scorer (an
10
+ import-graph analyzer, a semantic-similarity model, a learned conflict predictor),
11
+ bring a labelled corpus of concurrent-pair outcomes, and get back the two numbers that
12
+ matter:
13
+
14
+ * **false-admit rate** — of the pairs the scorer ADMITTED, the fraction that actually
15
+ `collided`. THE DANGEROUS CELL (the admission analogue of the judge's false-clear):
16
+ a non-empty cell means the scorer admitted a pair that corrupted shared state. The
17
+ exit code of `dos overlap-eval` is this verdict, so CI fails on any leak.
18
+ * **safe-concurrency-forgone rate** — of the pairs that did NOT collide, the fraction
19
+ the scorer REFUSED. The cost a stricter scorer pays; the SAFE-direction failure (a
20
+ needless serialization, never a corruption), so it is a quality knob, not a gate.
21
+
22
+ Ground truth is whether running the two trees concurrently ACTUALLY collided — a merge
23
+ conflict, a detonation log — derived from artifacts, NEVER from a scorer (the same
24
+ honesty stance as `judge_eval`: the eval is only as honest as its labels).
25
+
26
+ Everything here is **pure**: it consumes already-built cases, runs the scorer **under
27
+ the deterministic floor** (`admissible_under_floor`, so the eval measures exactly what
28
+ the arbiter would admit — not the raw policy, which could "admit" a pair the floor then
29
+ refuses), and counts. No I/O inside the scoring, no host names — it sits in the kernel
30
+ layer beside `overlap_policy`. A policy that does I/O inside `overlaps` (a model) does
31
+ it during scoring; that is the policy's surface, not the harness's.
32
+
33
+ Why score under the floor, not the raw policy
34
+ ==============================================
35
+
36
+ `score` runs each pair through `admissible_under_floor(policy, …)`, the SAME path
37
+ `DisjointnessPredicate` uses — so the grid reflects the *arbiter's* verdict, which is
38
+ the only verdict that matters operationally. A consequence worth stating: against the
39
+ prefix floor, a policy CANNOT register a false-admit on a prefix-colliding pair (the
40
+ floor refuses it regardless), so the false-admit cell is informative exactly where a
41
+ policy's admit set could legitimately extend past the prefix rule — i.e. under a
42
+ *stricter* floor (`docs/113 §3.1`, the glob-intersection floor). On today's prefix
43
+ floor the cell measures whether a *looser* `ratio_max` admitted a real collision the
44
+ soft-overlap tolerance should have caught. Either way it is the operationally-honest
45
+ number: what the arbiter would have let through.
46
+ """
47
+
48
+ from __future__ import annotations
49
+
50
+ from dataclasses import dataclass
51
+ from typing import Iterable
52
+
53
+ from dos.lane_overlap import OverlapDecision
54
+ from dos.overlap_policy import OverlapPolicy, admissible_under_floor
55
+
56
+
57
+ # A labelled example: the two trees + whether running them concurrently ACTUALLY
58
+ # collided (ground truth from artifacts — a merge conflict, a detonation). The
59
+ # trees are the same `list[str]` glob shape the arbiter leases.
60
+ @dataclass(frozen=True)
61
+ class OverlapCase:
62
+ tree_a: list[str] # the requested tree
63
+ tree_b: list[str] # the live-lease tree
64
+ collided: bool # ground truth: did concurrent execution corrupt shared state?
65
+ label: str = "" # optional human handle for the pair (carried, never scored)
66
+
67
+
68
+ @dataclass(frozen=True)
69
+ class OverlapReport:
70
+ """A scorer evaluated over labelled cases — the 2×2 confusion grid + rates.
71
+
72
+ The grid is the scorer's ADMIT/REFUSE verdict (under the floor) against each
73
+ pair's ground-truth collided/safe. The named cells:
74
+ * ``correct_admit`` — ADMIT a pair that did NOT collide (right: safe concurrency
75
+ allowed)
76
+ * ``false_admit`` — ADMIT a pair that DID collide (THE DANGEROUS CELL: a
77
+ collision let through — the one error admission must minimize)
78
+ * ``correct_refuse``— REFUSE a pair that DID collide (right: a collision caught)
79
+ * ``safe_forgone`` — REFUSE a pair that did NOT collide (wrong but SAFE: a
80
+ needless serialization, never a corruption)
81
+ """
82
+
83
+ n: int
84
+ correct_admit: int
85
+ false_admit: int
86
+ correct_refuse: int
87
+ safe_forgone: int
88
+
89
+ # --- aggregates ---
90
+
91
+ @property
92
+ def n_admit(self) -> int:
93
+ return self.correct_admit + self.false_admit
94
+
95
+ @property
96
+ def n_refuse(self) -> int:
97
+ return self.correct_refuse + self.safe_forgone
98
+
99
+ @property
100
+ def n_collided(self) -> int:
101
+ """Ground-truth COLLIDING pairs — the denominator for the leak rate."""
102
+ return self.false_admit + self.correct_refuse
103
+
104
+ @property
105
+ def n_safe(self) -> int:
106
+ """Ground-truth SAFE (non-colliding) pairs — the denominator for forgone-rate."""
107
+ return self.correct_admit + self.safe_forgone
108
+
109
+ # --- derived rates (all guard against divide-by-zero by returning 0.0) ---
110
+
111
+ @property
112
+ def false_admit_rate(self) -> float:
113
+ """Of the pairs the scorer ADMITTED, the fraction that actually collided. The
114
+ precision-of-admission number: when this scorer says "safe to run together,"
115
+ how often is it wrong? THE single most important admission metric — a scorer is
116
+ only safe to trust on its own if this is zero. (Against the prefix floor this is
117
+ zero for prefix-colliding pairs by construction; it becomes informative under a
118
+ looser `ratio_max` or a stricter floor — see the module docstring.)"""
119
+ return (self.false_admit / self.n_admit) if self.n_admit else 0.0
120
+
121
+ @property
122
+ def collision_leak_rate(self) -> float:
123
+ """Of all ground-truth COLLIDING pairs, the fraction the scorer admitted. The
124
+ recall-of-collisions number from the other side: what share of real collisions
125
+ leaked past admission entirely. `docs/90 §2`'s "detonations missed"."""
126
+ return (self.false_admit / self.n_collided) if self.n_collided else 0.0
127
+
128
+ @property
129
+ def safe_forgone_rate(self) -> float:
130
+ """Of all ground-truth SAFE pairs, the fraction the scorer REFUSED. `docs/90
131
+ §2`'s "safe concurrency forgone" — the cost of a stricter scorer. SAFE-direction,
132
+ so this trades against throughput, never against integrity (a high value means
133
+ lost parallelism, never a corruption)."""
134
+ return (self.safe_forgone / self.n_safe) if self.n_safe else 0.0
135
+
136
+ @property
137
+ def admit_rate(self) -> float:
138
+ """Fraction of all pairs admitted. The seductive raw throughput number — to be
139
+ read ALONGSIDE the collision cost it bought (`docs/90 §2`'s economic floor: the
140
+ right scalar is verified-velocity-per-$, never admit-rate alone)."""
141
+ return (self.n_admit / self.n) if self.n else 0.0
142
+
143
+ @property
144
+ def decisive_accuracy(self) -> float:
145
+ """How often the scorer's verdict matched ground truth — (correct_admit +
146
+ correct_refuse) / n. The overall correctness, both directions counted."""
147
+ return ((self.correct_admit + self.correct_refuse) / self.n) if self.n else 0.0
148
+
149
+ @property
150
+ def leaked(self) -> bool:
151
+ """True iff the dangerous cell is non-empty — the scorer admitted at least one
152
+ real collision. The boolean the `dos overlap-eval` exit code is built on."""
153
+ return self.false_admit > 0
154
+
155
+ def to_dict(self) -> dict:
156
+ return {
157
+ "n": self.n,
158
+ "grid": {
159
+ "correct_admit": self.correct_admit,
160
+ "false_admit": self.false_admit,
161
+ "correct_refuse": self.correct_refuse,
162
+ "safe_forgone": self.safe_forgone,
163
+ },
164
+ "rates": {
165
+ "false_admit_rate": round(self.false_admit_rate, 4),
166
+ "collision_leak_rate": round(self.collision_leak_rate, 4),
167
+ "safe_forgone_rate": round(self.safe_forgone_rate, 4),
168
+ "admit_rate": round(self.admit_rate, 4),
169
+ "decisive_accuracy": round(self.decisive_accuracy, 4),
170
+ },
171
+ "leaked": self.leaked,
172
+ }
173
+
174
+
175
+ def _admits(policy: OverlapPolicy, case: OverlapCase, config: object) -> bool:
176
+ """Whether the ARBITER would admit this pair under ``policy`` — i.e. the policy
177
+ AND-ed under the deterministic prefix floor (`admissible_under_floor`), the exact
178
+ path `DisjointnessPredicate` takes. An empty tree on either side is the
179
+ unknown-blast-radius case the predicate (not the policy) owns; the eval models the
180
+ both-known scoring the policy actually governs, so a case with an empty tree is
181
+ scored by the floor alone (empty-vs-known → the floor's own handling)."""
182
+ decision: OverlapDecision = admissible_under_floor(
183
+ policy, list(case.tree_a), list(case.tree_b), config)
184
+ return decision.admissible
185
+
186
+
187
+ def score(
188
+ policy: OverlapPolicy, cases: Iterable[OverlapCase], config: object = None,
189
+ ) -> OverlapReport:
190
+ """Run ``policy`` over labelled ``cases`` (UNDER THE FLOOR) and tabulate the grid.
191
+
192
+ Uses `admissible_under_floor`, so a policy that raises or returns garbage on a case
193
+ degrades to the floor verdict for that case rather than crashing the eval — the
194
+ report stays honest about a flaky scorer instead of hiding it (the `judge_eval`
195
+ fail-to-abstain posture, here fail-closed-to-floor). Pure: it reads the cases and
196
+ counts."""
197
+ ca = fa = cr = sf = 0
198
+ n = 0
199
+ for case in cases:
200
+ n += 1
201
+ admitted = _admits(policy, case, config)
202
+ if admitted:
203
+ if case.collided:
204
+ fa += 1 # false admit — the dangerous cell
205
+ else:
206
+ ca += 1 # correct admit
207
+ else:
208
+ if case.collided:
209
+ cr += 1 # correct refuse
210
+ else:
211
+ sf += 1 # safe concurrency forgone
212
+ return OverlapReport(
213
+ n=n, correct_admit=ca, false_admit=fa, correct_refuse=cr, safe_forgone=sf,
214
+ )
dos/overlap_policy.py ADDED
@@ -0,0 +1,342 @@
1
+ """The overlap-policy seam — Axis 7 of hackability: a pluggable disjointness scorer.
2
+
3
+ Why this exists
4
+ ===============
5
+
6
+ The arbiter's single most load-bearing verdict is *may these two known trees run
7
+ concurrently?* — the thing that stops two agents writing the same region. Until
8
+ this seam, that verdict was a **hardcoded `1/3` prefix-ratio** (`lane_overlap.
9
+ overlap_verdict`) buried inside `admission.DisjointnessPredicate`. Every other
10
+ hackability axis (reasons, renderers, predicates, judges, …) is *open* — bring
11
+ your own implementation via data / an entry-point / a driver, never fork the
12
+ package — but the disjointness *scorer* was sealed. `docs/90 §1`/`§2` named this
13
+ exact scalar a deliberate research stand-in and specified the answer-shape; this
14
+ module is that answer (the full argument is `docs/113`).
15
+
16
+ The unit a policy rules on is two **known** trees (the unknown-blast-radius
17
+ empty-tree asymmetry stays in `DisjointnessPredicate` — it is a soundness
18
+ invariant, not a *scoring* choice, so a policy never sees it). A policy returns
19
+ the existing typed `lane_overlap.OverlapDecision`, so it is a true drop-in at the
20
+ one seam every collision check already routes through.
21
+
22
+ The soundness floor — structural, not trusted
23
+ ==============================================
24
+
25
+ This is the security-load-bearing core. A policy returns a verdict that *includes
26
+ admit*, so — unlike an `AdmissionPredicate`, which can only refuse — the type
27
+ alone no longer guarantees the safe direction. The guarantee is restored
28
+ **structurally** by a deterministic floor:
29
+
30
+ > A resolved `OverlapPolicy` may turn an ADMIT into a REFUSE. It may never turn
31
+ > a REFUSE into an ADMIT relative to the unforgeable prefix floor.
32
+
33
+ `admissible_under_floor` computes the deterministic prefix-disjointness floor
34
+ (`PrefixOverlapPolicy` — pure path algebra, no provider, no I/O) AND the resolved
35
+ policy's verdict, and admits only when **both** admit:
36
+
37
+ admit ⟺ floor.admissible AND policy.admissible
38
+
39
+ So a policy that admits a pair the prefix floor refuses is **structurally
40
+ unable** to produce an admit (the floor is ANDed in, and the floor is not the
41
+ plugin's to compute). The worst a buggy/hostile policy can do is *refuse* pairs
42
+ the floor would admit — a visible, safe-direction loss of concurrency, never a
43
+ collision. A policy that raises / returns the wrong type degrades to the floor
44
+ verdict alone (fail-closed toward the prefix rule — i.e. to *today's* behavior).
45
+
46
+ This is the admission analogue of the judge seam's fail-to-ABSTAIN and the
47
+ predicate seam's conjunctive-only, and the `docs/76` design law applied to
48
+ admission: a researcher changes *what counts as overlap* (the signal), never
49
+ *which way the verdict fails* (the adjudication).
50
+
51
+ Purity & layering
52
+ =================
53
+
54
+ Pure stdlib + the kernel leaves it delegates to (`lane_overlap`) — a Protocol,
55
+ a built-in prefix policy, a resolver, and the floor-AND helper. No host names, no
56
+ I/O inside a verdict. So it sits in the kernel layer beside `admission` (which
57
+ likewise holds a pure protocol + resolver while real *implementations* live
58
+ outside). A policy MAY do I/O *inside* `overlaps` (call a model, read an import
59
+ graph) iff it lives in a driver — the JUDGE-rung allowance — but the kernel's own
60
+ `PrefixOverlapPolicy` does not. Entry-point discovery (the one bit of I/O) happens
61
+ at the call boundary in `active_overlap_policy`, exactly as `active_judges` /
62
+ `active_predicates` / renderer discovery do.
63
+ """
64
+
65
+ from __future__ import annotations
66
+
67
+ import sys
68
+ from typing import Protocol, runtime_checkable
69
+
70
+ from dos.lane_overlap import OVERLAP_RATIO_MAX, OverlapDecision, overlap_verdict
71
+
72
+
73
+ @runtime_checkable
74
+ class OverlapPolicy(Protocol):
75
+ """The contract a researcher implements to swap the disjointness *scorer*.
76
+
77
+ ``name`` is the token `dos overlap-eval --policy <name>` selects and
78
+ `dos doctor` lists. ``overlaps`` is handed two **known** (non-empty) trees +
79
+ the active ``config`` (read-only — it reads policy, e.g. a declared
80
+ ``ratio_max``, but the type gives it nothing to mutate) and returns an
81
+ `OverlapDecision` (the existing `lane_overlap` type).
82
+
83
+ A policy MAY do I/O *inside* ``overlaps`` (call a model, shell out, read an
84
+ import graph) — unlike a predicate or a renderer, which are pure — IFF it
85
+ lives in a driver, the same reason a ruling judge does. The discipline that
86
+ keeps it honest is NOT purity; it is the deterministic floor
87
+ (`admissible_under_floor`): whatever a policy returns, admission is the AND of
88
+ its admit and the kernel's own unforgeable prefix admit, so a policy is
89
+ structurally unable to admit a pair the prefix floor refuses.
90
+ """
91
+
92
+ name: str
93
+
94
+ def overlaps(
95
+ self, requested_tree: list[str], lease_tree: list[str], config: object,
96
+ ) -> OverlapDecision:
97
+ ...
98
+
99
+
100
+ class PrefixOverlapPolicy:
101
+ """The built-in, always-available policy: today's `1/3` prefix-ratio scorer.
102
+
103
+ A verbatim wrap of `lane_overlap.overlap_verdict`, threading the workspace's
104
+ declared ``ratio_max`` (``config.overlap_ratio_max`` — `dos.toml [overlap]`,
105
+ defaulting to ⅓). With no `[overlap]` table and no plugin, the resolver
106
+ returns THIS policy and behavior is **byte-for-byte identical to before the
107
+ seam** — the load-bearing litmus (the entire existing arbiter/overlap suite
108
+ stays green through `DisjointnessPredicate` → this policy).
109
+
110
+ It is also the **deterministic prefix floor** every other policy is ANDed
111
+ against (`admissible_under_floor`): it is pure path algebra, forgery-proof,
112
+ and the unshadowable lower bound a plugin can never displace (`resolve_overlap_
113
+ policy` resolves built-ins first) — the overlap analogue of the unshadowable
114
+ `text` renderer / `abstain` judge. The floor it computes uses the kernel's
115
+ own ⅓ default (NOT the workspace's possibly-looser declared ratio): the floor
116
+ is the conservative bound, so loosening `ratio_max` in data can only widen the
117
+ *policy's* admit set, never the floor it is checked against. See
118
+ `admissible_under_floor`.
119
+ """
120
+
121
+ name = "prefix"
122
+
123
+ def overlaps(
124
+ self, requested_tree: list[str], lease_tree: list[str], config: object,
125
+ ) -> OverlapDecision:
126
+ ratio_max = _ratio_max_from_config(config)
127
+ return overlap_verdict(requested_tree, lease_tree, ratio_max=ratio_max)
128
+
129
+
130
+ def _ratio_max_from_config(config: object) -> float:
131
+ """The soft-overlap tolerance to use, read off the config (data seam).
132
+
133
+ Reads ``config.overlap_ratio_max`` when present and a sane float in (0, 1];
134
+ otherwise the kernel default ⅓. Defensive on purpose — a hand-built test
135
+ config, a `None` config, or a malformed value all fall back to the default
136
+ rather than crashing the admission hot path (the warn-and-fall-back posture
137
+ every config axis takes). A non-positive or >1 value is ignored (a ratio
138
+ outside (0, 1] is meaningless for a shared/requested fraction)."""
139
+ raw = getattr(config, "overlap_ratio_max", None)
140
+ if raw is None:
141
+ return OVERLAP_RATIO_MAX
142
+ try:
143
+ val = float(raw)
144
+ except (TypeError, ValueError):
145
+ return OVERLAP_RATIO_MAX
146
+ if not (0.0 < val <= 1.0):
147
+ return OVERLAP_RATIO_MAX
148
+ return val
149
+
150
+
151
+ # The unforgeable floor instance — pure, stateless, reused.
152
+ _FLOOR = PrefixOverlapPolicy()
153
+
154
+
155
+ def floor_decision(requested_tree: list[str], lease_tree: list[str]) -> OverlapDecision:
156
+ """The deterministic prefix-disjointness floor verdict for two known trees.
157
+
158
+ Computed with the **kernel default** ⅓ tolerance, NOT a workspace-declared
159
+ one — the floor is the conservative bound a policy is checked against, so it
160
+ must not itself be loosened by `dos.toml [overlap]`.
161
+
162
+ The load-bearing consequence — net admission is ``policy ∧ floor``, so with the
163
+ floor fixed at ⅓:
164
+
165
+ * **Tightening** ``[overlap] ratio_max`` below ⅓ *works*: the policy is the
166
+ stricter voice, the floor never interferes, net admit = the tighter ratio.
167
+ * **Loosening** ``[overlap] ratio_max`` above ⅓ is *capped at ⅓*: the policy
168
+ would admit up to the looser ratio, but the floor re-refuses anything past
169
+ ⅓, so net admit stays ⅓. This is DELIBERATE — ⅓ is a fixed SAFETY CEILING
170
+ an operator cannot raise with a config line (loosening is the dangerous
171
+ direction for false-admits, `docs/90 §2`). To genuinely admit a pair the
172
+ prefix rule refuses you need a *stricter sound floor* (the glob-intersection
173
+ floor, `docs/113 §3.1`), not a looser scalar — soundness is not a knob.
174
+
175
+ So the data knob tunes admission *downward* freely and is bounded *upward* by
176
+ the kernel floor — the same asymmetry the rest of the seam has (a swappable
177
+ scorer can only refuse-more)."""
178
+ return overlap_verdict(requested_tree, lease_tree, ratio_max=OVERLAP_RATIO_MAX)
179
+
180
+
181
+ def admissible_under_floor(
182
+ policy: OverlapPolicy,
183
+ requested_tree: list[str],
184
+ lease_tree: list[str],
185
+ config: object,
186
+ ) -> OverlapDecision:
187
+ """Run ``policy`` for two known trees, AND-ed under the deterministic floor.
188
+
189
+ The structural soundness guarantee in one function. Returns an
190
+ `OverlapDecision` whose ``admissible`` is::
191
+
192
+ floor.admissible AND policy.admissible
193
+
194
+ so a policy can only ever move the verdict toward REFUSE relative to the
195
+ prefix floor:
196
+
197
+ * floor REFUSE → the floor verdict is returned regardless of the policy
198
+ (the dangerous cell — admitting a prefix-colliding pair — is unreachable;
199
+ the policy is not even able to express an admit that survives the AND).
200
+ * floor ADMIT, policy ADMIT → the policy verdict (it may carry a richer
201
+ reason; both agree it is safe).
202
+ * floor ADMIT, policy REFUSE → the policy's REFUSE (a stricter scorer caught
203
+ an overlap the prefix rule missed — the safe, more-refusing direction).
204
+ * policy raises / returns a non-`OverlapDecision` → the floor verdict alone
205
+ (fail-closed toward the prefix rule — today's behavior, never looser).
206
+
207
+ This is the ONE place `DisjointnessPredicate` consults a policy; it is what
208
+ makes "a swappable scorer can never admit a collision" a property of the
209
+ *shape of the computation*, not a property of the plugin behaving."""
210
+ floor = floor_decision(requested_tree, lease_tree)
211
+ # Floor refuses → no policy can admit. Return the floor verdict verbatim so the
212
+ # operator sees the unforgeable reason (and a hostile policy cannot even dilute
213
+ # the *message*, let alone the verdict).
214
+ if not floor.admissible:
215
+ return floor
216
+ # Floor admits → consult the policy, fail-closed to the floor on any misbehavior.
217
+ name = getattr(policy, "name", type(policy).__name__)
218
+ try:
219
+ decision = policy.overlaps(list(requested_tree), list(lease_tree), config)
220
+ except Exception as e: # fail-closed: a policy that raises falls back to the floor
221
+ return OverlapDecision(
222
+ floor.verdict, floor.shared, floor.requested,
223
+ (f"overlap policy {name!r} raised ({e!r}) — using the deterministic "
224
+ f"prefix floor verdict ({floor.reason})."),
225
+ )
226
+ if not isinstance(decision, OverlapDecision):
227
+ # A policy that does not return our type cannot be trusted to admit; we
228
+ # never read a foreign object's `.admissible`, so no admit leaks through a
229
+ # wrong return type. Fall back to the floor.
230
+ return OverlapDecision(
231
+ floor.verdict, floor.shared, floor.requested,
232
+ (f"overlap policy {name!r} returned a {type(decision).__name__}, not "
233
+ f"an OverlapDecision — using the deterministic prefix floor verdict "
234
+ f"({floor.reason})."),
235
+ )
236
+ if decision.admissible:
237
+ # Both floor and policy admit. Return the policy's (possibly richer) verdict.
238
+ return decision
239
+ # Floor admits but policy refuses — the stricter scorer wins (refuse-more is
240
+ # the safe direction). Surface the policy's reason.
241
+ return decision
242
+
243
+
244
+ # ---------------------------------------------------------------------------
245
+ # Resolution — built-in first, then the `dos.overlap_policies` entry-point group.
246
+ # ---------------------------------------------------------------------------
247
+
248
+ # The entry-point group a workspace/researcher registers an overlap policy under.
249
+ OVERLAP_POLICY_ENTRY_POINT_GROUP = "dos.overlap_policies"
250
+
251
+ # The built-in policies, resolvable by name and UNSHADOWABLE by a plugin (a plugin
252
+ # registering `prefix` cannot displace this one — built-ins resolve first). Only
253
+ # the deterministic prefix scorer ships in the kernel; a model-backed or
254
+ # import-graph policy lives in a driver/plugin.
255
+ _BUILT_IN_POLICIES: dict[str, type] = {
256
+ PrefixOverlapPolicy.name: PrefixOverlapPolicy,
257
+ }
258
+
259
+
260
+ def _discover_entry_point_policies(*, _stderr=None) -> list[tuple[str, OverlapPolicy]]:
261
+ """Find overlap policies registered under the `dos.overlap_policies` group.
262
+
263
+ A policy plugin registers ``name = "pkg.module:PolicyClass"`` in its
264
+ ``[project.entry-points."dos.overlap_policies"]``. We load each, instantiate
265
+ it if it is a class, and return ``(entry_point_name, policy)`` pairs sorted by
266
+ name (stable, so `dos doctor` order is deterministic). A plugin that fails to
267
+ load is skipped with a one-line stderr note rather than crashing arbitration —
268
+ the same posture `_discover_entry_point_judges` / predicate discovery take."""
269
+ stderr = _stderr if _stderr is not None else sys.stderr
270
+ out: list[tuple[str, OverlapPolicy]] = []
271
+ try:
272
+ from importlib.metadata import entry_points
273
+ except Exception: # pragma: no cover - importlib.metadata always present py3.11+
274
+ return out
275
+ try:
276
+ eps = entry_points(group=OVERLAP_POLICY_ENTRY_POINT_GROUP)
277
+ except TypeError: # pragma: no cover - py<3.10 selectable-API fallback
278
+ eps = entry_points().get(OVERLAP_POLICY_ENTRY_POINT_GROUP, []) # type: ignore[attr-defined]
279
+ except Exception: # pragma: no cover - defensive: never let discovery crash a call
280
+ return out
281
+ for ep in sorted(eps, key=lambda e: e.name):
282
+ try:
283
+ obj = ep.load()
284
+ policy = obj() if isinstance(obj, type) else obj
285
+ except Exception as e: # pragma: no cover - depends on third-party plugin
286
+ print(
287
+ f"warning: overlap policy plugin {ep.name!r} failed to load ({e}); "
288
+ f"skipping",
289
+ file=stderr,
290
+ )
291
+ continue
292
+ out.append((ep.name, policy))
293
+ return out
294
+
295
+
296
+ def resolve_overlap_policy(name: str, *, _stderr=None) -> OverlapPolicy:
297
+ """Resolve an overlap policy by name: built-ins first, then plugins.
298
+
299
+ Built-ins (`prefix`) resolve FIRST and cannot be shadowed by a plugin of the
300
+ same name — the trusted-floor guarantee, identical to `resolve_judge` /
301
+ `resolve_renderer`. An unknown name fails LOUD with the known list (it never
302
+ silently degrades to `prefix`, which would hide a typo'd `--policy`): the
303
+ caller asked for a specific scorer and getting a different one silently is
304
+ exactly the unannounced substitution the kernel refuses."""
305
+ if name in _BUILT_IN_POLICIES:
306
+ return _BUILT_IN_POLICIES[name]()
307
+ discovered = dict(_discover_entry_point_policies(_stderr=_stderr))
308
+ if name in discovered:
309
+ return discovered[name]
310
+ known = sorted(set(_BUILT_IN_POLICIES) | set(discovered))
311
+ raise ValueError(
312
+ f"unknown overlap policy {name!r}; known: {', '.join(known)}"
313
+ )
314
+
315
+
316
+ def active_overlap_policy(*, config: object = None, _stderr=None) -> OverlapPolicy:
317
+ """The overlap policy a CALLER threads into the disjointness check.
318
+
319
+ Resolution: a workspace may name its policy in ``config.overlap_policy_name``
320
+ (the `dos.toml [overlap] policy` data field); absent that, the built-in
321
+ `prefix` floor scorer. Does ENTRY-POINT DISCOVERY (I/O) when a non-`prefix`
322
+ name is configured, so it is a CALL-BOUNDARY helper (the CLI's `cmd_arbitrate`,
323
+ `dos doctor`), never called inside the pure `arbitrate`. The pure default —
324
+ no config, or `prefix` — returns the built-in with no discovery, so the hot
325
+ path stays I/O-free, exactly as `built_in_predicates` does.
326
+
327
+ Like the predicate seam, the kernel's pure `arbitrate` never calls this; the
328
+ boundary resolves the policy and passes it in (or the built-in `prefix` floor
329
+ is used). So `arbitrate`'s default path is byte-identical to before the seam."""
330
+ name = getattr(config, "overlap_policy_name", None) if config is not None else None
331
+ if not name or name == PrefixOverlapPolicy.name:
332
+ return PrefixOverlapPolicy()
333
+ return resolve_overlap_policy(str(name), _stderr=_stderr)
334
+
335
+
336
+ def active_overlap_policy_names(*, _stderr=None) -> list[str]:
337
+ """The names of every resolvable overlap policy (built-in + discovered) — what
338
+ `dos doctor` lists so an operator can see which scorers the arbiter could use
339
+ (the overlap analogue of "see the active predicates / judges")."""
340
+ built = list(_BUILT_IN_POLICIES)
341
+ discovered = [n for n, _p in _discover_entry_point_policies(_stderr=_stderr)]
342
+ return built + discovered