dos-kernel 0.22.0__py3-none-win_arm64.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/evidence.py
ADDED
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
"""The evidence-source seam — Axis 8 of hackability: a pluggable witness population.
|
|
2
|
+
|
|
3
|
+
docs/121 §5 — the throughline slice. `verify()` answers "did this effect actually
|
|
4
|
+
happen?" and today it reads exactly one witness: **git** (existence + ancestry +
|
|
5
|
+
ship-stamp grammar, via `dos.oracle`/`dos.git_delta`). docs/121 §2.1 is blunt that
|
|
6
|
+
this is the kernel having shipped the witness for *one* class of effect — a commit —
|
|
7
|
+
and being blind to every other: an email sent, a webhook delivered, a payment made,
|
|
8
|
+
a migration run, a deploy shipped. For each of those the witness is not git; it is
|
|
9
|
+
**the counterparty that received the effect** — the recipient's record, the
|
|
10
|
+
provider's sent-log, the bank's ledger, the OS exit code of a kernel-launched
|
|
11
|
+
command. Git is the witness in exactly one row of that table.
|
|
12
|
+
|
|
13
|
+
This module is the **pure seam** a witness plugs into, so that population becomes
|
|
14
|
+
open: `verify` stops being git-only and starts being "ask whichever witness is
|
|
15
|
+
accountable for *this* effect." It is field-for-field the apparatus `judges` (the
|
|
16
|
+
JUDGE rung), `overlap_policy` (the disjointness scorer), and `log_source` (the
|
|
17
|
+
log-adapter spectrum) already proved — a Protocol, frozen value types, an
|
|
18
|
+
unshadowable built-in baseline, a by-name resolver over an entry-point group, and a
|
|
19
|
+
fail-safe runner — fused with the **floor discipline** `overlap_policy` made
|
|
20
|
+
structural. Every *witnessing* source with real I/O surface (run a command and read
|
|
21
|
+
the exit code, call a provider's API, read a TEE attestation) lives in a `drivers/*`
|
|
22
|
+
module — it imports the kernel; the kernel never imports it (the `drivers/__init__`
|
|
23
|
+
rule). The kernel ships only the abstraction + the honest floor.
|
|
24
|
+
|
|
25
|
+
The one idea that makes this *verification* and not a fact-aggregation library
|
|
26
|
+
================================================================================
|
|
27
|
+
|
|
28
|
+
An `EvidenceSource` is **a witness whose byte-author is not the judged agent**
|
|
29
|
+
(docs/121 §2.1). `git` is the special case where the witness is a VCS and the effect
|
|
30
|
+
is a commit. So every source declares its **`accountability`** — who authored the
|
|
31
|
+
bytes — as DATA, reusing the docs/117 spectrum (`AGENT_AUTHORED` / `OS_RECORDED` /
|
|
32
|
+
`THIRD_PARTY`). That tag is the *ceiling* on how much a consumer may trust the
|
|
33
|
+
source, fixed by the source, never inferred from content.
|
|
34
|
+
|
|
35
|
+
The floor discipline — `believe ⟺ a non-forgeable source attests` (load-bearing)
|
|
36
|
+
==============================================================================
|
|
37
|
+
|
|
38
|
+
This is the security-load-bearing core, the dual of `overlap_policy`'s
|
|
39
|
+
`admissible_under_floor`. An evidence verdict (`believe` / `abstain`) is *positive*
|
|
40
|
+
— it can upgrade `verify` toward SHIPPED — so, exactly as a policy that *includes
|
|
41
|
+
admit* needs a floor ANDed under it, a witness population that *includes belief*
|
|
42
|
+
needs a floor ORed-only-from-the-non-forgeable-side:
|
|
43
|
+
|
|
44
|
+
> A swapped evidence source may add *more* abstention or contribute a *stronger*
|
|
45
|
+
> attestation. It may never let `verify` BELIEVE an effect that no non-forgeable
|
|
46
|
+
> source attests.
|
|
47
|
+
|
|
48
|
+
`believe_under_floor` computes this structurally: belief is granted **only** when at
|
|
49
|
+
least one source whose `accountability` is non-forgeable (`OS_RECORDED` /
|
|
50
|
+
`THIRD_PARTY`) reached the effect and attested it. A source on the **forgeable
|
|
51
|
+
floor** (`AGENT_AUTHORED` — a pasted receipt, the agent's own stdout, an mtime) is
|
|
52
|
+
*structurally incapable* of being the attesting source: its attestation is recorded
|
|
53
|
+
and shown, but it can never, by itself, move the verdict from abstain to believe.
|
|
54
|
+
This is what makes an *open* evidence-source set safe — the judge seam's
|
|
55
|
+
fail-to-ABSTAIN and the overlap seam's conjunctive-floor, re-aimed at belief: the
|
|
56
|
+
worst a buggy/hostile/lying source can do is *withhold* an attestation it should
|
|
57
|
+
have given (a visible, safe-direction loss — `verify` falls back to a weaker
|
|
58
|
+
witness), never *manufacture* a belief the agent could forge.
|
|
59
|
+
|
|
60
|
+
Purity & layering
|
|
61
|
+
=================
|
|
62
|
+
|
|
63
|
+
Pure stdlib — an enum (re-exported from `log_source` so the spectrum has one home),
|
|
64
|
+
two frozen value types, a built-in source that always abstains, a fail-safe runner,
|
|
65
|
+
a resolver, and the floor helper. NO provider surface, no I/O inside a verdict, names
|
|
66
|
+
no host. It sits in the kernel layer beside `judges`/`overlap_policy`/`log_source`.
|
|
67
|
+
Entry-point discovery (the one bit of I/O) happens at the call boundary in
|
|
68
|
+
`active_evidence_sources`, exactly as `active_judges` / `active_log_sources` do.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
from __future__ import annotations
|
|
72
|
+
|
|
73
|
+
import enum
|
|
74
|
+
import sys
|
|
75
|
+
from dataclasses import dataclass
|
|
76
|
+
from typing import Protocol, runtime_checkable
|
|
77
|
+
|
|
78
|
+
# The accountability spectrum has ONE home — docs/117's `log_source`. An evidence
|
|
79
|
+
# source sits on the same who-authored-the-byte axis as a log source (a log IS a
|
|
80
|
+
# kind of evidence), so we reuse the enum verbatim rather than fork a parallel one.
|
|
81
|
+
# Re-exported here so a consumer can `from dos.evidence import Accountability`
|
|
82
|
+
# without reaching across to the log seam.
|
|
83
|
+
from dos.log_source import Accountability
|
|
84
|
+
|
|
85
|
+
__all__ = [
|
|
86
|
+
"Accountability",
|
|
87
|
+
"EvidenceStance",
|
|
88
|
+
"EvidenceFacts",
|
|
89
|
+
"EvidenceSource",
|
|
90
|
+
"NullEvidenceSource",
|
|
91
|
+
"gather_evidence",
|
|
92
|
+
"believe_under_floor",
|
|
93
|
+
"derived_witness",
|
|
94
|
+
"EVIDENCE_SOURCE_ENTRY_POINT_GROUP",
|
|
95
|
+
"resolve_evidence_source",
|
|
96
|
+
"active_evidence_sources",
|
|
97
|
+
"active_evidence_source_names",
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class EvidenceStance(str, enum.Enum):
|
|
102
|
+
"""What a gathered `EvidenceFacts` says about the effect it was asked to witness.
|
|
103
|
+
|
|
104
|
+
Three-valued on purpose — the same honest split the typed-verdict family makes
|
|
105
|
+
(a behavioral oracle's GREEN/RED/PENDING/NO_SIGNAL collapses to belief /
|
|
106
|
+
refutation / no-answer): a binary attest/silent would have to *lie* about the case
|
|
107
|
+
where the witness was reached and saw the effect did NOT happen (a refutation is
|
|
108
|
+
not the same as "no signal"). `str`-valued so it round-trips through a CLI token /
|
|
109
|
+
JSON without a lookup table (the `Liveness` / `Accountability` idiom).
|
|
110
|
+
|
|
111
|
+
ATTESTED — the source was reached and witnessed the effect (the recipient has
|
|
112
|
+
the email; the exit code was 0; the ledger shows the payment). A
|
|
113
|
+
push toward belief — but ONLY counts toward `verify`'s belief if the
|
|
114
|
+
source's `accountability` is non-forgeable (the floor discipline).
|
|
115
|
+
REFUTED — the source was reached and witnessed the effect did NOT happen (the
|
|
116
|
+
recipient never got it; the exit code was non-zero). The
|
|
117
|
+
load-bearing third value: a refutation by an accountable witness is
|
|
118
|
+
STRONGER than no signal — it should redden `verify`, never be
|
|
119
|
+
mistaken for "could not tell."
|
|
120
|
+
NO_SIGNAL — the source could not be reached/read, or has no record either way.
|
|
121
|
+
The honest floor; what every failure degrades to. A consumer reads
|
|
122
|
+
it as abstain (the `run_judge` / behavioral-oracle fail-safe).
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
ATTESTED = "ATTESTED"
|
|
126
|
+
REFUTED = "REFUTED"
|
|
127
|
+
NO_SIGNAL = "NO_SIGNAL"
|
|
128
|
+
|
|
129
|
+
def __str__(self) -> str: # pragma: no cover - trivial
|
|
130
|
+
return self.value
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@dataclass(frozen=True)
|
|
134
|
+
class EvidenceFacts:
|
|
135
|
+
"""Frozen, caller-gathered facts about one effect, from one witness.
|
|
136
|
+
|
|
137
|
+
The `CiEvidence` / `LogEvidence` / `ProgressEvidence` analogue: facts gathered at
|
|
138
|
+
the boundary (inside a source's `gather`) and handed to a consuming verdict, which
|
|
139
|
+
is pure. No verdict lives inside — the source reports what it *saw*, the floor
|
|
140
|
+
helper decides what `verify` may *believe*.
|
|
141
|
+
|
|
142
|
+
source_name — the backend that produced this (`"os_acceptance"`,
|
|
143
|
+
`"stripe_ledger"`), for the operator-facing reason + the JSON
|
|
144
|
+
consumer.
|
|
145
|
+
accountability — the source's spectrum rung (docs/117). The load-bearing field:
|
|
146
|
+
`believe_under_floor` grants belief only on a NON-FORGEABLE
|
|
147
|
+
rung, never off content. A class-level property of the source,
|
|
148
|
+
echoed onto the facts so a downstream consumer routes off the
|
|
149
|
+
evidence object alone.
|
|
150
|
+
stance — ATTESTED / REFUTED / NO_SIGNAL (above).
|
|
151
|
+
subject — the effect this witnesses (an opaque correlation handle: a
|
|
152
|
+
run-id, a message-id, a command, a SHA — the source decides),
|
|
153
|
+
echoed for the operator surface.
|
|
154
|
+
detail — a one-line human note (the exit code, the provider id, why
|
|
155
|
+
unreachable) — legible distrust, for the reason / `dos doctor`.
|
|
156
|
+
reachable — was the witness actually reached? **Defaults to False** — the
|
|
157
|
+
fail-safe zero: facts nobody successfully populated read as
|
|
158
|
+
"no signal," never an empty-but-trusted attestation. A
|
|
159
|
+
`NO_SIGNAL` stance with `reachable=False` is the honest floor.
|
|
160
|
+
|
|
161
|
+
Three constructors make the outcomes unmistakable and keep the fail-safe default
|
|
162
|
+
from being fat-fingered: `attest(...)` (reached, effect happened),
|
|
163
|
+
`refute(...)` (reached, effect did NOT happen), `no_signal(...)` (every degrade).
|
|
164
|
+
There is deliberately no other way to set `reachable=True`.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
source_name: str
|
|
168
|
+
accountability: Accountability
|
|
169
|
+
stance: EvidenceStance = EvidenceStance.NO_SIGNAL
|
|
170
|
+
subject: str = ""
|
|
171
|
+
detail: str = ""
|
|
172
|
+
reachable: bool = False
|
|
173
|
+
|
|
174
|
+
@classmethod
|
|
175
|
+
def attest(
|
|
176
|
+
cls,
|
|
177
|
+
source_name: str,
|
|
178
|
+
accountability: Accountability,
|
|
179
|
+
subject: str,
|
|
180
|
+
*,
|
|
181
|
+
detail: str = "",
|
|
182
|
+
) -> "EvidenceFacts":
|
|
183
|
+
"""The witness was reached and the effect HAPPENED. One of only two
|
|
184
|
+
constructors that set `reachable=True` — so a reachable attestation is always
|
|
185
|
+
a deliberate, populated read, never an accident of the default."""
|
|
186
|
+
return cls(
|
|
187
|
+
source_name=source_name,
|
|
188
|
+
accountability=accountability,
|
|
189
|
+
stance=EvidenceStance.ATTESTED,
|
|
190
|
+
subject=subject,
|
|
191
|
+
detail=detail,
|
|
192
|
+
reachable=True,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
@classmethod
|
|
196
|
+
def refute(
|
|
197
|
+
cls,
|
|
198
|
+
source_name: str,
|
|
199
|
+
accountability: Accountability,
|
|
200
|
+
subject: str,
|
|
201
|
+
*,
|
|
202
|
+
detail: str = "",
|
|
203
|
+
) -> "EvidenceFacts":
|
|
204
|
+
"""The witness was reached and the effect did NOT happen (the recipient has no
|
|
205
|
+
record; the exit code was non-zero). `reachable=True`, stance REFUTED — a
|
|
206
|
+
positive disconfirmation, distinct from "could not tell.\""""
|
|
207
|
+
return cls(
|
|
208
|
+
source_name=source_name,
|
|
209
|
+
accountability=accountability,
|
|
210
|
+
stance=EvidenceStance.REFUTED,
|
|
211
|
+
subject=subject,
|
|
212
|
+
detail=detail,
|
|
213
|
+
reachable=True,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
def no_signal(
|
|
218
|
+
cls,
|
|
219
|
+
source_name: str,
|
|
220
|
+
accountability: Accountability,
|
|
221
|
+
subject: str = "",
|
|
222
|
+
*,
|
|
223
|
+
detail: str = "",
|
|
224
|
+
) -> "EvidenceFacts":
|
|
225
|
+
"""The witness could not be reached/read, or has no record either way — the
|
|
226
|
+
honest floor (no source wired, auth failed, timeout, no such record).
|
|
227
|
+
`reachable=False`, stance NO_SIGNAL. What every failure in `gather_evidence`
|
|
228
|
+
degrades to, and what a consuming verdict reads as abstain — never a
|
|
229
|
+
fabricated attestation (the `run_judge` fail-safe-never-fail-open discipline).
|
|
230
|
+
"""
|
|
231
|
+
return cls(
|
|
232
|
+
source_name=source_name,
|
|
233
|
+
accountability=accountability,
|
|
234
|
+
stance=EvidenceStance.NO_SIGNAL,
|
|
235
|
+
subject=subject,
|
|
236
|
+
detail=detail,
|
|
237
|
+
reachable=False,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def is_attesting(self) -> bool:
|
|
242
|
+
"""True iff this is a reached ATTESTED read — the precondition (not the whole
|
|
243
|
+
condition) for contributing belief. `believe_under_floor` ANDs this with the
|
|
244
|
+
non-forgeable-accountability check; on its own it is necessary, not
|
|
245
|
+
sufficient."""
|
|
246
|
+
return self.reachable and self.stance is EvidenceStance.ATTESTED
|
|
247
|
+
|
|
248
|
+
def to_dict(self) -> dict:
|
|
249
|
+
return {
|
|
250
|
+
"source_name": self.source_name,
|
|
251
|
+
"accountability": self.accountability.value,
|
|
252
|
+
"stance": self.stance.value,
|
|
253
|
+
"subject": self.subject,
|
|
254
|
+
"detail": self.detail,
|
|
255
|
+
"reachable": self.reachable,
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@runtime_checkable
|
|
260
|
+
class EvidenceSource(Protocol):
|
|
261
|
+
"""The contract a backend implements to add a witness.
|
|
262
|
+
|
|
263
|
+
`name` is the token a resolver selects and `dos doctor` would list.
|
|
264
|
+
`accountability` is the source's declared spectrum rung — a CLASS-LEVEL property,
|
|
265
|
+
fixed by the backend, not chosen per call (an `os_acceptance` source is
|
|
266
|
+
`OS_RECORDED` always; it has no honest path to a higher rung; a `paste_receipt`
|
|
267
|
+
source is `AGENT_AUTHORED` always and can never attest belief). `gather` is handed
|
|
268
|
+
a `subject` (the opaque correlation handle — the source decides what it means) and
|
|
269
|
+
the active `config` (read-only), and returns `EvidenceFacts`.
|
|
270
|
+
|
|
271
|
+
A backend MAY do I/O *inside* `gather` (run a command, call a provider API, read a
|
|
272
|
+
TEE quote) — unlike a predicate or renderer, which are pure. That is exactly why a
|
|
273
|
+
real backend lives in a driver, outside the kernel boundary: this seam is where
|
|
274
|
+
I/O surface is allowed, the same latitude the `Judge` / `LogSource` protocols give.
|
|
275
|
+
The discipline that keeps it honest is not purity but **fail-safe** (enforced by
|
|
276
|
+
`gather_evidence`, below, not by trusting the backend) plus the **fixed
|
|
277
|
+
accountability tag** (a backend cannot lie its way up the spectrum at call time)
|
|
278
|
+
plus the **floor discipline** (`believe_under_floor` — a forgeable-floor source's
|
|
279
|
+
attestation can never, by itself, move `verify` to belief).
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
name: str
|
|
283
|
+
accountability: Accountability
|
|
284
|
+
|
|
285
|
+
def gather(self, subject: str, config: object) -> EvidenceFacts:
|
|
286
|
+
...
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class NullEvidenceSource:
|
|
290
|
+
"""The built-in, always-available source: it witnesses nothing.
|
|
291
|
+
|
|
292
|
+
The evidence analogue of `NullLogSource` / `AbstainJudge` — a trusted,
|
|
293
|
+
unshadowable fallback (`resolve_evidence_source` resolves built-ins first) and the
|
|
294
|
+
honest zero of the seam: a deployment with NO witness wired still has a resolvable
|
|
295
|
+
source, and it returns `no_signal` for every subject. A device with no git and no
|
|
296
|
+
network resolves to {this} only, so `verify` honestly abstains (`via none`) rather
|
|
297
|
+
than inventing a witness.
|
|
298
|
+
|
|
299
|
+
Tagged `AGENT_AUTHORED` — the floor — so that even the *absence* of a real source
|
|
300
|
+
can never be mistaken for a trustworthy rung: the most a missing witness can claim
|
|
301
|
+
is the least-trusted tag, and it is unreachable on top of that. It is doubly
|
|
302
|
+
incapable of granting belief (forgeable rung AND never reachable).
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
name = "null"
|
|
306
|
+
accountability = Accountability.AGENT_AUTHORED
|
|
307
|
+
|
|
308
|
+
def gather(self, subject: str, config: object) -> EvidenceFacts:
|
|
309
|
+
return EvidenceFacts.no_signal(
|
|
310
|
+
self.name,
|
|
311
|
+
self.accountability,
|
|
312
|
+
subject,
|
|
313
|
+
detail=(
|
|
314
|
+
"no evidence source wired — the built-in null source witnesses "
|
|
315
|
+
"nothing, so this effect has no signal (configure a "
|
|
316
|
+
"dos.evidence_sources backend)."
|
|
317
|
+
),
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def gather_evidence(source: EvidenceSource, subject: str, config: object) -> EvidenceFacts:
|
|
322
|
+
"""Run one source against one subject, enforcing **fail-safe, never fail-open**.
|
|
323
|
+
|
|
324
|
+
The wrapper EVERY consumer should call instead of `source.gather(...)` directly —
|
|
325
|
+
what makes "a backend can never manufacture an attestation by failing" structural
|
|
326
|
+
rather than hoped-for (the `run_judge` / `gather_log` discipline, restated for
|
|
327
|
+
witnesses):
|
|
328
|
+
|
|
329
|
+
* a source that **raises** (command missing, API timeout, a bug) → an
|
|
330
|
+
unreachable `no_signal` naming the failure. Never propagates; never a
|
|
331
|
+
reachable read; never an ATTESTED.
|
|
332
|
+
* a source that returns **anything that is not `EvidenceFacts`** (None, a dict,
|
|
333
|
+
a bare bool, a duck-typed look-alike) → `no_signal`. We never read a foreign
|
|
334
|
+
object's `.stance`/`.reachable`, so no fabricated attestation can sneak through
|
|
335
|
+
a wrong return type.
|
|
336
|
+
|
|
337
|
+
The degrade preserves the source's declared `accountability` (read defensively via
|
|
338
|
+
`getattr`, defaulting to the forgeable floor) so a consumer still routes correctly
|
|
339
|
+
on failure AND a malformed source object cannot escape to a higher rung via the
|
|
340
|
+
failure path. The safe failure is "no signal" (abstain), never "attest" and never
|
|
341
|
+
"refute" — an evidence gatherer that cannot read produces NO_SIGNAL, the same
|
|
342
|
+
direction `gather_log` takes (evidence-gathering, not a safety gate).
|
|
343
|
+
"""
|
|
344
|
+
name = getattr(source, "name", type(source).__name__)
|
|
345
|
+
acct = getattr(source, "accountability", Accountability.AGENT_AUTHORED)
|
|
346
|
+
if not isinstance(acct, Accountability):
|
|
347
|
+
acct = Accountability.AGENT_AUTHORED
|
|
348
|
+
try:
|
|
349
|
+
facts = source.gather(subject, config)
|
|
350
|
+
except Exception as e: # fail-safe: a source that raises produces no signal
|
|
351
|
+
return EvidenceFacts.no_signal(
|
|
352
|
+
str(name),
|
|
353
|
+
acct,
|
|
354
|
+
subject,
|
|
355
|
+
detail=(
|
|
356
|
+
f"evidence source {name!r} raised ({e!r}) — no signal (a witness that "
|
|
357
|
+
f"cannot read produces NO_SIGNAL, never a fabricated attestation)."
|
|
358
|
+
),
|
|
359
|
+
)
|
|
360
|
+
if not isinstance(facts, EvidenceFacts):
|
|
361
|
+
return EvidenceFacts.no_signal(
|
|
362
|
+
str(name),
|
|
363
|
+
acct,
|
|
364
|
+
subject,
|
|
365
|
+
detail=(
|
|
366
|
+
f"evidence source {name!r} returned a {type(facts).__name__}, not "
|
|
367
|
+
f"EvidenceFacts — no signal (a source that does not return the evidence "
|
|
368
|
+
f"type cannot be trusted to have witnessed anything)."
|
|
369
|
+
),
|
|
370
|
+
)
|
|
371
|
+
return facts
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
# ---------------------------------------------------------------------------
|
|
375
|
+
# The floor discipline — `believe ⟺ a non-forgeable source attests`.
|
|
376
|
+
# The dual of `overlap_policy.admissible_under_floor`: a structural guarantee
|
|
377
|
+
# that no forgeable-floor source can manufacture belief.
|
|
378
|
+
# ---------------------------------------------------------------------------
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
@dataclass(frozen=True)
|
|
382
|
+
class BeliefVerdict:
|
|
383
|
+
"""The folded answer over a set of gathered `EvidenceFacts` for one effect.
|
|
384
|
+
|
|
385
|
+
`believe` is the positive bit `verify` may consume — but it is True ONLY when at
|
|
386
|
+
least one NON-FORGEABLE source attested (the floor discipline). `refuted` is
|
|
387
|
+
surfaced separately because a refutation by an accountable witness is a distinct,
|
|
388
|
+
stronger signal than mere absence of belief — a consumer may redden on it.
|
|
389
|
+
`attesting` / `refuting` / `silent` name the sources behind the verdict (legible
|
|
390
|
+
distrust — not just "believed" but *which witness* attested, the RND renderer
|
|
391
|
+
seam). `to_dict()` is the JSON shape for `--json` / MCP / the decisions queue.
|
|
392
|
+
"""
|
|
393
|
+
|
|
394
|
+
believe: bool
|
|
395
|
+
refuted: bool
|
|
396
|
+
reason: str
|
|
397
|
+
attesting: tuple[str, ...] = ()
|
|
398
|
+
refuting: tuple[str, ...] = ()
|
|
399
|
+
silent: tuple[str, ...] = ()
|
|
400
|
+
|
|
401
|
+
def to_dict(self) -> dict:
|
|
402
|
+
return {
|
|
403
|
+
"believe": self.believe,
|
|
404
|
+
"refuted": self.refuted,
|
|
405
|
+
"reason": self.reason,
|
|
406
|
+
"attesting": list(self.attesting),
|
|
407
|
+
"refuting": list(self.refuting),
|
|
408
|
+
"silent": list(self.silent),
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def believe_under_floor(facts: "tuple[EvidenceFacts, ...] | list[EvidenceFacts]") -> BeliefVerdict:
|
|
413
|
+
"""Fold gathered facts into a belief verdict, enforcing the floor discipline.
|
|
414
|
+
|
|
415
|
+
The security-load-bearing function — the dual of
|
|
416
|
+
`overlap_policy.admissible_under_floor`. It reads the gathered witnesses and
|
|
417
|
+
answers the only question `verify` cares about: *may we BELIEVE this effect
|
|
418
|
+
happened?* The rule is structural, not a threshold a host can loosen:
|
|
419
|
+
|
|
420
|
+
> believe ⟺ at least one source whose `accountability` is NON-FORGEABLE
|
|
421
|
+
> (`OS_RECORDED` / `THIRD_PARTY`) was reached and ATTESTED.
|
|
422
|
+
|
|
423
|
+
So a forgeable-floor source (`AGENT_AUTHORED` — a pasted receipt, the agent's own
|
|
424
|
+
stdout, an mtime) is *structurally incapable* of granting belief: its ATTESTED
|
|
425
|
+
facts are recorded in `attesting` and shown, but they are filtered out of the
|
|
426
|
+
belief decision by the accountability check. The worst a buggy/hostile/lying
|
|
427
|
+
AGENT_AUTHORED source can do is claim an attestation that is then **ignored for
|
|
428
|
+
belief** (a visible, safe-direction no-op), never manufacture a SHIPPED verdict.
|
|
429
|
+
|
|
430
|
+
A REFUTED by an accountable witness sets `refuted=True` (a consumer may redden);
|
|
431
|
+
a REFUTED by a forgeable source is recorded but, symmetrically, does not by itself
|
|
432
|
+
establish refutation (the floor cuts both ways — a forgeable source is too weak to
|
|
433
|
+
*redden* `verify` on its own, just as it is too weak to greenlight it). Belief and
|
|
434
|
+
refutation are independent: a population can attest (one accountable source) AND
|
|
435
|
+
refute (another) at once, which a consumer routes to a human as a conflict.
|
|
436
|
+
|
|
437
|
+
PURE — no I/O. The facts were gathered at the boundary; this only folds them.
|
|
438
|
+
"""
|
|
439
|
+
attesting: list[str] = []
|
|
440
|
+
refuting: list[str] = []
|
|
441
|
+
silent: list[str] = []
|
|
442
|
+
believe = False
|
|
443
|
+
refuted = False
|
|
444
|
+
|
|
445
|
+
for f in facts:
|
|
446
|
+
non_forgeable = not f.accountability.is_agent_authored
|
|
447
|
+
if f.reachable and f.stance is EvidenceStance.ATTESTED:
|
|
448
|
+
attesting.append(f.source_name)
|
|
449
|
+
if non_forgeable:
|
|
450
|
+
believe = True
|
|
451
|
+
elif f.reachable and f.stance is EvidenceStance.REFUTED:
|
|
452
|
+
refuting.append(f.source_name)
|
|
453
|
+
if non_forgeable:
|
|
454
|
+
refuted = True
|
|
455
|
+
else:
|
|
456
|
+
silent.append(f.source_name)
|
|
457
|
+
|
|
458
|
+
if believe and refuted:
|
|
459
|
+
reason = (
|
|
460
|
+
f"CONFLICT — accountable witnesses disagree: {', '.join(attesting)} attest, "
|
|
461
|
+
f"{', '.join(refuting)} refute (route to a human)"
|
|
462
|
+
)
|
|
463
|
+
elif believe:
|
|
464
|
+
reason = f"believed — non-forgeable witness attested: {', '.join(attesting)}"
|
|
465
|
+
elif refuted:
|
|
466
|
+
reason = f"refuted — non-forgeable witness disconfirmed: {', '.join(refuting)}"
|
|
467
|
+
elif attesting:
|
|
468
|
+
# Something attested, but only on the forgeable floor — the floor discipline
|
|
469
|
+
# withholds belief. Name it so the operator sees WHY a present attestation did
|
|
470
|
+
# not count.
|
|
471
|
+
reason = (
|
|
472
|
+
f"abstain — only forgeable-floor (AGENT_AUTHORED) sources attested "
|
|
473
|
+
f"({', '.join(attesting)}); no accountable witness — verify cannot believe"
|
|
474
|
+
)
|
|
475
|
+
else:
|
|
476
|
+
reason = "abstain — no witness reached this effect (no signal)"
|
|
477
|
+
|
|
478
|
+
return BeliefVerdict(
|
|
479
|
+
believe=believe,
|
|
480
|
+
refuted=refuted,
|
|
481
|
+
reason=reason,
|
|
482
|
+
attesting=tuple(attesting),
|
|
483
|
+
refuting=tuple(refuting),
|
|
484
|
+
silent=tuple(silent),
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
# ---------------------------------------------------------------------------
|
|
489
|
+
# The derived-witness primitive — the floor discipline lifted to a DERIVATION.
|
|
490
|
+
# (docs/156 — the byte-inequality axiom, docs/141, moved up one level: a value
|
|
491
|
+
# COMPUTED from operands is only as non-forgeable as BOTH its operands AND its
|
|
492
|
+
# recorded operation. Closes the grounded-RAG adoption's one soundness hole — a
|
|
493
|
+
# host that brute-forced agent-SELECTED arithmetic onto the THIRD_PARTY rung.)
|
|
494
|
+
# ---------------------------------------------------------------------------
|
|
495
|
+
|
|
496
|
+
# The accountability spectrum ordered weakest→strongest, so a derivation can be
|
|
497
|
+
# capped at the MINIMUM rung among its operands (you cannot derive a stronger fact
|
|
498
|
+
# than your weakest input — the "ceiling fixed by the source" rule, made inductive).
|
|
499
|
+
_RUNG_ORDER: dict[Accountability, int] = {
|
|
500
|
+
Accountability.AGENT_AUTHORED: 0,
|
|
501
|
+
Accountability.OS_RECORDED: 1,
|
|
502
|
+
Accountability.THIRD_PARTY: 2,
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def _operand_rung(operand: "EvidenceFacts | BeliefVerdict") -> "Accountability | None":
|
|
507
|
+
"""The non-forgeable rung an operand was witnessed at, or None if it is not a
|
|
508
|
+
reached non-forgeable attestation. A `BeliefVerdict` operand counts iff it
|
|
509
|
+
`believe`s (which `believe_under_floor` only grants on a non-forgeable attest, so
|
|
510
|
+
a believed verdict is THIRD_PARTY-equivalent — the strongest the floor mints). An
|
|
511
|
+
`EvidenceFacts` operand counts iff it `is_attesting` on a non-forgeable rung."""
|
|
512
|
+
if isinstance(operand, BeliefVerdict):
|
|
513
|
+
# A believed verdict already passed the floor: a non-forgeable witness attested.
|
|
514
|
+
# It carries no single rung, so treat it as the floor's own guarantee (THIRD_PARTY).
|
|
515
|
+
return Accountability.THIRD_PARTY if (operand.believe and not operand.refuted) else None
|
|
516
|
+
if isinstance(operand, EvidenceFacts):
|
|
517
|
+
if operand.is_attesting and not operand.accountability.is_agent_authored:
|
|
518
|
+
return operand.accountability
|
|
519
|
+
return None
|
|
520
|
+
return None
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def derived_witness(
|
|
524
|
+
source_name: str,
|
|
525
|
+
op: str,
|
|
526
|
+
operands: "tuple[EvidenceFacts | BeliefVerdict, ...] | list[EvidenceFacts | BeliefVerdict]",
|
|
527
|
+
*,
|
|
528
|
+
subject: str,
|
|
529
|
+
within_tol: bool,
|
|
530
|
+
detail: str = "",
|
|
531
|
+
) -> EvidenceFacts:
|
|
532
|
+
"""Mint an `EvidenceFacts` for a value the agent COMPUTED from operands, on a
|
|
533
|
+
non-forgeable rung ONLY when the derivation is honest by construction.
|
|
534
|
+
|
|
535
|
+
The dual of `believe_under_floor`, lifted from a witnessed effect to a *derivation*
|
|
536
|
+
(docs/156 §3). The security-load-bearing rule — structural, not a host knob:
|
|
537
|
+
|
|
538
|
+
> A derived value may be witnessed on a NON-FORGEABLE rung IFF
|
|
539
|
+
> (1) the operation `op` is a DECLARED token (passed in by the caller — never
|
|
540
|
+
> reverse-searched to fit the answer; a brute-force "does this equal SOME
|
|
541
|
+
> operand pairing?" search IS the agent-selection that forges the rung, and
|
|
542
|
+
> this helper refuses to express it — the caller must commit to one op), AND
|
|
543
|
+
> (2) EVERY operand was itself attested by a non-forgeable witness, AND
|
|
544
|
+
> (3) the recomputation of `op` over the operands matches the claim (`within_tol`).
|
|
545
|
+
|
|
546
|
+
Failure modes, in the safe direction:
|
|
547
|
+
|
|
548
|
+
- Any operand unwitnessed / forgeable → the derived fact degrades to
|
|
549
|
+
**AGENT_AUTHORED** (recorded, advisory, structurally incapable of granting belief
|
|
550
|
+
under `believe_under_floor`). You CANNOT reach a non-forgeable rung without
|
|
551
|
+
non-forgeable operands — the laundering the grounded-RAG host did (tagging
|
|
552
|
+
agent-selected arithmetic `THIRD_PARTY`) becomes impossible.
|
|
553
|
+
- `op` empty/missing → AGENT_AUTHORED (an undeclared op is a post-hoc fit).
|
|
554
|
+
- `within_tol=False` → **refute** (a positive disconfirmation, distinct from "could
|
|
555
|
+
not tell" — the recomputation was done and it disagreed).
|
|
556
|
+
|
|
557
|
+
The result's accountability is the **minimum rung** among the operands (the weakest
|
|
558
|
+
operand caps the derivation). PURE — no I/O; the operands were witnessed at the
|
|
559
|
+
boundary and the recomputation was done by the caller (it passes the verdict in via
|
|
560
|
+
`within_tol`); this only folds rung + op-declaration into the honest tag.
|
|
561
|
+
"""
|
|
562
|
+
rungs = [_operand_rung(o) for o in operands]
|
|
563
|
+
op_declared = bool(op and op.strip())
|
|
564
|
+
all_non_forgeable = bool(operands) and all(r is not None for r in rungs)
|
|
565
|
+
|
|
566
|
+
if not within_tol:
|
|
567
|
+
# The recomputation disagreed — a refutation. Rung still capped honestly: a
|
|
568
|
+
# refutation from forgeable operands is weaker, but a disagreement is a
|
|
569
|
+
# disagreement; tag it at the min available rung (AGENT_AUTHORED if any operand
|
|
570
|
+
# is forgeable, so a forgeable refute cannot, by the floor, redden on its own).
|
|
571
|
+
rung = _min_rung(rungs)
|
|
572
|
+
return EvidenceFacts.refute(
|
|
573
|
+
source_name, rung, subject,
|
|
574
|
+
detail=detail or f"derived op={op or '(undeclared)'} recomputation disagrees",
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
if op_declared and all_non_forgeable:
|
|
578
|
+
rung = _min_rung(rungs)
|
|
579
|
+
return EvidenceFacts.attest(
|
|
580
|
+
source_name, rung, subject,
|
|
581
|
+
detail=detail or f"derived op={op} from {len(operands)} non-forgeable operand(s)",
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
# Honest degrade: the derivation is recorded but cannot reach a non-forgeable rung
|
|
585
|
+
# (undeclared op, or an operand the agent could have authored). AGENT_AUTHORED so
|
|
586
|
+
# the floor treats it as advisory — never a forged THIRD_PARTY.
|
|
587
|
+
why = (
|
|
588
|
+
"undeclared operation (post-hoc fit)" if not op_declared
|
|
589
|
+
else "an operand lacks a non-forgeable witness"
|
|
590
|
+
)
|
|
591
|
+
return EvidenceFacts.attest(
|
|
592
|
+
source_name, Accountability.AGENT_AUTHORED, subject,
|
|
593
|
+
detail=detail or f"derived op={op or '(undeclared)'} — advisory only: {why}",
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def _min_rung(rungs: "list[Accountability | None]") -> Accountability:
|
|
598
|
+
"""The weakest non-None rung among operands, or AGENT_AUTHORED if any is None/empty
|
|
599
|
+
(a missing operand witness caps the whole derivation at the forgeable floor)."""
|
|
600
|
+
present = [r for r in rungs if r is not None]
|
|
601
|
+
if not present or len(present) != len(rungs):
|
|
602
|
+
return Accountability.AGENT_AUTHORED
|
|
603
|
+
return min(present, key=lambda r: _RUNG_ORDER[r])
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
# ---------------------------------------------------------------------------
|
|
607
|
+
# Resolution — built-in first, then the `dos.evidence_sources` entry-point group.
|
|
608
|
+
# (The `resolve_judge` / `resolve_log_source` discipline, verbatim.)
|
|
609
|
+
# ---------------------------------------------------------------------------
|
|
610
|
+
|
|
611
|
+
# The entry-point group a workspace/researcher registers a witness backend under.
|
|
612
|
+
EVIDENCE_SOURCE_ENTRY_POINT_GROUP = "dos.evidence_sources"
|
|
613
|
+
|
|
614
|
+
# The built-in sources, resolvable by name and UNSHADOWABLE by a plugin (a plugin
|
|
615
|
+
# registering `null` cannot displace this one — built-ins resolve first). Only the
|
|
616
|
+
# conservative `null` baseline ships in the kernel; every witnessing backend lives in
|
|
617
|
+
# a driver/plugin (the kernel has no I/O/provider surface).
|
|
618
|
+
_BUILT_IN_SOURCES: dict[str, type] = {
|
|
619
|
+
NullEvidenceSource.name: NullEvidenceSource,
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def _discover_entry_point_sources(*, _stderr=None) -> list[tuple[str, EvidenceSource]]:
|
|
624
|
+
"""Find witness backends registered under the `dos.evidence_sources` group.
|
|
625
|
+
|
|
626
|
+
A backend plugin registers ``name = "pkg.module:SourceClass"`` in its
|
|
627
|
+
``[project.entry-points."dos.evidence_sources"]``. We load each, instantiate it if
|
|
628
|
+
it is a class, and return ``(entry_point_name, source)`` pairs sorted by name
|
|
629
|
+
(stable, deterministic listing). A plugin that fails to load is skipped with a
|
|
630
|
+
one-line stderr note rather than crashing — the same posture judge / log-source /
|
|
631
|
+
predicate / renderer discovery take (a broken third-party plugin is the operator's
|
|
632
|
+
to fix, not a kernel fault).
|
|
633
|
+
"""
|
|
634
|
+
stderr = _stderr if _stderr is not None else sys.stderr
|
|
635
|
+
out: list[tuple[str, EvidenceSource]] = []
|
|
636
|
+
try:
|
|
637
|
+
from importlib.metadata import entry_points
|
|
638
|
+
except Exception: # pragma: no cover - importlib.metadata always present py3.11+
|
|
639
|
+
return out
|
|
640
|
+
try:
|
|
641
|
+
eps = entry_points(group=EVIDENCE_SOURCE_ENTRY_POINT_GROUP)
|
|
642
|
+
except TypeError: # pragma: no cover - py<3.10 selectable-API fallback
|
|
643
|
+
eps = entry_points().get(EVIDENCE_SOURCE_ENTRY_POINT_GROUP, []) # type: ignore[attr-defined]
|
|
644
|
+
except Exception: # pragma: no cover - defensive: never let discovery crash a call
|
|
645
|
+
return out
|
|
646
|
+
for ep in sorted(eps, key=lambda e: e.name):
|
|
647
|
+
try:
|
|
648
|
+
obj = ep.load()
|
|
649
|
+
source = obj() if isinstance(obj, type) else obj
|
|
650
|
+
except Exception as e: # pragma: no cover - depends on third-party plugin
|
|
651
|
+
print(
|
|
652
|
+
f"warning: evidence source plugin {ep.name!r} failed to load ({e}); skipping",
|
|
653
|
+
file=stderr,
|
|
654
|
+
)
|
|
655
|
+
continue
|
|
656
|
+
out.append((ep.name, source))
|
|
657
|
+
return out
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
def resolve_evidence_source(name: str, *, _stderr=None) -> EvidenceSource:
|
|
661
|
+
"""Resolve a witness by name: built-ins first, then `dos.evidence_sources` plugins.
|
|
662
|
+
|
|
663
|
+
Built-ins (`null`) resolve FIRST and cannot be shadowed by a plugin of the same
|
|
664
|
+
name — the trusted-fallback guarantee, identical to `resolve_judge` /
|
|
665
|
+
`resolve_log_source`. An unknown name fails LOUD with the known list (it never
|
|
666
|
+
silently degrades to `null`, which would hide a typo'd source name): the caller
|
|
667
|
+
asked for a specific witness and getting a different one silently is exactly the
|
|
668
|
+
unannounced substitution the kernel refuses.
|
|
669
|
+
"""
|
|
670
|
+
if name in _BUILT_IN_SOURCES:
|
|
671
|
+
return _BUILT_IN_SOURCES[name]()
|
|
672
|
+
discovered = dict(_discover_entry_point_sources(_stderr=_stderr))
|
|
673
|
+
if name in discovered:
|
|
674
|
+
return discovered[name]
|
|
675
|
+
known = sorted(set(_BUILT_IN_SOURCES) | set(discovered))
|
|
676
|
+
raise ValueError(f"unknown evidence source {name!r}; known: {', '.join(known)}")
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
def active_evidence_sources(*, _stderr=None) -> list[tuple[str, EvidenceSource]]:
|
|
680
|
+
"""Every resolvable source as ``(name, source)`` — built-ins THEN discovered
|
|
681
|
+
plugins. Does ENTRY-POINT DISCOVERY (I/O), so it is a call-boundary helper, never
|
|
682
|
+
called inside a verdict (the `active_judges` / `active_log_sources` discipline)."""
|
|
683
|
+
built = [(n, cls()) for n, cls in _BUILT_IN_SOURCES.items()]
|
|
684
|
+
discovered = _discover_entry_point_sources(_stderr=_stderr)
|
|
685
|
+
return built + discovered
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def active_evidence_source_names(*, _stderr=None) -> list[str]:
|
|
689
|
+
"""The names of every active source (built-in + discovered) — what `dos doctor`
|
|
690
|
+
would list so an operator can see which witnesses are wired (the evidence analogue
|
|
691
|
+
of "see the active judges / log sources / predicates")."""
|
|
692
|
+
return [name for name, _src in active_evidence_sources(_stderr=_stderr)]
|