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/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
|