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/reasons.py
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
"""The block-reason registry — the closed refusal vocabulary, *as data*.
|
|
2
|
+
|
|
3
|
+
This is the hackability seam for the kernel's single most-important syscall
|
|
4
|
+
(dispatch-os-vision §6 ranks structured refusal first): the closed
|
|
5
|
+
`reason_class` set a no-pick / blocked verdict may carry. Before this module the
|
|
6
|
+
set was a hardcoded enum in `dos.wedge_reason`; adding a reason meant editing the
|
|
7
|
+
package. That is the same mechanism/policy coupling the lane taxonomy already
|
|
8
|
+
broke — `LaneTaxonomy` lifted the job repo's hardcoded `_CLUSTERS` constants into
|
|
9
|
+
per-workspace `SubstrateConfig` data so "the arbiter never mentions a domain lane
|
|
10
|
+
name." This module does the same for reasons: the *mechanism* (emit / verify /
|
|
11
|
+
refuse / man, all keyed on a reason's fields) lives here; the *set of reasons* is
|
|
12
|
+
per-workspace data a host declares.
|
|
13
|
+
|
|
14
|
+
Why a registry and not a runtime-mutable enum
|
|
15
|
+
==============================================
|
|
16
|
+
|
|
17
|
+
The load-bearing invariant the kernel exists to protect is that every reason is
|
|
18
|
+
**simultaneously emittable, verifiable, and refusable** — the lockstep the
|
|
19
|
+
`wedge_reason` ↔ `picker_oracle` test pins, and the completeness rail DOM's
|
|
20
|
+
`man --check` wants ("no runtime name without a definition"). A monkeypatched
|
|
21
|
+
enum would silently re-open the `UNCLASSIFIED` prose-drift the kernel was built
|
|
22
|
+
to close. So hackability is NOT "mutate the enum at runtime"; it is "**declare
|
|
23
|
+
your closed set once, as data, and let every consumer derive from that single
|
|
24
|
+
declaration.**" A `ReasonRegistry` is exactly one such declaration: closed (you
|
|
25
|
+
can enumerate it), verifiable (every entry carries its category + refusal-ness +
|
|
26
|
+
fix), and projectable (the `man` renderer reads these fields directly).
|
|
27
|
+
|
|
28
|
+
The shape
|
|
29
|
+
=========
|
|
30
|
+
|
|
31
|
+
* `ReasonSpec` — one reason as data: its token, the coarse `category` it rolls
|
|
32
|
+
up to (the `picker_oracle.NoPickCause` value string), whether carrying it
|
|
33
|
+
means *refuse* (route to /replan) vs *advisory*, and the curated `fix` /
|
|
34
|
+
`see_also` text the man-page projects. Co-locating `fix`/`see_also` with the
|
|
35
|
+
token (rather than in a separate doc) is DOM Design-rule 1: the one bit of
|
|
36
|
+
curated prose lives beside the symbol so it cannot drift away from it.
|
|
37
|
+
* `ReasonRegistry` — a closed, ordered set of `ReasonSpec`s with lookup +
|
|
38
|
+
membership + category-map + refusal-set projections. Immutable once built
|
|
39
|
+
(`extend()` returns a NEW registry — you compose, you don't mutate), so the
|
|
40
|
+
"closed set" property holds: a process's active registry is a value, not a
|
|
41
|
+
mutable global a plugin can scribble on mid-run.
|
|
42
|
+
|
|
43
|
+
`BASE_REASONS` is the built-in registry — the seven reasons the job spine shipped
|
|
44
|
+
as a closed enum, reproduced verbatim so `dos.wedge_reason` stays byte-compatible
|
|
45
|
+
and the existing lockstep test passes unchanged. A workspace that wants its own
|
|
46
|
+
reasons calls `BASE_REASONS.extend([...])` (or declares them in `dos.toml`, which
|
|
47
|
+
the loader turns into the same `extend` call) and installs the result on its
|
|
48
|
+
`SubstrateConfig`.
|
|
49
|
+
|
|
50
|
+
Pure stdlib — no third-party imports, no I/O — so `wedge_reason` / `picker_oracle`
|
|
51
|
+
/ the man renderer can all import it as a leaf, exactly as they import the old
|
|
52
|
+
`wedge_reason` enum.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
from __future__ import annotations
|
|
56
|
+
|
|
57
|
+
from dataclasses import dataclass
|
|
58
|
+
from pathlib import Path
|
|
59
|
+
from typing import Iterable
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# The coarse categories a reason rolls up to. These string values MUST be members
|
|
63
|
+
# of `picker_oracle.NoPickCause` (the oracle maps a reason onto its verification
|
|
64
|
+
# branch by this string) — the lockstep the refusal-plane test pins. Kept as bare
|
|
65
|
+
# strings here, not an import of `NoPickCause`, so this module stays a leaf with
|
|
66
|
+
# zero `dos`-internal deps (the same circular-import dodge `wedge_reason` used).
|
|
67
|
+
KNOWN_CATEGORIES: frozenset[str] = frozenset({
|
|
68
|
+
"TRUE_DRAIN",
|
|
69
|
+
"OPERATOR_GATE",
|
|
70
|
+
"STALE_CLAIM",
|
|
71
|
+
"MISROUTE",
|
|
72
|
+
"UNCLASSIFIED",
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
# The category an unknown / undeclared token classifies as. A token observed in
|
|
76
|
+
# the wild that is NOT in the active registry surfaces as this — the drift signal
|
|
77
|
+
# the `--check` rail turns into a CI failure (it is a bug to add, not tolerate).
|
|
78
|
+
UNCLASSIFIED = "UNCLASSIFIED"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass(frozen=True)
|
|
82
|
+
class ReasonSpec:
|
|
83
|
+
"""One block reason, as data. The unit a workspace declares to add a reason.
|
|
84
|
+
|
|
85
|
+
Fields:
|
|
86
|
+
token — the `reason_class` string a no-pick / blocked verdict carries
|
|
87
|
+
(canonical UPPER_SNAKE; the registry normalizes case on lookup).
|
|
88
|
+
category — the coarse `picker_oracle.NoPickCause` value this rolls up to
|
|
89
|
+
(must be in `KNOWN_CATEGORIES`; the registry validates).
|
|
90
|
+
refusal — True ⇒ a verdict carrying this token must NOT be rendered
|
|
91
|
+
(route to /replan); False ⇒ advisory-only (deferred-but-valid).
|
|
92
|
+
Defaults True: a no-pick reason is a refusal unless declared
|
|
93
|
+
otherwise, matching today's "all reasons refuse" behavior.
|
|
94
|
+
fix — one-line operator-facing remedy sketch (the man-page TYPICAL FIX
|
|
95
|
+
line). Curated text, co-located with the token by design.
|
|
96
|
+
see_also — man-page SEE ALSO pointers (other reasons / lanes / oracles).
|
|
97
|
+
summary — one-line gloss of what the reason MEANS (the man-page NAME line
|
|
98
|
+
continuation). Optional; falls back to the token.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
token: str
|
|
102
|
+
category: str
|
|
103
|
+
refusal: bool = True
|
|
104
|
+
fix: str = ""
|
|
105
|
+
see_also: tuple[str, ...] = ()
|
|
106
|
+
summary: str = ""
|
|
107
|
+
|
|
108
|
+
def __post_init__(self) -> None:
|
|
109
|
+
if not self.token or not self.token.strip():
|
|
110
|
+
raise ValueError("ReasonSpec.token must be a non-empty string")
|
|
111
|
+
if self.category not in KNOWN_CATEGORIES:
|
|
112
|
+
raise ValueError(
|
|
113
|
+
f"ReasonSpec {self.token!r} has category {self.category!r}, "
|
|
114
|
+
f"which is not a known NoPickCause value {sorted(KNOWN_CATEGORIES)}. "
|
|
115
|
+
f"A reason must roll up to a category the oracle can verify against."
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def key(self) -> str:
|
|
120
|
+
"""The normalized lookup key (UPPER, stripped) — what `coerce` matches."""
|
|
121
|
+
return self.token.strip().upper()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass(frozen=True)
|
|
125
|
+
class ReasonRegistry:
|
|
126
|
+
"""A closed, ordered set of `ReasonSpec`s — the active refusal vocabulary.
|
|
127
|
+
|
|
128
|
+
Immutable: `extend()` returns a NEW registry. A process's active registry is
|
|
129
|
+
therefore a value (installed on the `SubstrateConfig`), never a mutable global
|
|
130
|
+
a plugin scribbles on — which is what keeps "closed set" a real property and
|
|
131
|
+
not a hope. Lookup is case-insensitive and whitespace-tolerant (a hand-authored
|
|
132
|
+
envelope written during a prose→data transition still classifies).
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
specs: tuple[ReasonSpec, ...] = ()
|
|
136
|
+
|
|
137
|
+
def __post_init__(self) -> None:
|
|
138
|
+
seen: set[str] = set()
|
|
139
|
+
for s in self.specs:
|
|
140
|
+
if s.key in seen:
|
|
141
|
+
raise ValueError(
|
|
142
|
+
f"duplicate reason token {s.token!r} in registry — a reason "
|
|
143
|
+
f"is declared exactly once (later declarations would shadow "
|
|
144
|
+
f"silently, the drift this registry exists to forbid)"
|
|
145
|
+
)
|
|
146
|
+
seen.add(s.key)
|
|
147
|
+
|
|
148
|
+
# -- lookup ------------------------------------------------------------
|
|
149
|
+
def get(self, token: str | None) -> ReasonSpec | None:
|
|
150
|
+
"""The `ReasonSpec` for `token`, or None if not a member of this set."""
|
|
151
|
+
if not token:
|
|
152
|
+
return None
|
|
153
|
+
k = token.strip().upper()
|
|
154
|
+
for s in self.specs:
|
|
155
|
+
if s.key == k:
|
|
156
|
+
return s
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
def is_known(self, token: str | None) -> bool:
|
|
160
|
+
return self.get(token) is not None
|
|
161
|
+
|
|
162
|
+
def tokens(self) -> tuple[str, ...]:
|
|
163
|
+
"""Every declared token, in declaration order."""
|
|
164
|
+
return tuple(s.key for s in self.specs)
|
|
165
|
+
|
|
166
|
+
# -- projections (what the consumers read) -----------------------------
|
|
167
|
+
def category_for(self, token: str | None) -> str:
|
|
168
|
+
"""Map a token onto its category value; UNCLASSIFIED for an unknown one.
|
|
169
|
+
|
|
170
|
+
Forward-compatible by construction: a brand-new label does not crash a
|
|
171
|
+
consumer, it classifies as drift until declared (the `--check` rail is
|
|
172
|
+
what turns that drift into a loud CI failure rather than a silent one).
|
|
173
|
+
"""
|
|
174
|
+
spec = self.get(token)
|
|
175
|
+
return spec.category if spec is not None else UNCLASSIFIED
|
|
176
|
+
|
|
177
|
+
def is_refusal(self, token: str | None) -> bool:
|
|
178
|
+
"""True iff a verdict carrying `token` must NOT be rendered.
|
|
179
|
+
|
|
180
|
+
An unknown token is refused conservatively — a no-pick envelope with an
|
|
181
|
+
unrecognised reason_class is still a no-pick, and launching against it is
|
|
182
|
+
the exact hazard. A *known* token honors its declared `refusal` flag, so a
|
|
183
|
+
workspace can declare an advisory-only reason (refusal=False).
|
|
184
|
+
"""
|
|
185
|
+
spec = self.get(token)
|
|
186
|
+
if spec is None:
|
|
187
|
+
return True
|
|
188
|
+
return spec.refusal
|
|
189
|
+
|
|
190
|
+
def category_map(self) -> dict[str, str]:
|
|
191
|
+
"""`{token: category}` for every declared reason. The dict `picker_oracle`
|
|
192
|
+
derives its `REASON_CLASS_MAP` from, so a declared reason is verifiable the
|
|
193
|
+
moment it is emittable (no second map to keep in sync)."""
|
|
194
|
+
return {s.key: s.category for s in self.specs}
|
|
195
|
+
|
|
196
|
+
def refusal_tokens(self) -> frozenset[str]:
|
|
197
|
+
"""The subset of tokens whose verdicts must route to /replan."""
|
|
198
|
+
return frozenset(s.key for s in self.specs if s.refusal)
|
|
199
|
+
|
|
200
|
+
# -- composition (the hackability verb) --------------------------------
|
|
201
|
+
def extend(self, more: Iterable[ReasonSpec]) -> "ReasonRegistry":
|
|
202
|
+
"""Return a NEW registry with `more` appended. The one way to add reasons.
|
|
203
|
+
|
|
204
|
+
Raises if any new token collides with an existing one (the same
|
|
205
|
+
declared-exactly-once guard `__post_init__` enforces) — a workspace
|
|
206
|
+
re-declaring a built-in is a mistake to surface, not to silently honor.
|
|
207
|
+
To *change* a built-in (e.g. flip its refusal flag), build a fresh
|
|
208
|
+
registry from the specs you want rather than extend-over-shadow.
|
|
209
|
+
"""
|
|
210
|
+
return ReasonRegistry(specs=tuple(self.specs) + tuple(more))
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# ---------------------------------------------------------------------------
|
|
214
|
+
# The built-in registry — the seven reasons the job spine shipped as a closed
|
|
215
|
+
# enum (`dos.wedge_reason.WedgeReason`), reproduced verbatim (token, category,
|
|
216
|
+
# refusal-ness) so `wedge_reason` stays byte-compatible and the lockstep test
|
|
217
|
+
# passes unchanged. The `summary`/`fix`/`see_also` text is lifted from the enum's
|
|
218
|
+
# own comment blocks — the man-page content the DOM plan notes "already exists as
|
|
219
|
+
# structured symbols," now co-located as fields. A foreign workspace ignores this
|
|
220
|
+
# and builds its own (or `extend`s it).
|
|
221
|
+
#
|
|
222
|
+
# Categories mirror `picker_oracle.NoPickCause` values exactly:
|
|
223
|
+
# LANE_DRAINED -> TRUE_DRAIN
|
|
224
|
+
# LANE_BLOCKED_ON_SOAK_GATED_PHASES -> OPERATOR_GATE
|
|
225
|
+
# LANE_LEASE_HELD_BY_LIVE_DISPATCH_LOOP -> OPERATOR_GATE (LEASE_HELD alias)
|
|
226
|
+
# LANE_ALL_INFLIGHT_OR_DEFERRED -> STALE_CLAIM (INFLIGHT alias)
|
|
227
|
+
# LANE_ALL_SHIPPED_INFLIGHT_OR_STALE_STAMP -> STALE_CLAIM (INFLIGHT alias)
|
|
228
|
+
# LANE_ALL_BLOCKED_OR_STALE_STAMP -> OPERATOR_GATE
|
|
229
|
+
# LANE_BLOCKED_ON_OPERATOR_DECISION -> OPERATOR_GATE
|
|
230
|
+
# ---------------------------------------------------------------------------
|
|
231
|
+
BASE_REASONS = ReasonRegistry(specs=(
|
|
232
|
+
ReasonSpec(
|
|
233
|
+
token="LANE_DRAINED",
|
|
234
|
+
category="TRUE_DRAIN",
|
|
235
|
+
refusal=True,
|
|
236
|
+
summary="0 plans + 0 findings — the lane is genuinely drained.",
|
|
237
|
+
fix="Nothing to do — the backlog is empty. /replan to refill if you expect work.",
|
|
238
|
+
see_also=("oracle picker_oracle",),
|
|
239
|
+
),
|
|
240
|
+
ReasonSpec(
|
|
241
|
+
token="LANE_BLOCKED_ON_SOAK_GATED_PHASES",
|
|
242
|
+
category="OPERATOR_GATE",
|
|
243
|
+
refusal=True,
|
|
244
|
+
summary="Lane has pickable phases but all remaining gate on an open soak window.",
|
|
245
|
+
fix="Wait for the soak window to close, or /replan to re-shape the lane.",
|
|
246
|
+
see_also=("meta gates_on_soak", "meta soak_until", "oracle picker_oracle"),
|
|
247
|
+
),
|
|
248
|
+
ReasonSpec(
|
|
249
|
+
token="LANE_LEASE_HELD_BY_LIVE_DISPATCH_LOOP",
|
|
250
|
+
category="OPERATOR_GATE",
|
|
251
|
+
refusal=True,
|
|
252
|
+
summary="A foreign, live /dispatch-loop holds this cluster's lane lease.",
|
|
253
|
+
fix="Wait for the holding loop to release the lease (racing it is the "
|
|
254
|
+
"collision the arbiter prevents). The reason string carries the holder.",
|
|
255
|
+
see_also=("lane <holder>", "oracle picker_oracle"),
|
|
256
|
+
),
|
|
257
|
+
ReasonSpec(
|
|
258
|
+
token="LANE_ALL_INFLIGHT_OR_DEFERRED",
|
|
259
|
+
category="STALE_CLAIM",
|
|
260
|
+
refusal=True,
|
|
261
|
+
summary="Remaining phases are all soft-claimed in-flight by a sibling, "
|
|
262
|
+
"and/or deferred by the plan body's own gate.",
|
|
263
|
+
fix="Wait for the sibling packet to ship/release, or /replan to re-rank.",
|
|
264
|
+
see_also=("oracle picker_oracle",),
|
|
265
|
+
),
|
|
266
|
+
ReasonSpec(
|
|
267
|
+
token="LANE_ALL_SHIPPED_INFLIGHT_OR_STALE_STAMP",
|
|
268
|
+
category="STALE_CLAIM",
|
|
269
|
+
refusal=True,
|
|
270
|
+
summary="Remaining phases are a mix of shipped-but-unstamped + in-flight "
|
|
271
|
+
"+ stale-stamped (the apply/tailor 'already done or drifting' shape).",
|
|
272
|
+
fix="/replan to reconcile the stale SHIPPED stamps, then re-dispatch.",
|
|
273
|
+
see_also=("oracle ship_oracle",),
|
|
274
|
+
),
|
|
275
|
+
ReasonSpec(
|
|
276
|
+
token="LANE_ALL_BLOCKED_OR_STALE_STAMP",
|
|
277
|
+
category="OPERATOR_GATE",
|
|
278
|
+
refusal=True,
|
|
279
|
+
summary="Every remaining phase is blocked, or its stamp is drifted "
|
|
280
|
+
"(soak + stamp-drift co-occur).",
|
|
281
|
+
fix="/replan to reconcile stamps and surface the blocks.",
|
|
282
|
+
see_also=("oracle ship_oracle",),
|
|
283
|
+
),
|
|
284
|
+
ReasonSpec(
|
|
285
|
+
token="LANE_BLOCKED_ON_OPERATOR_DECISION",
|
|
286
|
+
category="OPERATOR_GATE",
|
|
287
|
+
refusal=True,
|
|
288
|
+
summary="Lane is blocked on an unanswered operator decision; the routing "
|
|
289
|
+
"finding is already soft-claimed by a sibling. No automation clears it.",
|
|
290
|
+
fix="Answer the open decision (it surfaces once), then /replan.",
|
|
291
|
+
see_also=("oracle picker_oracle",),
|
|
292
|
+
),
|
|
293
|
+
# ADM Phase 2 — the typed refuse the SELF_MODIFY admission predicate emits
|
|
294
|
+
# (`dos.self_modify.SelfModifyPredicate`). A lease whose tree includes the
|
|
295
|
+
# orchestrator's own running code (`arbiter.py`, the classifiers, the reason
|
|
296
|
+
# vocabulary, the config seam) is a misrouted lease — work aimed at the kernel
|
|
297
|
+
# adjudicating it rather than at userland — so it rolls up to MISROUTE. Refusal
|
|
298
|
+
# (route the operator to /replan or to --force if the kernel edit is deliberate).
|
|
299
|
+
# Declared here so the arbiter-emitted reason is simultaneously emittable,
|
|
300
|
+
# verifiable (`category_for`), refusable (`is_refusal`), and `dos man wedge
|
|
301
|
+
# SELF_MODIFY`-documented — the Axis-1 completeness rail the predicate rides.
|
|
302
|
+
ReasonSpec(
|
|
303
|
+
token="SELF_MODIFY",
|
|
304
|
+
category="MISROUTE",
|
|
305
|
+
refusal=True,
|
|
306
|
+
summary="Lease tree includes the orchestrator's own running code — a live "
|
|
307
|
+
"loop must not rewrite the kernel that is adjudicating it.",
|
|
308
|
+
fix="Edit kernel runtime files OUTSIDE a live dispatch loop, or pass "
|
|
309
|
+
"--force to override (the operator's explicit 'I am deliberately "
|
|
310
|
+
"editing the kernel between loop runs').",
|
|
311
|
+
# The see_also points at the EXCLUSIVE-lane concept (the kernel's own
|
|
312
|
+
# region runs alone) via the `dos man lane` verb rather than a specific
|
|
313
|
+
# lane NAME — a generic registry must not name a lane a foreign workspace
|
|
314
|
+
# may not declare (the `dos man lane` ref resolves on every workspace; a
|
|
315
|
+
# bare `lane orchestration` dangled on any taxonomy without that lane,
|
|
316
|
+
# which `config_lint.REASON_SEE_ALSO_DANGLES` correctly flagged).
|
|
317
|
+
see_also=("dos man lane", "dos arbitrate"),
|
|
318
|
+
),
|
|
319
|
+
# docs/115 primitive 4 — the typed refuse that surfaces `durable_schema`'s
|
|
320
|
+
# refuse-don't-guess floor through the closed refusal vocabulary. When a reader
|
|
321
|
+
# meets a durable record (intent ledger, WAL, env-print) tagged at a NON-additive
|
|
322
|
+
# version this kernel predates (`durable_schema.classify` → UNREADABLE_NEWER), the
|
|
323
|
+
# sound answer is to REFUSE the record, never best-effort-parse a shape it does
|
|
324
|
+
# not know. A record this kernel cannot soundly read is work it must route
|
|
325
|
+
# elsewhere (a newer kernel / a migration), not guess at — so it rolls up to
|
|
326
|
+
# MISROUTE, the SELF_MODIFY sibling. Declared here so the floor is simultaneously
|
|
327
|
+
# emittable, verifiable (`category_for`), refusable (`is_refusal`), and
|
|
328
|
+
# `dos man wedge SCHEMA_UNREADABLE`-documented. The carried verdict
|
|
329
|
+
# (`ReadabilityVerdict`: family + understood-ceiling + record-version) is the
|
|
330
|
+
# remedy-with-the-refusal — the MCP `UnsupportedProtocolVersionError(-32004)`
|
|
331
|
+
# `{supported, requested}` shape, which DOS's durable_schema predates.
|
|
332
|
+
ReasonSpec(
|
|
333
|
+
token="SCHEMA_UNREADABLE",
|
|
334
|
+
category="MISROUTE",
|
|
335
|
+
refusal=True,
|
|
336
|
+
summary="A durable record is tagged at a schema version this kernel predates "
|
|
337
|
+
"— refuse-don't-guess (never best-effort-parse an unknown shape).",
|
|
338
|
+
fix="Upgrade the kernel to one that understands this record's schema "
|
|
339
|
+
"version, or run a migration. The refusal carries the family + the "
|
|
340
|
+
"version this kernel reads + the record's version (the supported set).",
|
|
341
|
+
see_also=("durable_schema", "resume", "dos man wedge SELF_MODIFY"),
|
|
342
|
+
),
|
|
343
|
+
# docs/104 §4 (control-flow arm) — the typed refuse the arbiter emits when an
|
|
344
|
+
# EXPLICIT keyword request names a lane the workspace's taxonomy never heard of
|
|
345
|
+
# and it resolves to no tree. Auto-pick's license is "the caller expressed NO
|
|
346
|
+
# preference"; a named keyword is a preference the kernel cannot place, so
|
|
347
|
+
# silently substituting a different free lane (the old degrade-to-bare) is the
|
|
348
|
+
# refuse-don't-guess violation turned inward — the lease would describe the
|
|
349
|
+
# wrong region and disjointness would guard the wrong tree. Work aimed at a lane
|
|
350
|
+
# that does not exist here is misrouted (vs SELF_MODIFY = misrouted to the
|
|
351
|
+
# kernel, SCHEMA_UNREADABLE = misrouted to a newer kernel) → MISROUTE. Declared
|
|
352
|
+
# here so the arbiter-emitted reason is simultaneously emittable, verifiable
|
|
353
|
+
# (`category_for`), refusable (`is_refusal`), and `dos man wedge UNKNOWN_LANE`-
|
|
354
|
+
# documented — the same completeness rail SELF_MODIFY/SCHEMA_UNREADABLE ride.
|
|
355
|
+
ReasonSpec(
|
|
356
|
+
token="UNKNOWN_LANE",
|
|
357
|
+
category="MISROUTE",
|
|
358
|
+
refusal=True,
|
|
359
|
+
summary="An explicit keyword request named a lane this workspace's taxonomy "
|
|
360
|
+
"does not contain — the kernel refuses to guess a substitute "
|
|
361
|
+
"(auto-pick only chooses when the caller expresses no preference).",
|
|
362
|
+
fix="Pass a lane the workspace knows as --scope (see the refusal's "
|
|
363
|
+
"known-lane list or `dos man lane`), run a bare invocation to auto-pick "
|
|
364
|
+
"any free lane, or register the lane in dos.toml.",
|
|
365
|
+
see_also=("lane", "dos arbitrate", "dos man wedge SELF_MODIFY"),
|
|
366
|
+
),
|
|
367
|
+
))
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
# ---------------------------------------------------------------------------
|
|
371
|
+
# The declarative on-ramp: read reasons out of a workspace's `dos.toml`.
|
|
372
|
+
#
|
|
373
|
+
# `dos init` scaffolds a `dos.toml`; this is the function that turns its
|
|
374
|
+
# `[reasons.*]` table into a `ReasonRegistry` extending `BASE_REASONS`. It is the
|
|
375
|
+
# chosen "no-code" path (operator decision): a host adds a block reason by editing
|
|
376
|
+
# data, not by importing the package. The TOML shape mirrors the dataclass:
|
|
377
|
+
#
|
|
378
|
+
# [reasons.LANE_PARKED_FOR_BUDGET]
|
|
379
|
+
# category = "OPERATOR_GATE" # required; must be a KNOWN_CATEGORIES value
|
|
380
|
+
# refusal = true # optional, default true
|
|
381
|
+
# summary = "lane parked: monthly token budget hit"
|
|
382
|
+
# fix = "raise the budget cap or /replan"
|
|
383
|
+
# see_also = ["meta budget", "oracle picker_oracle"]
|
|
384
|
+
#
|
|
385
|
+
# Behavioral hooks (custom renderers / admission predicates) are NOT declarable in
|
|
386
|
+
# TOML — those load via Python packaging entry_points (see docs/HACKING.md). TOML
|
|
387
|
+
# is for the data axes (reasons, and later lanes/paths); entry_points for code.
|
|
388
|
+
# ---------------------------------------------------------------------------
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def specs_from_table(table: dict) -> list[ReasonSpec]:
|
|
392
|
+
"""Turn a parsed `[reasons]` TOML table into a list of `ReasonSpec`.
|
|
393
|
+
|
|
394
|
+
`table` is `{token: {category, refusal?, summary?, fix?, see_also?}}` — the
|
|
395
|
+
shape `tomllib.load(...)["reasons"]` yields. Pure (no I/O); raises
|
|
396
|
+
`ValueError` (via `ReasonSpec.__post_init__`) on a bad category or empty
|
|
397
|
+
token, so a malformed declaration fails loudly at load instead of silently
|
|
398
|
+
classifying as drift later.
|
|
399
|
+
"""
|
|
400
|
+
specs: list[ReasonSpec] = []
|
|
401
|
+
for token, body in (table or {}).items():
|
|
402
|
+
if not isinstance(body, dict):
|
|
403
|
+
raise ValueError(
|
|
404
|
+
f"[reasons.{token}] must be a table, got {type(body).__name__}"
|
|
405
|
+
)
|
|
406
|
+
if "category" not in body:
|
|
407
|
+
raise ValueError(f"[reasons.{token}] is missing required `category`")
|
|
408
|
+
see = body.get("see_also") or ()
|
|
409
|
+
if isinstance(see, str):
|
|
410
|
+
see = (see,)
|
|
411
|
+
specs.append(ReasonSpec(
|
|
412
|
+
token=str(token),
|
|
413
|
+
category=str(body["category"]),
|
|
414
|
+
refusal=bool(body.get("refusal", True)),
|
|
415
|
+
fix=str(body.get("fix", "")),
|
|
416
|
+
see_also=tuple(str(s) for s in see),
|
|
417
|
+
summary=str(body.get("summary", "")),
|
|
418
|
+
))
|
|
419
|
+
return specs
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def load_from_toml(path: Path | str, *, base: ReasonRegistry = BASE_REASONS) -> ReasonRegistry:
|
|
423
|
+
"""Build a `ReasonRegistry` from a `dos.toml`'s `[reasons]` table.
|
|
424
|
+
|
|
425
|
+
Returns `base` unchanged when the file is absent, has no `[reasons]` table, or
|
|
426
|
+
`tomllib` is unavailable (Python < 3.11 with no `tomli`) — the declarative
|
|
427
|
+
path is purely additive, so a missing/empty config degrades to the built-in
|
|
428
|
+
set, never an error. A *present but malformed* `[reasons]` table raises
|
|
429
|
+
(`specs_from_table`), because a host that declared a reason wrong wants that
|
|
430
|
+
surfaced, not swallowed.
|
|
431
|
+
"""
|
|
432
|
+
p = Path(path)
|
|
433
|
+
if not p.exists():
|
|
434
|
+
return base
|
|
435
|
+
try:
|
|
436
|
+
import tomllib # py3.11+
|
|
437
|
+
except ModuleNotFoundError: # pragma: no cover - py<3.11 fallback
|
|
438
|
+
try:
|
|
439
|
+
import tomli as tomllib # type: ignore
|
|
440
|
+
except ModuleNotFoundError:
|
|
441
|
+
return base
|
|
442
|
+
# `utf-8-sig` transparently strips a UTF-8 BOM (PowerShell's default `utf8`
|
|
443
|
+
# encoding writes one; raw `tomllib.load(rb)` chokes on it and would silently
|
|
444
|
+
# drop a valid declared table — see the same fix in `config._load_toml_table`).
|
|
445
|
+
data = tomllib.loads(p.read_text(encoding="utf-8-sig"))
|
|
446
|
+
table = data.get("reasons")
|
|
447
|
+
if not isinstance(table, dict) or not table:
|
|
448
|
+
return base
|
|
449
|
+
return base.extend(specs_from_table(table))
|
dos/reconcile.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""`reconcile` — the quiet-completion gate (docs/168 Concept 3, docs/207 Phase 4).
|
|
2
|
+
|
|
3
|
+
The picker-boundary closure of the quiet-failure line (docs/149–164). Those docs
|
|
4
|
+
DETECT quiet failure *in a trajectory* (a run that narrated success but the world
|
|
5
|
+
did not move); `reconcile` is what KEEPS a quietly-incomplete unit in the residual
|
|
6
|
+
*across runs* — so the next cycle re-offers it, flagged, instead of believing the
|
|
7
|
+
"✅ done" and dropping it. The `job` repo's FQ-336 quiet-DRAIN storm is the
|
|
8
|
+
motivating bug: a mere *touch* of a plan doc counted as a ship → a false "all
|
|
9
|
+
shipped" → `child_skipped_replan` ×8 re-confirms. A `QUIET_INCOMPLETE` keep would
|
|
10
|
+
have caught it: the touch is the agent's self-report, the oracle is ground truth.
|
|
11
|
+
|
|
12
|
+
It is a JOIN over two verdicts the kernel ALREADY produces — the agent's CLAIM and
|
|
13
|
+
the `oracle` verdict (the same `oracle.is_shipped` `verify` answers from git
|
|
14
|
+
ancestry, never self-report) — NOT a new sensor. The rule is the intent-ledger
|
|
15
|
+
rule (docs/107: a `STEP_CLAIMED` stays, a `STEP_VERIFIED` is what removes work)
|
|
16
|
+
generalized to the picker:
|
|
17
|
+
|
|
18
|
+
> **Fail-closed on the claim.** The agent's word never REMOVES work; only ground
|
|
19
|
+
> truth does. claim-done ∧ oracle-NOT_SHIPPED → QUIET_INCOMPLETE, KEPT in the
|
|
20
|
+
> residual, flagged.
|
|
21
|
+
|
|
22
|
+
The three states (mutually exclusive):
|
|
23
|
+
|
|
24
|
+
* ``VERIFIED`` — the oracle confirms the unit shipped (ground truth). It
|
|
25
|
+
leaves the residual. (The claim is irrelevant here — a
|
|
26
|
+
verified unit is done whether or not the agent claimed it.)
|
|
27
|
+
* ``QUIET_INCOMPLETE`` — the agent CLAIMED done but the oracle says NOT_SHIPPED.
|
|
28
|
+
The dangerous case: a self-report that, believed, would
|
|
29
|
+
silently drop real work. KEPT in the residual, FLAGGED so
|
|
30
|
+
the host routes it (a verifier pass / /replan / a finding).
|
|
31
|
+
* ``HONEST_OPEN`` — the agent did NOT claim done and the oracle says
|
|
32
|
+
NOT_SHIPPED. Honest unfinished work; stays in the residual
|
|
33
|
+
with no flag (it is not a quiet failure, just open).
|
|
34
|
+
|
|
35
|
+
DETECT-and-KEEP, never FIX (docs/164). `reconcile` does not mutate, re-run, or
|
|
36
|
+
correct anything — it KEEPS the work alive and FLAGS the divergence; the host owns
|
|
37
|
+
the correction. The kernel stays a PDP, never a PEP.
|
|
38
|
+
|
|
39
|
+
⚓ Pure; the oracle verdict + the claim are gathered at the caller boundary and
|
|
40
|
+
handed in (the `oracle.is_shipped` read, the claim parse), exactly like
|
|
41
|
+
`completion.classify` is handed its `AncestryFacts`. So the toolathlon scoring
|
|
42
|
+
gate (docs/207 Phase 4) replays on the committed corpus offline.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from __future__ import annotations
|
|
46
|
+
|
|
47
|
+
import enum
|
|
48
|
+
from dataclasses import dataclass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Reconciliation(str, enum.Enum):
|
|
52
|
+
"""The typed quiet-completion verdict (docs/168 §3).
|
|
53
|
+
|
|
54
|
+
`str`-valued so it round-trips a `--json` token / exit code without a lookup
|
|
55
|
+
table (the `Completion` / `gate_classify.Verdict` idiom). The load-bearing
|
|
56
|
+
asymmetry: only VERIFIED removes the unit from the residual; QUIET_INCOMPLETE
|
|
57
|
+
and HONEST_OPEN both KEEP it (the fail-closed-on-the-claim floor).
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
VERIFIED = "VERIFIED" # oracle confirms shipped — leaves the residual
|
|
61
|
+
QUIET_INCOMPLETE = "QUIET_INCOMPLETE" # claimed done BUT oracle says not — KEEP + flag
|
|
62
|
+
HONEST_OPEN = "HONEST_OPEN" # not claimed + not shipped — honest open work, KEEP
|
|
63
|
+
|
|
64
|
+
def __str__(self) -> str: # pragma: no cover - trivial
|
|
65
|
+
return self.value
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def keeps_in_residual(self) -> bool:
|
|
69
|
+
"""True iff the unit STAYS in the residual (everything but VERIFIED)."""
|
|
70
|
+
return self is not Reconciliation.VERIFIED
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def is_quiet_failure(self) -> bool:
|
|
74
|
+
"""True iff this is the dangerous case — a claim the oracle refutes."""
|
|
75
|
+
return self is Reconciliation.QUIET_INCOMPLETE
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass(frozen=True)
|
|
79
|
+
class ReconciliationVerdict:
|
|
80
|
+
"""The single verdict `reconcile` returns, with the inputs echoed back.
|
|
81
|
+
|
|
82
|
+
``state`` is the typed `Reconciliation`. ``unit`` is the unit id. ``claimed`` /
|
|
83
|
+
``oracle_shipped`` are the two inputs the join read (so a surfaced verdict is
|
|
84
|
+
legible). ``reason`` is the operator-facing one-liner. ``flag`` is the routing
|
|
85
|
+
tag a quiet-incomplete carries (``"QUIET_INCOMPLETE"`` so the host can route it),
|
|
86
|
+
empty otherwise.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
state: Reconciliation
|
|
90
|
+
unit: str
|
|
91
|
+
claimed: bool
|
|
92
|
+
oracle_shipped: bool
|
|
93
|
+
reason: str
|
|
94
|
+
flag: str = ""
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def keeps_in_residual(self) -> bool:
|
|
98
|
+
return self.state.keeps_in_residual
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def is_quiet_failure(self) -> bool:
|
|
102
|
+
"""True iff this is the dangerous case — a claim the oracle refutes."""
|
|
103
|
+
return self.state.is_quiet_failure
|
|
104
|
+
|
|
105
|
+
def to_dict(self) -> dict:
|
|
106
|
+
return {
|
|
107
|
+
"state": self.state.value,
|
|
108
|
+
"unit": self.unit,
|
|
109
|
+
"claimed": self.claimed,
|
|
110
|
+
"oracle_shipped": self.oracle_shipped,
|
|
111
|
+
"keeps_in_residual": self.keeps_in_residual,
|
|
112
|
+
"flag": self.flag,
|
|
113
|
+
"reason": self.reason,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def reconcile(
|
|
118
|
+
unit: str,
|
|
119
|
+
*,
|
|
120
|
+
claimed_done: bool,
|
|
121
|
+
oracle_shipped: bool,
|
|
122
|
+
) -> ReconciliationVerdict:
|
|
123
|
+
"""Reconcile a unit's CLAIM against the ORACLE's ground-truth verdict. PURE.
|
|
124
|
+
|
|
125
|
+
``claimed_done`` is the agent's self-report ("I finished this unit") — gathered
|
|
126
|
+
at the boundary (a `claim_done` tool call, a plan-meta `shipped:` entry, a
|
|
127
|
+
packet disposition). ``oracle_shipped`` is `oracle.is_shipped`'s verdict from
|
|
128
|
+
git ancestry (the non-forgeable rung) — also gathered at the boundary.
|
|
129
|
+
|
|
130
|
+
The fail-closed-on-the-claim join (docs/168 §3):
|
|
131
|
+
|
|
132
|
+
* oracle_shipped → VERIFIED (leaves the residual; the
|
|
133
|
+
claim is moot — ground truth confirms it).
|
|
134
|
+
* claimed_done ∧ ¬oracle_shipped → QUIET_INCOMPLETE (the agent's word would
|
|
135
|
+
silently drop real work; KEEP + flag).
|
|
136
|
+
* ¬claimed_done ∧ ¬oracle_shipped → HONEST_OPEN (honest unfinished work; KEEP,
|
|
137
|
+
no flag — not a quiet failure).
|
|
138
|
+
|
|
139
|
+
The agent's claim NEVER removes the unit from the residual; only the oracle
|
|
140
|
+
does. Returns a `ReconciliationVerdict`; never raises.
|
|
141
|
+
"""
|
|
142
|
+
uid = str(unit)
|
|
143
|
+
if oracle_shipped:
|
|
144
|
+
return ReconciliationVerdict(
|
|
145
|
+
state=Reconciliation.VERIFIED,
|
|
146
|
+
unit=uid,
|
|
147
|
+
claimed=bool(claimed_done),
|
|
148
|
+
oracle_shipped=True,
|
|
149
|
+
reason=(f"oracle confirms {uid} shipped (git ancestry) — verified, "
|
|
150
|
+
f"leaves the residual"),
|
|
151
|
+
)
|
|
152
|
+
if claimed_done:
|
|
153
|
+
return ReconciliationVerdict(
|
|
154
|
+
state=Reconciliation.QUIET_INCOMPLETE,
|
|
155
|
+
unit=uid,
|
|
156
|
+
claimed=True,
|
|
157
|
+
oracle_shipped=False,
|
|
158
|
+
flag="QUIET_INCOMPLETE",
|
|
159
|
+
reason=(
|
|
160
|
+
f"{uid} was CLAIMED done but the oracle says NOT_SHIPPED — a quiet "
|
|
161
|
+
f"failure; KEPT in the residual and flagged (the claim is a "
|
|
162
|
+
f"self-report; only ground truth removes work). Route to a verifier "
|
|
163
|
+
f"pass / /replan / a finding — do NOT believe the claim"
|
|
164
|
+
),
|
|
165
|
+
)
|
|
166
|
+
return ReconciliationVerdict(
|
|
167
|
+
state=Reconciliation.HONEST_OPEN,
|
|
168
|
+
unit=uid,
|
|
169
|
+
claimed=False,
|
|
170
|
+
oracle_shipped=False,
|
|
171
|
+
reason=(f"{uid} is not claimed and not shipped — honest open work; stays in "
|
|
172
|
+
f"the residual (not a quiet failure)"),
|
|
173
|
+
)
|