merge: legacy cleanup -- audit by real imports, dead modules removed, unread config retired, archives, dockerignore, comments

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
Marius Ciepluch
2026-10-02 08:43:32 +02:00
co-authored by Claude Fable 5.1
64 changed files with 473 additions and 2543 deletions
+19 -58
View File
@@ -20,10 +20,24 @@ _PLANE_RUN = "`ousast plane run`, see ops/ax/README.md"
_RETIRED_PANELS = (
f"LLM fusion panels were retired; fusion is deterministic; independent LLM agreement is the plane's `agree` task ({_PLANE_RUN})"
)
# Keys that loaded but were never read (retired 2026-10-02, legacy cleanup): accepting them silently would let
# a user believe they configure something.
_UNREAD_ON = "retired 2026-10-02 because nothing ever read it"
_UNREAD_DYNAMIC = "no dynamic or deep tier exists; deep mode will define its own keys when it is built"
_UNREAD_EVIDENCE = "evidence tiers come from the program model's ladder (model/ladder.py), not from configuration"
_UNREAD_VARIANTS = "the structural variant search it configured was deleted with semantic/variants.py"
RETIRED_KEYS: dict[tuple[str, str], str] = {
("models", "verifier"): f"LLM verification runs on the plane (`verify` + `agree`, {_PLANE_RUN})",
("fusion", "panel_model"): _RETIRED_PANELS,
("fusion", "decider_model"): _RETIRED_PANELS,
("models", "verifier"): f"{_RETIRED_ON}: LLM verification runs on the plane (`verify` + `agree`, {_PLANE_RUN})",
("fusion", "panel_model"): f"{_RETIRED_ON}: {_RETIRED_PANELS}",
("fusion", "decider_model"): f"{_RETIRED_ON}: {_RETIRED_PANELS}",
("models", "patcher"): f"{_UNREAD_ON}: no patching stage calls a model",
("dynamic", "enabled"): f"{_UNREAD_ON}: {_UNREAD_DYNAMIC}",
("dynamic", "network_scope"): f"{_UNREAD_ON}: {_UNREAD_DYNAMIC}",
("evidence", "minimum_report_verified"): f"{_UNREAD_ON}: {_UNREAD_EVIDENCE}",
("evidence", "minimum_exploit"): f"{_UNREAD_ON}: {_UNREAD_EVIDENCE}",
("evidence", "minimum_patch"): f"{_UNREAD_ON}: {_UNREAD_EVIDENCE}",
("variants", "enabled"): f"{_UNREAD_ON}: {_UNREAD_VARIANTS}",
("variants", "max_mechanisms"): f"{_UNREAD_ON}: {_UNREAD_VARIANTS}",
}
RETIRED_SECTION = "harnessx" # retired 2026-09-30: loads with one warning and is otherwise ignored (Req 3.3)
RETIRED_SECTION_WARNING = (
@@ -38,10 +52,10 @@ class RetiredConfigError(ValueError):
def _check_retired(data: dict[str, object]) -> None:
problems = []
for (section, key), replacement in RETIRED_KEYS.items():
for (section, key), reason in RETIRED_KEYS.items():
table = data.get(section)
if isinstance(table, dict) and key in table:
problems.append(f"`[{section}] {key}` was {_RETIRED_ON}: {replacement}. Remove the key.")
problems.append(f"`[{section}] {key}` was {reason}. Remove the key.")
if problems:
raise RetiredConfigError("\n".join(problems))
if RETIRED_SECTION in data:
@@ -70,7 +84,6 @@ def load_dotenv(path: Path | None = None, *, force: bool = False) -> None:
class ModelConfig:
ranker: str | None = None
hunter: str | None = None
patcher: str | None = None
judge: str | None = None # second, independent judgement before anything is published as proven
chat_base_url: str | None = None # chat endpoint; independent of the embedding endpoint
chat_api_key_env: str | None = None # environment variable holding that endpoint's key
@@ -96,19 +109,6 @@ class SandboxConfig:
pids_limit: int = 512
@dataclass(frozen=True)
class DynamicConfig:
enabled: bool = False
network_scope: tuple[str, ...] = ()
@dataclass(frozen=True)
class EvidenceConfig:
minimum_report_verified: str = "static_corroboration"
minimum_exploit: str = "crash_reproduced"
minimum_patch: str = "root_cause_explained"
@dataclass(frozen=True)
class StaticAnalysisConfig:
sarif_paths: tuple[str, ...] = ()
@@ -160,13 +160,6 @@ class RegressConfig:
images: tuple[tuple[str, str], ...] = ()
@dataclass(frozen=True)
class VariantsConfig:
# corpus-seeded-mechanisms: structural variant search in MAP (standard/deep only).
enabled: bool = True
max_mechanisms: int = 500
@dataclass(frozen=True)
class ModelLayerConfig:
# contributor-scan: the model layer in MAP. Named for the LAYER, not the model: `ModelConfig` is already
@@ -214,8 +207,6 @@ class ResolvedConfig:
models: ModelConfig = ModelConfig()
embeddings: EmbeddingConfig = EmbeddingConfig()
sandbox: SandboxConfig = SandboxConfig()
dynamic: DynamicConfig = DynamicConfig()
evidence: EvidenceConfig = EvidenceConfig()
static_analysis: StaticAnalysisConfig = StaticAnalysisConfig()
score: ScoreConfig = ScoreConfig()
ruleset: RulesetConfig = RulesetConfig()
@@ -223,7 +214,6 @@ class ResolvedConfig:
hardening: HardeningConfig = HardeningConfig()
complexity: ComplexityConfig = ComplexityConfig()
regress: RegressConfig = RegressConfig()
variants: VariantsConfig = VariantsConfig()
obligations: ObligationsConfig = ObligationsConfig()
model: ModelLayerConfig = ModelLayerConfig()
push: PushConfig = PushConfig()
@@ -243,8 +233,6 @@ def load_config(config_path: Path | None = None, *, dotenv: bool = True) -> Reso
models=_load_models(data.get("models", {})),
embeddings=_load_embeddings(data.get("embeddings", {})),
sandbox=_load_sandbox(data.get("sandbox", {})),
dynamic=_load_dynamic(data.get("dynamic", {})),
evidence=_load_evidence(data.get("evidence", {})),
static_analysis=_load_static_analysis(data.get("static_analysis", {})),
score=_load_score(data.get("score", {})),
ruleset=_load_ruleset_config(data.get("ruleset", {})),
@@ -252,7 +240,6 @@ def load_config(config_path: Path | None = None, *, dotenv: bool = True) -> Reso
hardening=_load_hardening(data.get("hardening", {})),
complexity=_load_complexity(data.get("complexity", {})),
regress=_load_regress(data.get("regress", {})),
variants=_load_variants(data.get("variants", {})),
obligations=_load_obligations(data.get("obligations", {})),
model=_load_model(data.get("model", {})),
push=PushConfig.from_payload(data.get("push", {})),
@@ -283,7 +270,6 @@ def _load_models(value: object) -> ModelConfig:
return ModelConfig(
ranker=_string(data.get("ranker")),
hunter=_string(data.get("hunter")),
patcher=_string(data.get("patcher")),
judge=_string(data.get("judge")),
chat_base_url=_string(data.get("chat_base_url")),
chat_api_key_env=_string(data.get("chat_api_key_env")),
@@ -310,23 +296,6 @@ def _load_sandbox(value: object) -> SandboxConfig:
)
def _load_dynamic(value: object) -> DynamicConfig:
data = _section(value)
scope = data.get("network_scope", [])
if not isinstance(scope, list):
scope = []
return DynamicConfig(enabled=bool(data.get("enabled", False)), network_scope=tuple(str(item) for item in scope))
def _load_evidence(value: object) -> EvidenceConfig:
data = _section(value)
return EvidenceConfig(
minimum_report_verified=str(data.get("minimum_report_verified", "static_corroboration")),
minimum_exploit=str(data.get("minimum_exploit", "crash_reproduced")),
minimum_patch=str(data.get("minimum_patch", "root_cause_explained")),
)
def _int_value(value: object, default: int) -> int:
if isinstance(value, bool):
return default
@@ -416,14 +385,6 @@ def _float_value(value: object, default: float) -> float:
return default
def _load_variants(value: object) -> VariantsConfig:
data = _section(value)
return VariantsConfig(
enabled=bool(data.get("enabled", True)),
max_mechanisms=_int_value(data.get("max_mechanisms"), 500),
)
def _load_model(value: object) -> ModelLayerConfig:
data = _section(value)
return ModelLayerConfig(
+2 -2
View File
@@ -1,11 +1,11 @@
"""Fusion adjudication — OpenUltraCode-style two-panel deepening (Phase 13).
"""Fusion adjudication — OpenUltraCode-style two-panel deepening (Phase 13 of the retired openrouter-sast-harness plan).
Fusion is the deepening mechanism for findings that need more reasoning than the
ranker → hunter → verifier → mapping loop provides: critical/high severity, verifier
disagreement, static-vs-semantic evidence conflict, findings that gate a risky fix or
disclosure, or an explicit high-assurance request. Two panels independently review the
bounded evidence (one steel-manning the vulnerability case, one the false-positive
case), critique and revise, vote, and a decider issues the final disposition.
case) and vote, and a decider issues the final disposition.
The panels and the decider are deterministic and auditable. Every fused finding receives
exactly one of the five dispositions, and the decision discloses panel roles, votes and the
+1 -1
View File
@@ -1,4 +1,4 @@
"""Detection-quality gate — the hard feasibility constraint (Phase 4 task 8).
"""Detection-quality gate — the hard feasibility constraint (Phase 4 task 8 of the retired openrouter-sast-harness plan).
Aggregate recall and false-positive rate over the benchmark corpus must clear the
project goal: recall >= 90% AND false-positive rate < 10%. This gate is computed
+1 -1
View File
@@ -181,7 +181,7 @@ def write_harness_config(
"ranker": config.models.ranker,
"hunter": config.models.hunter,
"verifier": None, # schema kept byte for byte; LLM verification runs on the plane
"patcher": config.models.patcher,
"patcher": None, # schema kept; `[models] patcher` was retired 2026-10-02 (never read)
},
processor_versions={processor.spec.name: processor.spec.version for processor in processors},
prompt_hashes={name: _hash_text(prompt) for name, prompt in sorted(prompts.items())},
+2 -2
View File
@@ -1,8 +1,8 @@
"""Narrow MCP server for OpenCode integration (Phase 12).
"""Narrow MCP server for OpenCode integration (Phase 12 of the retired openrouter-sast-harness plan).
Exposes only stable, project-level operations over MCP stdio (newline-delimited
JSON-RPC 2.0), implemented with the standard library only. No tool exposes arbitrary
shell, Docker, or internal hunter tools — those stay inside controlled harness stages.
shell, Docker, or internal hunter tools — those stay inside the scan's own stages.
The pure :meth:`McpServer.handle` dispatch is separated from the :func:`serve` stdio
loop so the surface is unit-testable without a live client.
"""
+62 -20
View File
@@ -6,9 +6,12 @@ checkable rather than a thing someone notices two specs later.
A module is:
load_bearing reachable from a gate, the scan CLI, or a surviving requirement
standalone_capability its own entry point and users, not on the scan path
orphaned no importer, no entry point, no requirement — it should not be in the tree
load_bearing reachable by imports from a gate, the CLI, or a module run with ``python -m``
standalone_capability reachable the same way, but its own entry point and users, not on the scan path
orphaned not reachable from any of those -- it should not be in the tree
Reachability decides the class. ``SPEC_OWNED`` and ``STANDALONE`` only supply the reason text: being named
by a spec does not keep a module nobody imports alive.
Pure stdlib, no imports of the package it audits: it reads the import graph with ``ast`` so a broken module
is still classifiable.
@@ -26,7 +29,7 @@ PACKAGE = "openultrasast"
# Entry points a module can be reachable from. The three gates are the contract; the CLI is the product.
ROOTS = ("gate", "map_gate", "pair_gate", "cli", "__main__")
# Modules that stand alone: their own CLI subcommand or external protocol, deliberately off the scan path.
# Reason text for reachable modules, by the requirement that owns them. Not a classification: see audit().
SPEC_OWNED = {
"push.cache": "pre-push-safety-net task 6.2: bounded artifact publication and leased eviction; runner integration in 6.4",
"push.report": "pre-push-safety-net Req 5.1/5.2/5.5/7.3: compact output and complete artifacts; runner integration pending",
@@ -38,16 +41,13 @@ SPEC_OWNED = {
"push": "pre-push-safety-net Req 5.5/7.3: experimental push contract package; no hook installed",
"push.contracts": "pre-push-safety-net Req 5.5/7.3: immutable comparisons and independent result statuses; runner pending",
"model.specs": "model-grounded-detection Req 7.2-7.4: the security vocabularies the CPG queries are parameterised by",
"model.endpoint": "model-grounded-detection Req 8: the judge client, wired by model/judge.py in group 3",
"model.endpoint": "model-grounded-detection Req 8: the bounded model client the hunter, the plane and the CLI share",
"model.audit": "model-grounded-detection Req 3: this classifier; run by the maintainer, not by the scan",
"model.ladder": "model-grounded-detection Req 5: the evidence ladder every finding carries",
"model.taint": "model-grounded-detection Req 6.1: taint reachability over the CPG, the flow-family arbiter",
"model.judge": "model-grounded-detection Req 8: one bounded question, checked against the model",
"model.dominance": "model-grounded-detection Req 6.2: guard dominance, the arbiter for bugs that never crash",
"model.config_value": "model-grounded-detection Req 6.3: constant abstraction for the configuration families",
"model.calibrate": "model-grounded-detection Req 9: the corpus as the model's calibration set",
"model.report": "model-grounded-detection Req 9.3/11.1: per-slice reporting with the overfitting gap",
"model.execution": "model-grounded-detection Req 10: the deferred execution tier seam, adopted not built",
"model.pipeline": "model-grounded-detection Req 8: the enumerator proposes, the LLM answers, the model disposes",
"model.regions": "contributor-scan Req 2: regions from entry points, with the families each admits",
"model.scan": "contributor-scan Req 3: the repository driver -- one CPG, one budget, ranked spend",
@@ -81,14 +81,13 @@ SPEC_OWNED = {
"plane.runner": "ai-service-plane Req 4.1-4.4: the ax-task-runner entrypoint (PID 1 of the task image); delivers the output directory",
}
# Reachable modules that stand alone: their own CLI subcommand or external protocol, off the scan path.
STANDALONE = {
"push_scoring": "pre-push-safety-net task 1.3: frozen-profile diagnostic outcome scorer; python -m openultrasast.push_scoring",
"push_inputs": "pre-push-safety-net task 1.2: offline input provenance validator; python -m openultrasast.push_inputs",
"mcp": "narrow MCP server over stdio; its own `ousast mcp` entry point and OpenCode integration",
"skills": "skill router; consumed by mcp and by the OpenCode integration, not by the scan path",
"fusion": "two-panel adjudication engine; reached from the scan's report stage and used standalone",
"stage_processors": "deterministic slot-contracted scan stages; pinned by the zero-dependency guard in test_gate",
"slot_contract": "the slot/contract protocol stage_processors implements",
}
@@ -140,6 +139,10 @@ def import_graph(root: Path) -> dict[str, set[str]]:
continue
if target:
imported.add(target)
# `from .plane import engine_alerts` imports the submodule `plane.engine_alerts`, not only the
# package; names that are not modules are dropped later because they are not in the graph.
prefix = f"{target}." if target else ""
imported.update(f"{prefix}{alias.name}" for alias in node.names if alias.name != "*")
elif isinstance(node, ast.Import):
for alias in node.names:
if alias.name.startswith(f"{PACKAGE}."):
@@ -148,6 +151,28 @@ def import_graph(root: Path) -> dict[str, set[str]]:
return graph
def main_modules(root: Path) -> set[str]:
"""Modules with a ``__main__`` block: run by name (``python -m``, the plane runner), so they are entry points."""
found: set[str] = set()
for path in sorted(root.rglob("*.py")):
if "__pycache__" in path.parts:
continue
try:
tree = ast.parse(path.read_text())
except (OSError, SyntaxError):
continue
for node in tree.body:
if (
isinstance(node, ast.If)
and isinstance(node.test, ast.Compare)
and isinstance(node.test.left, ast.Name)
and node.test.left.id == "__name__"
and any(isinstance(c, ast.Constant) and c.value == "__main__" for c in node.test.comparators)
):
found.add(module_name(path, root))
return found
def _reachable(graph: Mapping[str, set[str]], roots: Iterable[str]) -> set[str]:
seen: set[str] = set()
stack = [root for root in roots if root in graph]
@@ -173,24 +198,33 @@ def audit(root: Path) -> tuple[ModuleAudit, ...]:
for candidate in (target, target.rsplit(".", 1)[0]):
if candidate in importers and candidate != name:
importers[candidate].add(name)
live = _reachable(graph, ROOTS)
run_by_name = main_modules(root)
live = _reachable(graph, (*ROOTS, *sorted(run_by_name)))
results: list[ModuleAudit] = []
for name in sorted(graph):
if not name: # the package __init__ itself
continue
who = tuple(sorted(importers[name]))
if name in SPEC_OWNED:
listed = SPEC_OWNED.get(name) or STANDALONE.get(name)
# Reachability decides; the lists only supply reason text. A listed module nothing reaches is still
# orphaned -- the list is what hid five dead modules behind "load_bearing" until 2026-10-02.
if name not in live:
reason = "no import path from an entry point or a python -m module"
if who:
reason += f" (imported only by unreachable {', '.join(who)})"
if listed:
reason += f"; listed as: {listed}"
results.append(ModuleAudit(name, "orphaned", reason, who))
elif name in STANDALONE:
results.append(ModuleAudit(name, "standalone_capability", STANDALONE[name], who))
elif name in SPEC_OWNED:
results.append(ModuleAudit(name, "load_bearing", SPEC_OWNED[name], who))
elif name in ROOTS:
results.append(ModuleAudit(name, "load_bearing", "entry point", who))
elif name in STANDALONE:
results.append(ModuleAudit(name, "standalone_capability", STANDALONE[name], who))
elif name in live:
results.append(ModuleAudit(name, "load_bearing", f"reachable from {', '.join(ROOTS[:3])} or the CLI", who))
elif who:
results.append(ModuleAudit(name, "load_bearing", f"imported by {', '.join(who)}", who))
elif name in run_by_name:
results.append(ModuleAudit(name, "load_bearing", f"python -m openultrasast.{name}", who))
else:
results.append(ModuleAudit(name, "orphaned", "no importer, no entry point, no surviving requirement", who))
results.append(ModuleAudit(name, "load_bearing", f"reachable from {', '.join(ROOTS[:4])}", who))
return tuple(results)
@@ -211,4 +245,12 @@ def to_dict(results: Iterable[ModuleAudit]) -> dict[str, object]:
}
__all__ = ["ModuleAudit", "SPEC_OWNED", "STANDALONE", "audit", "import_graph", "module_name", "to_dict"]
__all__ = ["ModuleAudit", "SPEC_OWNED", "STANDALONE", "audit", "import_graph", "main_modules", "module_name", "to_dict"]
if __name__ == "__main__": # python -m openultrasast.model.audit [SRC] -- the manifest's regeneration command
import json
import sys
source = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).resolve().parents[1]
print(json.dumps(to_dict(audit(source)), indent=2))
-180
View File
@@ -1,180 +0,0 @@
"""The corpus calibrates the model, not an LLM vote (model-grounded-detection, Req 9).
This is the inversion the whole feature rests on. In the prior architecture a pair the detector missed was a
*detector* failure, to be averaged away over K runs and chased with a prompt change. Here the arbiter is
deterministic: it will miss that pair the same way every time, and the only thing that moves it is better
modelling. So a pair the model cannot split is a **named gap in the model**, recorded with its reason, and
the report's job is to make those gaps legible by family and by slice rather than to produce a score.
`covered` means the model reached a strictly higher rung on the vulnerable side than on its fix. Flagging
both sides equally is not coverage — it distinguishes nothing, which is exactly the failure the group-2
measurement found taint reachability making on every parameterised SQL pair.
"""
from __future__ import annotations
from collections.abc import Iterable, Mapping, Sequence
from dataclasses import dataclass, field
from typing import TypedDict
class FamilyGaps(TypedDict):
"""Gaps for one family: how many pairs, and the tally of reasons behind them."""
pairs: int
reasons: dict[str, int]
# Ascending, matching `ladder.Rung`. A pair is split when the vulnerable side outranks its fix.
_ORDER = {"none": 0, "": 0, "suspicion": 0, "model_corroborated": 1, "model_entailed": 2, "execution_confirmed": 3}
@dataclass(frozen=True)
class PairOutcome:
"""What the model made of one vulnerable/fixed pair."""
pair: str
family: str
slice: str
vuln_rung: str
fixed_rung: str
reason: str = ""
@dataclass(frozen=True)
class ModelGap:
"""A pair the model could not split, and why. Not a miss to average — a gap to close."""
pair: str
family: str
slice: str
reason: str
@dataclass(frozen=True)
class HarvestRecipe:
"""Where a pair comes from. The fix commit's parent is the vulnerable side, by construction."""
repo: str
fix_commit: str
relpath: str
function: str = ""
@property
def parent_ref(self) -> str:
return f"{self.fix_commit}^"
@dataclass(frozen=True)
class CalibrationReport:
covered: tuple[str, ...]
gaps: tuple[ModelGap, ...]
per_slice: Mapping[str, Mapping[str, int]]
per_family: Mapping[str, Mapping[str, int]] = field(default_factory=dict)
def gaps_by_family(self) -> dict[str, FamilyGaps]:
"""Gaps grouped by family, each with a tally of the reasons behind them."""
out: dict[str, FamilyGaps] = {}
for gap in self.gaps:
block = out.setdefault(gap.family, {"pairs": 0, "reasons": {}})
block["pairs"] += 1
block["reasons"][gap.reason] = block["reasons"].get(gap.reason, 0) + 1
return out
def to_markdown(self) -> str:
"""Per-slice rows first; there is no headline here that is not backed by them (Req 9.3, 11.1)."""
lines = [
"## Model calibration",
"",
"Coverage is a pair the model *splits*: a strictly higher rung on",
"the vulnerable side than on its fix. Everything else is a named gap.",
"",
"| slice | pairs | covered | gaps | coverage |",
"|---|---|---|---|---|",
]
for slice_name, block in sorted(self.per_slice.items()):
pairs = block["pairs"] or 1
lines.append(f"| {slice_name} | {block['pairs']} | {block['covered']} | {block['gaps']} | {block['covered'] / pairs:.0%} |")
by_family = self.gaps_by_family()
if by_family:
lines += ["", "### Gaps by family", ""]
for family, gaps in sorted(by_family.items()):
detail = ", ".join(f"{k} x{v}" for k, v in sorted(gaps["reasons"].items()))
lines.append(f"- `{family}`: {gaps['pairs']} pair(s) — {detail}")
return "\n".join(lines) + "\n"
def calibrate(outcomes: Iterable[PairOutcome]) -> CalibrationReport:
"""Split the corpus into what the model covers and what it cannot, with reasons."""
covered: list[str] = []
gaps: list[ModelGap] = []
per_slice: dict[str, dict[str, int]] = {}
per_family: dict[str, dict[str, int]] = {}
for outcome in outcomes:
split = _ORDER.get(outcome.vuln_rung, 0) > _ORDER.get(outcome.fixed_rung, 0)
for key, store in ((outcome.slice, per_slice), (outcome.family, per_family)):
block = store.setdefault(key, {"pairs": 0, "covered": 0, "gaps": 0})
block["pairs"] += 1
block["covered" if split else "gaps"] += 1
if split:
covered.append(outcome.pair)
else:
gaps.append(
ModelGap(pair=outcome.pair, family=outcome.family, slice=outcome.slice, reason=outcome.reason or _default_reason(outcome))
)
return CalibrationReport(
covered=tuple(sorted(covered)),
gaps=tuple(sorted(gaps, key=lambda g: (g.family, g.pair))),
per_slice=per_slice,
per_family=per_family,
)
def _default_reason(outcome: PairOutcome) -> str:
"""Every gap carries a reason, even when the caller supplied none — a bare gap teaches nothing."""
if _ORDER.get(outcome.vuln_rung, 0) == 0:
return "no_verdict_on_the_vulnerable_side"
if outcome.vuln_rung == outcome.fixed_rung:
return f"both_sides_reached_{outcome.vuln_rung}: the model distinguished nothing"
return "fixed_side_outranked_the_vulnerable_one"
def harvest_recipe(entry: Mapping[str, object]) -> HarvestRecipe:
"""A pair from CVE history: the fix commit and its parent, with the fix diff as the oracle (Req 9.4).
Nothing here is hand-labelled. The recipe names where to fetch both sides; what makes the pair *true* is
the fix diff itself, read by the model rather than annotated by us.
"""
return HarvestRecipe(
repo=str(entry.get("repo", "")),
fix_commit=str(entry.get("fix_commit", "")),
relpath=str(entry.get("relpath", "")),
function=str(entry.get("function", "") or ""),
)
def outcomes_from_rows(rows: Sequence[Mapping[str, object]]) -> list[PairOutcome]:
"""Adapt the ceiling artifact's rows, so calibration reads the measurement rather than re-running it."""
return [
PairOutcome(
pair=str(row.get("pair", "")),
family=str(row.get("family", "")),
slice=str(row.get("slice", "")),
vuln_rung=str(row.get("vuln_rung", "none")),
fixed_rung=str(row.get("fixed_rung", "none")),
reason=str(row.get("miss_cause", "") or ""),
)
for row in rows
]
__all__ = [
"CalibrationReport",
"HarvestRecipe",
"ModelGap",
"PairOutcome",
"calibrate",
"harvest_recipe",
"outcomes_from_rows",
]
-118
View File
@@ -1,118 +0,0 @@
"""The execution tier: deferred, adopted, and deliberately narrow (model-grounded-detection, Req 10).
This is the rung this project does **not** build. Clearwing already runs sandbox lifecycles, container pools,
sanitizer images, PoC replay and stability classification, and the corrective brief that started this feature
concluded — from this project's own defect history — that reimplementing that class of machinery is exactly
where we go wrong. So the module is a seam: it decides *eligibility*, calls an adopted tool, and records what
happened. A test asserts structurally that it imports no container or subprocess machinery and defines no
build/run/compile/patch function, because the failure mode to prevent is this file quietly growing a sandbox.
Eligibility is narrow on purpose, and both halves matter:
* **Only a family a static model cannot arbitrate.** Memory safety in C turns on runtime layout, so a crash
is the right oracle. Every web/logic family has taint reachability, guard dominance or constant
abstraction, and Req 10.3 forbids any of them depending on this tier.
* **Only a finding the model could not decide.** The tier escalates uncertainty; it never re-checks a verdict
the model already reached.
And a failure to reproduce is *not* a refutation. A sandbox that does not trigger a bug has not shown the bug
is absent — it has shown this attempt did not trigger it. The candidate stays exactly where the model left it.
"""
from __future__ import annotations
import os
from dataclasses import dataclass
from typing import Protocol
from .ladder import Rung
# Families whose bugs a static model genuinely cannot arbitrate. Everything else is served by taint
# reachability, guard dominance or constant abstraction, and must not wait on a sandbox.
EXECUTION_FAMILIES = frozenset({"memory"})
# Rungs that mean "the model could not decide". Anything above is already arbitrated.
UNDECIDED = frozenset({"suspicion", ""})
CLEARWING_ENV = "OPENULTRASAST_CLEARWING"
_OFF = frozenset({"0", "off", "false", "no"})
_ON = frozenset({"1", "on", "true", "yes"})
@dataclass(frozen=True)
class Candidate:
"""What the execution tier is asked about: a finding the static model left undecided."""
finding_id: str
family: str
rung: str
path: str
line: int | None = None
@dataclass(frozen=True)
class Outcome:
"""What the tier concluded. ``rung`` is ``None`` whenever nothing was established."""
rung: Rung | None
reason: str
class ExecutionBackend(Protocol):
def available(self) -> bool: ...
def reproduce(self, candidate: Candidate) -> bool: ...
@dataclass
class NullExecutionBackend:
"""No adopted tool present. The normal case, and not an error."""
def available(self) -> bool:
return False
def reproduce(self, candidate: Candidate) -> bool:
del candidate
return False
def eligible(candidate: Candidate) -> bool:
"""Is this candidate one the execution tier should even look at?"""
return candidate.family in EXECUTION_FAMILIES and candidate.rung in UNDECIDED
def confirm(candidate: Candidate, *, backend: ExecutionBackend | None = None) -> Outcome:
"""Escalate one undecided candidate to the adopted execution tier, if there is one."""
if not eligible(candidate):
return Outcome(
rung=None,
reason=f"not_eligible: family {candidate.family!r} at rung {candidate.rung!r} is arbitrated statically",
)
engine = backend if backend is not None else resolve_execution_backend()
if not engine.available():
return Outcome(rung=None, reason="clearwing_unavailable: no adopted execution tier on this machine")
if not engine.reproduce(candidate):
# Deliberate wording: this is not a refutation. Failing to trigger a bug is not showing it is absent.
return Outcome(rung=None, reason="not_reproduced: the attempt did not trigger it; the finding is unchanged")
return Outcome(rung=Rung.EXECUTION_CONFIRMED, reason="reproduced in the adopted sandbox")
def resolve_execution_backend() -> ExecutionBackend:
"""The adopted tier for this machine. Absent by default; the environment flag is what the matrix drives."""
flag = os.environ.get(CLEARWING_ENV, "").strip().lower()
if flag in _OFF or flag not in _ON:
return NullExecutionBackend()
return NullExecutionBackend() # the adopted tool is wired here when a class needs it (Req 10.1)
__all__ = [
"CLEARWING_ENV",
"EXECUTION_FAMILIES",
"Candidate",
"ExecutionBackend",
"NullExecutionBackend",
"Outcome",
"confirm",
"eligible",
"resolve_execution_backend",
]
-136
View File
@@ -1,136 +0,0 @@
"""The LLM proposes; the model disposes (model-grounded-detection, Req 8).
This module is where the prior architecture's premise is inverted. There, the LLM was an oracle whose noise
had to be averaged over K runs, floored per family, and gated by a sign test. Here it answers one bounded,
typed question about a candidate it was *handed*, and its answer is checked against the CPG. Three outcomes,
and they are exhaustive:
the model entails it report `model_entailed`, and never call the model at all
the model contradicts it drop the claim, recording the contradiction
the model cannot decide report `suspicion`, honestly
Nothing here averages, votes, retries for agreement, or maintains a budget: a deterministic arbiter removes
the need for every one of those. The LLM is consulted only for the residual the graph cannot settle — whether
a sanitizer on a real flow is actually sufficient — and even that answer only ever *confirms* a flow the CPG
already found. It can never conjure one.
"""
from __future__ import annotations
import json
import logging
from collections.abc import Mapping
from typing import Any
from ..cpg.backend import CpgResult
from ..redaction import redact_secrets
from .ladder import Rung, Verdict
from .specs import TaintSpec
from .taint import verdict as taint_verdict
logger = logging.getLogger(__name__)
QUESTION = """You are judging ONE candidate a static model already found. Do not look for other issues.
A dataflow analysis resolved this path in {language} code:
source: {source}
sink: {sink} (line {line}, function {function})
a sanitizer appears on the path: {sanitized}
The graph has established that the value reaches the sink. The only open question is whether the sanitizer on
that path actually makes it safe for the {family} weakness class.
Answer ONLY with JSON: {{"vulnerable": true|false, "why": "<one sentence>"}}"""
def judge(
cpg: CpgResult,
spec: TaintSpec,
*,
function: str = "",
client: Any | None = None,
model: str = "",
parameter_sources: bool = False,
) -> Verdict:
"""Arbitrate one candidate. Always returns a verdict; ``suspicion`` is a real answer, not a failure."""
answer = taint_verdict(cpg, spec, function=function, parameter_sources=parameter_sources)
if answer is not None and answer.rung is Rung.ENTAILED:
return answer # the graph decided; the model is not consulted
if answer is None:
# No resolved flow. Whatever anyone claims about this site, the model saw nothing to support it.
return Verdict(
rung=Rung.SUSPICION,
family=spec.family,
contradiction="the model resolved no flow: no source reaches this sink",
)
# A real flow through a sanitizer. Whether that sanitizer suffices is a semantic judgement the graph
# cannot make, so it is the one thing the LLM is asked -- about evidence the model itself produced.
if client is None:
return Verdict(
rung=Rung.SUSPICION,
family=spec.family,
witness=answer.witness,
contradiction="no chat endpoint: the sanitizer's sufficiency was never judged",
)
said_vulnerable = _ask(client, model, spec, answer.witness, function)
if said_vulnerable is None:
return Verdict(rung=Rung.SUSPICION, family=spec.family, witness=answer.witness, contradiction="the judge returned no usable answer")
if not said_vulnerable:
return Verdict(
rung=Rung.SUSPICION,
family=spec.family,
witness=answer.witness,
contradiction="the judge held the sanitizer on this path sufficient",
)
return answer # CORROBORATED: a real flow the judge agrees is not neutralised
def _ask(client: Any, model: str, spec: TaintSpec, witness: str, function: str) -> bool | None:
"""One question, one answer. No tools, no retries for agreement, no second opinion."""
source, _, sink = witness.partition(" -> ")
line = ""
if "(line " in sink:
sink, _, tail = sink.partition(" (line ")
line = tail.split(",")[0].strip(") ")
prompt = redact_secrets(
QUESTION.format(
language=spec.language,
source=source.strip(),
sink=sink.strip(),
line=line or "?",
function=function or "?",
sanitized="yes",
family=spec.family,
)
)
try:
response = client.complete(
model=model,
messages=[{"role": "user", "content": prompt}],
tools=[], # the candidate is given; the model never searches for it
json_object=True,
)
except Exception as exc: # noqa: BLE001 -- an endpoint failure is a degradation, never a verdict
logger.warning("judge call failed: %s", exc)
return None
return _vulnerable(getattr(response, "content", None))
def _vulnerable(content: object) -> bool | None:
if not isinstance(content, str) or not content.strip():
return None
try:
payload = json.loads(content)
except json.JSONDecodeError:
return None
if isinstance(payload, Mapping) and isinstance(payload.get("vulnerable"), bool):
return bool(payload["vulnerable"])
return None
__all__ = ["QUESTION", "judge"]
+2 -2
View File
@@ -4,8 +4,8 @@ Everything before this module built an *arbiter* — something that judges a flo
found on its own. Nothing proposed candidates to it, which is why the arbiter's recall was never comparable to
a detector's: it could only report what it happened to find unaided.
This is the wiring the architecture was designed around, and it turns on a distinction that `judge` alone
conflated:
This is the wiring the architecture was designed around, and it turns on a distinction that the first judge
(`model/judge.py`, deleted 2026-10-02 because this module replaced it) conflated:
the model CONTRADICTS it has coverage at this site and says no — a sanitizer lies on the path, or the
sink is written in its declared-safe shape. The claim is dropped (Req 8.2).
+1 -1
View File
@@ -1,4 +1,4 @@
"""Secret redaction for traces and reports (Phase 15 hardening).
"""Secret redaction for traces and reports (Phase 15 hardening of the retired openrouter-sast-harness plan).
Scan artifacts (trace events, reports) can quote source code that contains live
credentials. This module masks well-known secret shapes before they are persisted, so
+1 -1
View File
@@ -1,4 +1,4 @@
"""Trail of Bits skill index + router (Phase 14).
"""Trail of Bits skill index + router (Phase 14 of the retired openrouter-sast-harness plan).
Security skills are scoped expertise, not global prompt bloat. Each skill is a small
descriptor with routing metadata (languages, tags, vulnerability classes, stages) and
-88
View File
@@ -1,88 +0,0 @@
"""Slot-contract discipline for model-disabled deterministic stages (task 6.3).
The deterministic scan stages carry their work in named *slots* and run with model
invocation disabled (``skip_model = True``). Each stage declares a read/write slot
allow-list that is validated on every lifecycle hook, preserving the
``ProcessorSpec.reads/writes`` provenance discipline of the sync ``HarnessRuntime``
(the top-level driver).
This is the zero-dependency substrate for the deterministic pipeline.
"""
from __future__ import annotations
from collections.abc import Mapping
from typing import Any, Protocol
from .harness import HarnessContractError
class SlotContractError(HarnessContractError):
"""Raised when a stage reads or writes a slot outside its declared allow-list."""
class SlotContractMixin:
"""A model-disabled stage that carries work in slots under a read/write allow-list."""
name: str = "slot-stage"
version: str = "1"
reads_slots: tuple[str, ...] = ()
writes_slots: tuple[str, ...] = ()
skip_model: bool = True # deterministic: never invokes a model
def run(self, slots: Mapping[str, Any]) -> dict[str, Any]:
raise NotImplementedError
def validate_reads(self, slots: Mapping[str, Any]) -> None:
missing = sorted(slot for slot in self.reads_slots if slot not in slots)
if missing:
raise SlotContractError(f"{self.name}: missing read slots: {', '.join(missing)}")
def validate_writes(self, updates: Mapping[str, Any]) -> None:
undeclared = sorted(set(updates) - set(self.writes_slots))
if undeclared:
raise SlotContractError(f"{self.name}: undeclared write slots: {', '.join(undeclared)}")
def execute(self, slots: Mapping[str, Any]) -> dict[str, Any]:
"""Validate the read allow-list, run the stage, validate the write allow-list."""
self.validate_reads(slots) # pre-hook
updates = self.run(slots) or {}
if not isinstance(updates, dict):
raise SlotContractError(f"{self.name}: stage returned a non-dict update")
self.validate_writes(updates) # post-hook
return updates
class _Emitter(Protocol):
def emit(self, event_type: Any, stage: str | None, payload: dict[str, Any]) -> None: ...
class SlotPipeline:
"""Runs slot processors in order, validating contracts and enforcing model-disable."""
def __init__(self, processors: list[SlotContractMixin]) -> None:
for proc in processors:
if not proc.skip_model:
raise SlotContractError(f"{proc.name}: deterministic stage must be model-disabled (skip_model=True)")
self._processors = processors
@property
def processors(self) -> list[SlotContractMixin]:
return list(self._processors)
def run(self, initial_slots: Mapping[str, Any], *, runtime: _Emitter | None = None) -> dict[str, Any]:
"""Execute every stage; with ``runtime`` set, emit stage_start/stage_end lifecycle hooks."""
slots: dict[str, Any] = dict(initial_slots)
for proc in self._processors:
if runtime is not None:
runtime.emit(
"stage_start", proc.name, {"reads": list(proc.reads_slots), "writes": list(proc.writes_slots), "skip_model": True}
)
updates = proc.execute(slots)
slots.update(updates)
if runtime is not None:
runtime.emit("stage_end", proc.name, {"status": "succeeded", "wrote": sorted(updates)})
return slots
__all__ = ["SlotContractError", "SlotContractMixin", "SlotPipeline"]
-135
View File
@@ -1,135 +0,0 @@
"""Deterministic scan stages as model-disabled, slot-contracted processors (task 6.3).
Each stage is a :class:`slot_contract.SlotContractMixin` with ``skip_model = True`` and
a declared read/write slot allow-list. Running them through a :class:`SlotPipeline`
reproduces the deterministic quick-scan path byte-for-byte while enforcing the slot
contract on every lifecycle hook. The hunter and verifier stages are *not* here; this
module is the deterministic, model-free remainder that ``test_gate`` pins.
"""
from __future__ import annotations
from collections.abc import Mapping
from pathlib import Path
from typing import Any
from .findings import quick_scan_findings
from .mapping import analyze_entry_points, attach_reachability_hints
from .policy import load_policy
from .preprocess import preprocess_repository
from .rank import rank_targets
from .ruleset import DEFAULT_RULESET_DIR, load_ruleset
from .scoring import build_score_artifact
from .slot_contract import SlotContractMixin, SlotPipeline
class PreprocessProcessor(SlotContractMixin):
name = "preprocess"
reads_slots = ("root",)
writes_slots = ("snapshot", "targets")
def run(self, slots: Mapping[str, Any]) -> dict[str, Any]:
snapshot, targets = preprocess_repository(slots["root"])
return {"snapshot": snapshot, "targets": targets}
class EntryPointProcessor(SlotContractMixin):
name = "entry_point"
reads_slots = ("root", "targets")
writes_slots = ("targets", "entry_points")
def run(self, slots: Mapping[str, Any]) -> dict[str, Any]:
entry_points = analyze_entry_points(slots["root"], slots["targets"])
targets = attach_reachability_hints(slots["targets"], entry_points)
return {"targets": targets, "entry_points": entry_points}
class RankProcessor(SlotContractMixin):
name = "rank"
reads_slots = ("targets",)
writes_slots = ("rankings",)
def run(self, slots: Mapping[str, Any]) -> dict[str, Any]:
return {"rankings": rank_targets(slots["targets"])}
class QuickFindingsProcessor(SlotContractMixin):
name = "quick_findings"
reads_slots = ("root", "targets", "rankings", "ruleset", "policy")
writes_slots = ("findings",)
def run(self, slots: Mapping[str, Any]) -> dict[str, Any]:
findings = quick_scan_findings(slots["root"], slots["targets"], slots["rankings"], slots["ruleset"], slots["policy"])
return {"findings": findings}
class PolicyScoringProcessor(SlotContractMixin):
name = "policy_scoring"
reads_slots = ("findings",)
writes_slots = ("reported_findings", "shadow_findings")
def run(self, slots: Mapping[str, Any]) -> dict[str, Any]:
findings = slots["findings"]
shadow = [finding for finding in findings if finding.status == "shadow"]
reported = [finding for finding in findings if finding.status != "shadow"]
return {"reported_findings": reported, "shadow_findings": shadow}
class ScoreProcessor(SlotContractMixin):
name = "score"
reads_slots = ("reported_findings", "ruleset", "policy")
writes_slots = ("score",)
def run(self, slots: Mapping[str, Any]) -> dict[str, Any]:
cwe_by_rule = {rule.rule_id: rule.cwe for rule in slots["ruleset"]}
reported = slots["reported_findings"]
rule_cwe_by_id = {f.finding_id: cwe_by_rule.get(f.finding_id.split(":", 1)[0], "") for f in reported}
return {"score": build_score_artifact(reported, rule_cwe_by_id, slots["policy"])}
def deterministic_processors() -> list[SlotContractMixin]:
"""The ordered deterministic stages (preprocess → entry-point → rank → findings → policy → score)."""
return [
PreprocessProcessor(),
EntryPointProcessor(),
RankProcessor(),
QuickFindingsProcessor(),
PolicyScoringProcessor(),
ScoreProcessor(),
]
def build_deterministic_pipeline() -> SlotPipeline:
return SlotPipeline(deterministic_processors())
def run_quick_pipeline(
root: Path | str,
*,
ruleset: Any = None,
policy: Any = None,
runtime: Any = None,
) -> dict[str, Any]:
"""Run the deterministic quick-scan pipeline and return the final slot map.
Defaults to the bundled ruleset and the vendored policy, which makes the
``reported_findings`` slot byte-identical to ``findings.quick_scan_findings`` on
the same target.
"""
resolved_ruleset = ruleset if ruleset is not None else load_ruleset(DEFAULT_RULESET_DIR)
resolved_policy = policy if policy is not None else load_policy()
initial = {"root": Path(root), "ruleset": resolved_ruleset, "policy": resolved_policy}
return build_deterministic_pipeline().run(initial, runtime=runtime)
__all__ = [
"EntryPointProcessor",
"PolicyScoringProcessor",
"PreprocessProcessor",
"QuickFindingsProcessor",
"RankProcessor",
"ScoreProcessor",
"build_deterministic_pipeline",
"deterministic_processors",
"run_quick_pipeline",
]