2026-09-16 15:31:34 +02:00
|
|
|
"""Experimental recorded-veto evaluation for release milestone M1a.
|
|
|
|
|
|
|
|
|
|
Record every comparison and admission veto per raw finding instead of applying it, so
|
|
|
|
|
a maintainer can see which vetoes discard a detection the arbiter already reported.
|
|
|
|
|
|
|
|
|
|
This module never admits a defect, enables a capability or changes the production
|
|
|
|
|
result. The production comparison and admission have already run on the applied path.
|
|
|
|
|
Here the same policy functions run again over a *lifted* view of the same evidence in
|
|
|
|
|
which recognized completeness vetoes are removed and recorded. Failures are never
|
|
|
|
|
lifted: deadline exhaustion, unanswered queries, unreadable input, empty graphs and
|
|
|
|
|
unparsed files stay applied, because silence from failure is not a quiet fixed twin.
|
|
|
|
|
Every payload produced here carries an experimental label.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
from collections import Counter
|
|
|
|
|
from collections.abc import Sequence
|
|
|
|
|
from dataclasses import dataclass, replace
|
2026-09-16 19:59:12 +02:00
|
|
|
from pathlib import Path
|
2026-09-16 15:31:34 +02:00
|
|
|
|
|
|
|
|
from openultrasast.contracts import Contract
|
|
|
|
|
from openultrasast.model.contracts import ChangeContext, ExecutionBudget, QuestionIdentity
|
|
|
|
|
from openultrasast.model.scan import ModelScanResult
|
|
|
|
|
from openultrasast.push.contracts import PushComparison
|
|
|
|
|
from openultrasast.push.policy import (
|
|
|
|
|
AdmissionCandidate,
|
|
|
|
|
CandidateDelta,
|
|
|
|
|
CapabilityAdmission,
|
|
|
|
|
CapabilityKey,
|
|
|
|
|
EvidenceOperation,
|
|
|
|
|
_base_location,
|
|
|
|
|
_compare_evidence,
|
|
|
|
|
_context_boundaries,
|
|
|
|
|
_counterpart,
|
2026-09-16 19:59:12 +02:00
|
|
|
_grounded_template,
|
2026-09-16 15:31:34 +02:00
|
|
|
admit_candidates,
|
2026-09-16 18:55:31 +02:00
|
|
|
operation_provenance,
|
2026-09-16 15:31:34 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
FLAG = "--experimental-record-vetoes"
|
|
|
|
|
LABEL = (
|
|
|
|
|
"EXPERIMENTAL recorded-veto evaluation (release milestone M1a). Listed vetoes were recorded, not applied. "
|
|
|
|
|
"Nothing in this section is an alert, an admitted defect, a capability or a comparison result; the production "
|
|
|
|
|
"result and admission above remain authoritative."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Outcome reasons that scan.py applies to an *answered* question after the fact. Lifting them
|
|
|
|
|
# restores the answered rows; an unanswered or not-arbitrated question is never lifted.
|
|
|
|
|
LIFTED_OUTCOME_REASONS = frozenset({"change_context_incomplete", "graph_incomplete"})
|
|
|
|
|
# Physical vendor exclusion is retained; only its global completion veto is recorded here.
|
|
|
|
|
LIFTED_DEGRADATION_REASONS = frozenset({"vendor_semantics_unresolved"})
|
|
|
|
|
# Boundary kinds diagnosed in release-remediation-research.md as completeness vetoes rather
|
|
|
|
|
# than input failures. The kind is one colon-separated segment of a boundary string.
|
|
|
|
|
LIFTED_BOUNDARY_KINDS = frozenset(
|
|
|
|
|
{
|
|
|
|
|
"vendor_semantics_unresolved",
|
|
|
|
|
"ambiguous_line_correspondence",
|
|
|
|
|
"context_projection_unavailable",
|
|
|
|
|
"context_scope_empty",
|
|
|
|
|
"dynamic_external_or_depth_context_unresolved",
|
|
|
|
|
"context_location_unavailable",
|
|
|
|
|
"configuration_dependency_projection_unavailable",
|
|
|
|
|
"deleted_dependency_projection_unavailable",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
# Admission reasons that this flag can never remove; they are recorded with that explanation.
|
|
|
|
|
_EXPECTED_ADMISSION = {
|
|
|
|
|
"capability_unavailable": "no evaluated capability is enabled; the recorded-veto flag cannot enable one",
|
|
|
|
|
"capability_disabled": "capability declared but not enabled; the recorded-veto flag cannot enable one",
|
|
|
|
|
"capability_unevaluated": "capability not qualified; the recorded-veto flag cannot qualify one",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
class RecordedVeto(Contract):
|
|
|
|
|
stage: str
|
|
|
|
|
veto: str
|
|
|
|
|
detail: str
|
|
|
|
|
lifted: bool
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
class QuestionReceipt(Contract):
|
|
|
|
|
identity: QuestionIdentity
|
|
|
|
|
status: str
|
|
|
|
|
reason: str
|
|
|
|
|
rows: int | None
|
|
|
|
|
lifted: bool
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
class OutcomeCount(Contract):
|
|
|
|
|
status: str
|
|
|
|
|
reason: str
|
|
|
|
|
count: int
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
class ScanSummary(Contract):
|
|
|
|
|
findings: int
|
|
|
|
|
outcomes: tuple[OutcomeCount, ...]
|
|
|
|
|
lifted: tuple[OutcomeCount, ...]
|
|
|
|
|
degradations: tuple[str, ...]
|
|
|
|
|
lifted_degradations: tuple[str, ...]
|
|
|
|
|
boundaries_by_kind: tuple[OutcomeCount, ...]
|
|
|
|
|
changed_path_questions: tuple[QuestionReceipt, ...]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
class RecordedFinding(Contract):
|
|
|
|
|
family: str
|
|
|
|
|
site: str
|
|
|
|
|
rung: str
|
|
|
|
|
witness: str | None
|
|
|
|
|
question: QuestionIdentity | None
|
|
|
|
|
operation: EvidenceOperation | None
|
|
|
|
|
operation_method: str | None
|
|
|
|
|
base_location: str | None
|
|
|
|
|
production_novelty: str
|
|
|
|
|
production_reason: str
|
|
|
|
|
production_admission: tuple[str, ...]
|
|
|
|
|
recorded_novelty: str
|
|
|
|
|
recorded_reason: str
|
|
|
|
|
recorded_admission: tuple[str, ...]
|
|
|
|
|
vetoes: tuple[RecordedVeto, ...]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
class BaseOnlyFinding(Contract):
|
|
|
|
|
family: str
|
|
|
|
|
site: str
|
|
|
|
|
rung: str
|
|
|
|
|
witness: str | None
|
|
|
|
|
head_counterparts: tuple[QuestionReceipt, ...]
|
|
|
|
|
|
|
|
|
|
|
2026-09-16 19:59:12 +02:00
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
class EvaluationFinding(Contract):
|
|
|
|
|
"""A developer-readable rendering of one evaluation finding, for reading, never for alerting.
|
|
|
|
|
|
|
|
|
|
Consequence and repair come from a supplied declaration's templates through the same
|
|
|
|
|
grounded-template path the admitted case uses, so no prose is invented here. When no
|
|
|
|
|
declaration matches, both are absent with the reason stated. This record is produced outside
|
|
|
|
|
admission and cannot become an alert.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
family: str
|
|
|
|
|
path: str
|
|
|
|
|
line: int
|
|
|
|
|
novelty: str
|
|
|
|
|
reason: str
|
|
|
|
|
witness: str
|
|
|
|
|
change_evidence: str
|
|
|
|
|
provenance: str
|
|
|
|
|
question_function: str | None
|
|
|
|
|
engine_method: str | None
|
|
|
|
|
operation: str
|
|
|
|
|
operation_symbol: str
|
|
|
|
|
consequence: str | None
|
|
|
|
|
repair: str | None
|
|
|
|
|
unavailable: tuple[str, ...]
|
|
|
|
|
declaration: str | None
|
|
|
|
|
declaration_verdict: str | None
|
|
|
|
|
declaration_enabled: bool
|
|
|
|
|
|
|
|
|
|
|
2026-09-16 15:31:34 +02:00
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
class RecordedVetoReport(Contract):
|
|
|
|
|
label: str
|
|
|
|
|
flag: str
|
|
|
|
|
lifted_kinds: tuple[str, ...]
|
|
|
|
|
head: ScanSummary
|
|
|
|
|
base: ScanSummary | None
|
|
|
|
|
findings: tuple[RecordedFinding, ...]
|
|
|
|
|
base_only_findings: tuple[BaseOnlyFinding, ...]
|
|
|
|
|
lifted_coverage_reasons: tuple[str, ...]
|
|
|
|
|
remaining_coverage_reasons: tuple[str, ...]
|
2026-09-16 19:59:12 +02:00
|
|
|
evaluation: tuple[EvaluationFinding, ...] = ()
|
2026-09-16 15:31:34 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def boundary_kind(boundary: str) -> str | None:
|
|
|
|
|
"""Return the recognized lifted kind inside a boundary string, if any."""
|
|
|
|
|
return next((segment for segment in boundary.split(":") if segment in LIFTED_BOUNDARY_KINDS), None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _lift_scan(scan: ModelScanResult) -> tuple[ModelScanResult, Counter[str]]:
|
|
|
|
|
lifted: Counter[str] = Counter()
|
|
|
|
|
outcomes = []
|
|
|
|
|
for outcome in scan.question_outcomes:
|
|
|
|
|
if outcome.status == "unresolved" and outcome.reason in LIFTED_OUTCOME_REASONS and outcome.raw_rows_json is not None:
|
|
|
|
|
lifted["question:" + outcome.reason] += 1
|
|
|
|
|
outcomes.append(replace(outcome, status="completed", reason="recorded_veto:" + outcome.reason))
|
|
|
|
|
else:
|
|
|
|
|
outcomes.append(outcome)
|
|
|
|
|
degradations = []
|
|
|
|
|
for degradation in scan.degradations:
|
|
|
|
|
if str(degradation.get("reason")) in LIFTED_DEGRADATION_REASONS:
|
|
|
|
|
lifted["degradation:" + str(degradation.get("reason"))] += 1
|
|
|
|
|
else:
|
|
|
|
|
degradations.append(degradation)
|
|
|
|
|
scope = scan.scope
|
|
|
|
|
if scope is not None:
|
|
|
|
|
kept = []
|
|
|
|
|
for boundary in scope.unresolved_boundaries:
|
|
|
|
|
kind = boundary_kind(boundary)
|
|
|
|
|
if kind is None:
|
|
|
|
|
kept.append(boundary)
|
|
|
|
|
else:
|
|
|
|
|
lifted["boundary:" + kind] += 1
|
|
|
|
|
scope = replace(scope, unresolved_boundaries=tuple(kept))
|
|
|
|
|
return replace(scan, question_outcomes=tuple(outcomes), degradations=tuple(degradations), scope=scope), lifted
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _lift_context(context: ChangeContext) -> tuple[ChangeContext, tuple[str, ...]]:
|
|
|
|
|
lifted = tuple(b for b in context.unresolved_boundaries if boundary_kind(b) is not None)
|
|
|
|
|
kept = tuple(b for b in context.unresolved_boundaries if boundary_kind(b) is None)
|
|
|
|
|
return replace(context, unresolved_boundaries=kept), lifted
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _rows(raw: str | None) -> int | None:
|
|
|
|
|
if raw is None:
|
|
|
|
|
return None
|
|
|
|
|
rows = json.loads(raw)
|
|
|
|
|
return len(rows) if isinstance(rows, list) else None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _receipt(scan: ModelScanResult, lifted: ModelScanResult, identity: QuestionIdentity) -> QuestionReceipt | None:
|
|
|
|
|
original = next((o for o in scan.question_outcomes if o.identity == identity), None)
|
|
|
|
|
if original is None:
|
|
|
|
|
return None
|
|
|
|
|
after = next((o for o in lifted.question_outcomes if o.identity == identity), original)
|
|
|
|
|
return QuestionReceipt(identity, original.status, original.reason, _rows(original.raw_rows_json), after.status != original.status)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _summary(scan: ModelScanResult, lifted: ModelScanResult, counts: Counter[str], context: ChangeContext) -> ScanSummary:
|
|
|
|
|
outcomes = Counter((o.status, o.reason) for o in scan.question_outcomes)
|
|
|
|
|
boundaries = Counter(boundary_kind(b) or b.split(":")[0] for b in (scan.scope.unresolved_boundaries if scan.scope else ()))
|
|
|
|
|
changed = {context.decode_path(p) for p in context.changed_paths}
|
|
|
|
|
receipts = tuple(
|
|
|
|
|
receipt
|
|
|
|
|
for o in scan.question_outcomes
|
|
|
|
|
if o.identity.path in changed
|
|
|
|
|
for receipt in (_receipt(scan, lifted, o.identity),)
|
|
|
|
|
if receipt is not None
|
|
|
|
|
)
|
|
|
|
|
return ScanSummary(
|
|
|
|
|
len(scan.findings),
|
|
|
|
|
tuple(OutcomeCount(status, reason, count) for (status, reason), count in sorted(outcomes.items())),
|
|
|
|
|
tuple(
|
|
|
|
|
OutcomeCount(key.split(":", 1)[0], key.split(":", 1)[1], count)
|
|
|
|
|
for key, count in sorted(counts.items())
|
|
|
|
|
if not key.startswith("boundary:")
|
|
|
|
|
),
|
|
|
|
|
tuple(dict.fromkeys(str(d.get("reason")) for d in scan.degradations)),
|
|
|
|
|
tuple(key.split(":", 1)[1] for key in sorted(counts) if key.startswith("degradation:")),
|
|
|
|
|
tuple(OutcomeCount("boundary", kind, count) for kind, count in sorted(boundaries.items())),
|
|
|
|
|
receipts,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _method(op: EvidenceOperation) -> str | None:
|
2026-09-16 18:55:31 +02:00
|
|
|
return op.method
|
2026-09-16 15:31:34 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _boundary_vetoes(stage: str, boundaries: Sequence[str]) -> list[RecordedVeto]:
|
|
|
|
|
return [RecordedVeto(stage, boundary_kind(b) or b, b, boundary_kind(b) is not None) for b in boundaries]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _degradation_vetoes(stage: str, scan: ModelScanResult) -> list[RecordedVeto]:
|
|
|
|
|
return [
|
|
|
|
|
RecordedVeto(
|
|
|
|
|
stage,
|
|
|
|
|
"degradation:" + str(d.get("reason")),
|
|
|
|
|
json.dumps(dict(d), sort_keys=True, default=str),
|
|
|
|
|
str(d.get("reason")) in LIFTED_DEGRADATION_REASONS,
|
|
|
|
|
)
|
|
|
|
|
for d in scan.degradations
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _question_veto(stage: str, receipt: QuestionReceipt | None, identity: QuestionIdentity) -> list[RecordedVeto]:
|
|
|
|
|
if receipt is None:
|
|
|
|
|
return [RecordedVeto(stage, "question_missing", identity.question_id, False)]
|
|
|
|
|
if receipt.status == "completed":
|
|
|
|
|
return []
|
|
|
|
|
detail = f"{identity.question_id} status={receipt.status} rows={receipt.rows}"
|
|
|
|
|
return [RecordedVeto(stage, receipt.reason, detail, receipt.lifted)]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _finding_vetoes(
|
|
|
|
|
op: EvidenceOperation | None,
|
|
|
|
|
*,
|
|
|
|
|
head: ModelScanResult,
|
|
|
|
|
lifted_head: ModelScanResult,
|
|
|
|
|
base: ModelScanResult | None,
|
|
|
|
|
lifted_base: ModelScanResult | None,
|
|
|
|
|
context: ChangeContext,
|
|
|
|
|
recorded: CandidateDelta,
|
|
|
|
|
admission: Sequence[str],
|
|
|
|
|
) -> tuple[RecordedVeto, ...]:
|
|
|
|
|
vetoes: list[RecordedVeto] = []
|
|
|
|
|
if op is None:
|
|
|
|
|
vetoes.append(RecordedVeto("comparison", recorded.reason, "no completed head operation matches this finding after lifting", False))
|
|
|
|
|
else:
|
|
|
|
|
question = op.question
|
|
|
|
|
vetoes.extend(_question_veto("head_question", _receipt(head, lifted_head, question), question))
|
|
|
|
|
vetoes.extend(_boundary_vetoes("head_scan", _context_boundaries(head, question)))
|
|
|
|
|
vetoes.extend(_degradation_vetoes("head_scan", head))
|
|
|
|
|
method = _method(op)
|
2026-09-16 18:55:31 +02:00
|
|
|
provenance = operation_provenance(op)
|
|
|
|
|
if provenance != "exact":
|
2026-09-16 15:31:34 +02:00
|
|
|
vetoes.append(
|
|
|
|
|
RecordedVeto(
|
|
|
|
|
"identity",
|
|
|
|
|
"query_identity_differs_from_witness",
|
2026-09-16 18:55:31 +02:00
|
|
|
f"provenance={provenance}; question function={question.function!r}; engine method={method!r}",
|
2026-09-16 15:31:34 +02:00
|
|
|
False,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
if base is None or lifted_base is None:
|
|
|
|
|
vetoes.append(RecordedVeto("base_scan", "base_unavailable", "no base scan was produced", False))
|
|
|
|
|
else:
|
|
|
|
|
counterpart = _counterpart(question, context)
|
|
|
|
|
vetoes.extend(_question_veto("base_question", _receipt(base, lifted_base, counterpart), counterpart))
|
|
|
|
|
vetoes.extend(_boundary_vetoes("base_scan", _context_boundaries(base)))
|
|
|
|
|
vetoes.extend(_degradation_vetoes("base_scan", base))
|
|
|
|
|
if base.scope is not None:
|
|
|
|
|
if not base.scope.population_complete:
|
|
|
|
|
vetoes.append(RecordedVeto("base_scan", "population_incomplete", "base census not asserted complete", False))
|
|
|
|
|
for deferred in base.scope.deferred:
|
|
|
|
|
if deferred.reason != "tier_zero":
|
|
|
|
|
vetoes.append(RecordedVeto("base_scan", "deferred:" + deferred.reason, deferred.identity.question_id, False))
|
|
|
|
|
pending = [o for o in lifted_base.question_outcomes if o.status != "completed"]
|
|
|
|
|
if pending:
|
|
|
|
|
detail = f"{len(pending)} of {len(lifted_base.question_outcomes)} base questions not completed after lifting"
|
|
|
|
|
vetoes.append(RecordedVeto("base_scan", "questions_not_completed", detail, False))
|
|
|
|
|
vetoes.extend(_boundary_vetoes("change_context", context.unresolved_boundaries))
|
|
|
|
|
if op is not None and recorded.novelty == "unknown":
|
|
|
|
|
location = _base_location(op, context)
|
|
|
|
|
where = f"{location[0]}:{location[1]}" if location else "unresolved (operation on a changed line or ambiguous)"
|
|
|
|
|
vetoes.append(RecordedVeto("comparison", recorded.reason, "base location " + where, False))
|
|
|
|
|
for reason in admission:
|
|
|
|
|
explanation = _EXPECTED_ADMISSION.get(reason, "production admission reason on the lifted candidate")
|
|
|
|
|
vetoes.append(RecordedVeto("admission", reason, explanation, False))
|
|
|
|
|
return tuple(vetoes)
|
|
|
|
|
|
|
|
|
|
|
2026-09-16 19:59:12 +02:00
|
|
|
def load_declarations(path: Path) -> tuple[CapabilityAdmission, ...]:
|
|
|
|
|
"""Read experimental declarations for the evaluation rendering only.
|
|
|
|
|
|
|
|
|
|
These never reach admission. The installed registry remains the sole source of eligibility,
|
|
|
|
|
so a declaration read here cannot enable a capability or emit an alert however it is written.
|
|
|
|
|
"""
|
|
|
|
|
content = path.read_bytes()[: 1024**2 + 1]
|
|
|
|
|
if len(content) > 1024**2:
|
|
|
|
|
raise ValueError("declaration_size_limit")
|
|
|
|
|
payload = json.loads(content)
|
|
|
|
|
if not isinstance(payload, list):
|
|
|
|
|
raise ValueError("declarations must be a list")
|
|
|
|
|
return tuple(CapabilityAdmission.from_payload(item) for item in payload)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _evaluation(delta: CandidateDelta, declarations: Sequence[CapabilityAdmission], semantics: str) -> EvaluationFinding | None:
|
|
|
|
|
"""Render one established finding. Returns None for anything not attributed to the change."""
|
|
|
|
|
op = delta.head_operation
|
|
|
|
|
if op is None or delta.novelty not in ("new", "worsened") or delta.witness is None or not delta.change_evidence:
|
|
|
|
|
return None
|
|
|
|
|
key = CapabilityKey(op.question.language, "unspecified", "unspecified", delta.family, op.mechanism, "unreviewed", semantics)
|
|
|
|
|
matching = [declaration for declaration in declarations if declaration.key == key]
|
|
|
|
|
symbol = op.operation.partition("(")[0].strip()
|
|
|
|
|
unavailable: list[str] = []
|
|
|
|
|
consequence = repair = None
|
|
|
|
|
declaration = matching[0] if len(matching) == 1 else None
|
|
|
|
|
if not matching:
|
|
|
|
|
unavailable.append("no_matching_declaration")
|
|
|
|
|
elif len(matching) > 1:
|
|
|
|
|
unavailable.append("ambiguous_declaration")
|
|
|
|
|
if declaration is not None:
|
|
|
|
|
consequence = _grounded_template(declaration.consequence_template, op, key.context)
|
|
|
|
|
repair = _grounded_template(declaration.repair_template, op, key.context)
|
|
|
|
|
if consequence is None:
|
|
|
|
|
unavailable.append("consequence_template_ungrounded")
|
|
|
|
|
if repair is None:
|
|
|
|
|
unavailable.append("repair_template_ungrounded")
|
|
|
|
|
if symbol not in declaration.operation_symbols:
|
|
|
|
|
unavailable.append("declaration_does_not_cover_operation_symbol")
|
|
|
|
|
return EvaluationFinding(
|
|
|
|
|
delta.family,
|
|
|
|
|
op.path,
|
|
|
|
|
op.line,
|
|
|
|
|
delta.novelty,
|
|
|
|
|
delta.reason,
|
|
|
|
|
delta.witness,
|
|
|
|
|
delta.change_evidence[0],
|
|
|
|
|
operation_provenance(op),
|
|
|
|
|
op.question.function,
|
|
|
|
|
op.method,
|
|
|
|
|
op.operation,
|
|
|
|
|
symbol,
|
|
|
|
|
consequence,
|
|
|
|
|
repair,
|
|
|
|
|
tuple(unavailable),
|
|
|
|
|
declaration.evaluation_id if declaration else None,
|
|
|
|
|
declaration.verdict if declaration else None,
|
|
|
|
|
bool(declaration.enabled) if declaration else False,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2026-09-16 15:31:34 +02:00
|
|
|
def record_vetoes(
|
|
|
|
|
*,
|
|
|
|
|
head: ModelScanResult,
|
|
|
|
|
base: ModelScanResult | None,
|
|
|
|
|
context: ChangeContext,
|
|
|
|
|
comparison: PushComparison,
|
|
|
|
|
semantics: str,
|
|
|
|
|
production: Sequence[CandidateDelta],
|
|
|
|
|
production_admission: dict[str, tuple[str, ...]],
|
|
|
|
|
coverage_reasons: Sequence[str],
|
|
|
|
|
capabilities: Sequence[CapabilityAdmission] = (),
|
2026-09-16 19:59:12 +02:00
|
|
|
declarations: Sequence[CapabilityAdmission] = (),
|
2026-09-16 15:31:34 +02:00
|
|
|
execution_budget: ExecutionBudget | None = None,
|
|
|
|
|
) -> RecordedVetoReport:
|
|
|
|
|
"""Re-run the production comparison and admission over lifted evidence and list what was lifted.
|
|
|
|
|
|
|
|
|
|
`production` and `production_admission` (keyed by delta.defect_id) are the applied results the
|
|
|
|
|
runner already produced; they are retained beside the recorded outcome for every finding.
|
|
|
|
|
"""
|
|
|
|
|
lifted_head, head_counts = _lift_scan(head)
|
|
|
|
|
lifted_base, base_counts = _lift_scan(base) if base is not None else (None, Counter())
|
|
|
|
|
lifted_context, lifted_boundaries = _lift_context(context)
|
|
|
|
|
lifted_reasons = tuple(r for r in coverage_reasons if boundary_kind(r) is not None or r in LIFTED_OUTCOME_REASONS)
|
|
|
|
|
remaining = tuple(r for r in coverage_reasons if r not in lifted_reasons)
|
|
|
|
|
recorded = _compare_evidence(
|
|
|
|
|
lifted_head,
|
|
|
|
|
lifted_base,
|
|
|
|
|
context=lifted_context,
|
|
|
|
|
head_semantics=semantics,
|
|
|
|
|
base_semantics=semantics,
|
|
|
|
|
execution_budget=execution_budget,
|
|
|
|
|
)
|
|
|
|
|
candidates = tuple(
|
|
|
|
|
AdmissionCandidate(
|
|
|
|
|
delta,
|
|
|
|
|
CapabilityKey(
|
|
|
|
|
delta.head_operation.question.language if delta.head_operation else "unknown",
|
|
|
|
|
"unspecified",
|
|
|
|
|
"unspecified",
|
|
|
|
|
delta.family,
|
|
|
|
|
delta.head_operation.mechanism if delta.head_operation else "taint",
|
|
|
|
|
"unreviewed",
|
|
|
|
|
semantics,
|
|
|
|
|
),
|
|
|
|
|
comparison,
|
|
|
|
|
remaining,
|
|
|
|
|
)
|
|
|
|
|
for delta in recorded
|
|
|
|
|
)
|
|
|
|
|
# Only the per-candidate reasons are retained. Any defect the lifted path would admit is
|
|
|
|
|
# deliberately dropped here: this section is a diagnostic record, never an alert channel.
|
|
|
|
|
admitted = admit_candidates(candidates, capabilities=capabilities)
|
|
|
|
|
recorded_admission = {d.candidate.delta.defect_id: d.reasons for d in admitted.dispositions}
|
|
|
|
|
applied = {(c.family, c.site, c.witness): c for c in production}
|
|
|
|
|
findings = []
|
|
|
|
|
for finding, delta in zip(head.findings, recorded, strict=True):
|
|
|
|
|
before = applied.get((finding.family, finding.site, finding.witness or None))
|
|
|
|
|
op = delta.head_operation
|
|
|
|
|
location = _base_location(op, context) if op else None
|
|
|
|
|
admission = recorded_admission.get(delta.defect_id, ())
|
|
|
|
|
findings.append(
|
|
|
|
|
RecordedFinding(
|
|
|
|
|
finding.family,
|
|
|
|
|
finding.site,
|
|
|
|
|
finding.rung.value,
|
|
|
|
|
finding.witness or None,
|
|
|
|
|
op.question if op else None,
|
|
|
|
|
op,
|
|
|
|
|
_method(op) if op else None,
|
|
|
|
|
f"{location[0]}:{location[1]}" if location else None,
|
|
|
|
|
before.novelty if before else "unknown",
|
|
|
|
|
before.reason if before else "production_candidate_missing",
|
|
|
|
|
production_admission.get(before.defect_id, ()) if before else (),
|
|
|
|
|
delta.novelty,
|
|
|
|
|
delta.reason,
|
|
|
|
|
admission,
|
|
|
|
|
_finding_vetoes(
|
|
|
|
|
op,
|
|
|
|
|
head=head,
|
|
|
|
|
lifted_head=lifted_head,
|
|
|
|
|
base=base,
|
|
|
|
|
lifted_base=lifted_base,
|
|
|
|
|
context=context,
|
|
|
|
|
recorded=delta,
|
|
|
|
|
admission=admission,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
base_only = []
|
|
|
|
|
if base is not None and lifted_base is not None:
|
|
|
|
|
head_sites = {(f.family, f.site) for f in head.findings}
|
|
|
|
|
for finding in base.findings:
|
|
|
|
|
if (finding.family, finding.site) in head_sites:
|
|
|
|
|
continue
|
|
|
|
|
path = finding.site.split(":", 1)[0]
|
|
|
|
|
counterparts = tuple(
|
|
|
|
|
receipt
|
|
|
|
|
for o in head.question_outcomes
|
|
|
|
|
if o.identity.path == path and o.identity.family == finding.family
|
|
|
|
|
for receipt in (_receipt(head, lifted_head, o.identity),)
|
|
|
|
|
if receipt is not None
|
|
|
|
|
)
|
|
|
|
|
base_only.append(BaseOnlyFinding(finding.family, finding.site, finding.rung.value, finding.witness or None, counterparts))
|
|
|
|
|
kinds = (
|
|
|
|
|
*("question:" + r for r in LIFTED_OUTCOME_REASONS),
|
|
|
|
|
*("degradation:" + r for r in LIFTED_DEGRADATION_REASONS),
|
|
|
|
|
*("boundary:" + k for k in LIFTED_BOUNDARY_KINDS),
|
|
|
|
|
)
|
|
|
|
|
return RecordedVetoReport(
|
|
|
|
|
LABEL,
|
|
|
|
|
FLAG,
|
|
|
|
|
tuple(sorted(kinds)),
|
|
|
|
|
_summary(head, lifted_head, head_counts, context),
|
|
|
|
|
_summary(base, lifted_base, base_counts, context) if base is not None and lifted_base is not None else None,
|
|
|
|
|
tuple(findings),
|
|
|
|
|
tuple(base_only),
|
|
|
|
|
tuple(dict.fromkeys((*lifted_reasons, *lifted_boundaries))),
|
|
|
|
|
remaining,
|
2026-09-16 19:59:12 +02:00
|
|
|
# Rendered from the PRODUCTION comparison, so the demonstration shows what the production
|
|
|
|
|
# rules established, not what the lifted view would have.
|
|
|
|
|
tuple(item for delta in production for item in (_evaluation(delta, declarations, semantics),) if item is not None),
|
2026-09-16 15:31:34 +02:00
|
|
|
)
|