chore: remove dead modules (judge, execution, calibrate, stage_processors, slot_contract)

The reachability-first audit (previous commit) found these five with no
import path from any entry point:

- model/judge.py: replaced by model/pipeline.py, which never imported it.
- model/execution.py: the MGD Req 10 execution seam, never built or called.
- model/calibrate.py: no CLI, no importer; learn/calibrate.py is the live one.
- stage_processors.py + slot_contract.py: kept only so test_gate could pin
  them; the scan runs findings.quick_scan_findings directly.

Their tests go with them, test_gate's zero-dependency list drops the two
entries, the audit lists drop their rows, and the manifest is regenerated:
150 modules, 145 load_bearing, 5 standalone, 0 orphaned.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
Marius Ciepluch
2026-10-02 07:22:35 +02:00
co-authored by Claude Opus 5.5
parent b7f1f3d05b
commit 3091ed42ea
13 changed files with 14 additions and 1217 deletions
+1 -6
View File
@@ -41,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",
@@ -91,8 +88,6 @@ STANDALONE = {
"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",
}
-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"]
-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",
]