feat(detection-core): complete single-writer v2 cutover

This commit is contained in:
Marius Ciepluch
2026-11-04 09:54:47 +01:00
parent 10e93e09b7
commit 586543d799
28 changed files with 1106 additions and 1417 deletions
+1 -1
View File
@@ -398,7 +398,7 @@
- _Requirements: 14.3, 15.3_
- _Boundary: Migration Tests and Operational Tooling_
- _Depends: 10.4, 10.5_
- [ ] 10.7 Delete v1 write capability and publish the single v2 surface atomically
- [x] 10.7 Delete v1 write capability and publish the single v2 surface atomically
- Require every inventory entry to be terminal and the migration-time production-consumer boundary to be zero before deletion begins.
- Delete the compatibility namespace, v1 writer facade, and temporary shims while publishing the canonical v2 modules and top-level exports in the same integration change.
- Prove old top-level and submodule imports and writes fail and that no v1 serializer or converter remains.
+3 -85
View File
@@ -1,87 +1,5 @@
"""Detection core: the shared security-trace contract package.
"""Family-neutral detection-core v2 public surface."""
Owns contracts only -- no analysis logic. Arms implement the trace contract;
runners and consumers adopt coverage, budget, provenance, and admission contracts.
"""
from openultrasast.core import admission, consumer, verdict
from openultrasast.core.legacy_v1 import (
REPORT_SCHEMA_KEYS,
AdmissionManifest,
AdmissionManifestError,
BoundedSilence,
Budget,
BudgetGateFailure,
Complexity,
Coverage,
FactPack,
FactPackError,
FittedComponentBlocked,
Guard,
Hop,
InstrumentFailure,
Judgment,
ProbeResult,
ProvenanceMismatch,
ProvenClaim,
PushRequest,
PushVerdict,
ResolutionOutcome,
ShortcutDetected,
TraceRecord,
Witness,
admit_fact_pack,
as_clean,
assert_no_shortcut,
check_budget,
check_reads,
consume_verdict,
derive_budget,
require_admission_evidence,
reversed_pair_probe,
stamp,
validate_admission_manifest,
validate_report_schema,
verify_fact_pack,
verify_stamp,
)
__all__ = [
"REPORT_SCHEMA_KEYS",
"AdmissionManifest",
"AdmissionManifestError",
"BoundedSilence",
"Budget",
"BudgetGateFailure",
"Complexity",
"Coverage",
"FactPack",
"FactPackError",
"FittedComponentBlocked",
"Guard",
"Hop",
"InstrumentFailure",
"Judgment",
"ProbeResult",
"ProvenClaim",
"ProvenanceMismatch",
"PushRequest",
"PushVerdict",
"ResolutionOutcome",
"ShortcutDetected",
"TraceRecord",
"Witness",
"admit_fact_pack",
"as_clean",
"assert_no_shortcut",
"check_budget",
"check_reads",
"consume_verdict",
"derive_budget",
"require_admission_evidence",
"reversed_pair_probe",
"stamp",
"validate_admission_manifest",
"validate_report_schema",
"verify_fact_pack",
"verify_stamp",
]
__all__ = ["admission", "consumer", "verdict"]
@@ -59,7 +59,7 @@ from openultrasast.core.semantics import (
SourceLocation,
)
from openultrasast.core.validation import EvidenceValidationInput
from openultrasast.core.verdict_v2 import BoundedSatisfaction, BoundedSilence, DefectClaim
from openultrasast.core.verdict import BoundedSatisfaction, BoundedSilence, DefectClaim
_DATACLASSES: tuple[type[object], ...] = (
ScopeKey,
@@ -1,19 +0,0 @@
"""Temporary explicit import shim for the pre-v2 fact-pack contract."""
from openultrasast.core.legacy_v1.contracts import (
FactPackError,
LegacyFactPack,
admit_fact_pack,
admit_pack_file,
load_pack_file,
verify_fact_pack,
)
__all__ = [
"FactPackError",
"LegacyFactPack",
"admit_fact_pack",
"admit_pack_file",
"load_pack_file",
"verify_fact_pack",
]
+1 -1
View File
@@ -16,7 +16,7 @@ from openultrasast.core.proof import (
ProofVerificationReceipt,
derive_producer_identity_digest,
)
from openultrasast.core.verdict_v2 import DefectClaim
from openultrasast.core.verdict import DefectClaim
def _validate_claim_binding(claim: DefectClaim, attachment: ProofAttachment) -> None:
+160 -12
View File
@@ -1,17 +1,165 @@
"""Temporary explicit import shim for the pre-v2 admission contract."""
"""Canonical v2 admission facade."""
from openultrasast.core.legacy_v1.contracts import (
REPORT_SCHEMA_KEYS,
AdmissionManifest,
AdmissionManifestError,
validate_admission_manifest,
validate_report_schema,
from openultrasast.core.admission_case import (
ADMISSION_CASE_RELEASE_PAYLOAD_SCHEMA,
ADMISSION_CASE_RELEASE_SCHEMA,
RELEASED_CASE_BINDING_SCHEMA,
AdmissionCaseRelease,
AdmissionCaseReleaseError,
AdmissionCaseResult,
AdmissionCaseRole,
ReleasedCaseBinding,
create_admission_case_result,
derive_admission_case_release_digest,
derive_admission_case_release_payload_digest,
derive_admission_case_result_digest,
derive_case_coverage_digest,
derive_released_case_binding_digest,
validate_admission_case_release,
validate_admission_case_shape,
)
from openultrasast.core.admission_context import (
ADMISSION_VALIDATION_CONTEXT_SCHEMA,
AdmissionContextError,
AdmissionValidationContext,
derive_admission_validation_context_digest,
derive_analysis_identity,
derive_fact_schema_digest,
derive_outcome_mapping_digest,
derive_semantic_vocabulary_digest,
make_admission_validation_context,
validate_admission_context,
)
from openultrasast.core.admission_feasibility import (
AdmissionFeasibilityError,
FeasibleCountAssignment,
ThresholdFeasibility,
evaluate_threshold_feasibility,
)
from openultrasast.core.admission_fitted import (
REPOSITORY_POPULATION_MANIFEST_SCHEMA,
FittedComponentAuthorizationError,
FittedComponentEvidence,
authorize_fitted_component,
)
from openultrasast.core.admission_metrics import (
AdmissionMetricEvidence,
AdmissionMetricsError,
derive_admission_metrics,
derive_scoped_pair_key,
)
from openultrasast.core.admission_report import (
ADMISSION_REPLAY_SCHEMA,
ADMISSION_REPORT_SCHEMA,
AdmissionDecision,
AdmissionReplay,
AdmissionReportError,
AdmissionReportV2,
CaseBudgetEvaluation,
GateKind,
GateResult,
ThresholdEvaluation,
admission_replay_value,
admission_report_value,
score_admission,
validate_admission_replay,
)
from openultrasast.core.admission_review import (
AdmissionManifestV2,
AdmissionReviewError,
AdmissionThreshold,
PopulationCensus,
ScopeCensus,
derive_census_digest,
derive_census_payload_digest,
derive_manifest_digest,
derive_manifest_payload_digest,
derive_threshold_set_digest,
validate_reviewed_admission_payloads,
)
from openultrasast.core.admission_spend import (
ADMISSION_AUTHORIZATION_SCHEMA,
ADMISSION_PREPARATION_SCHEMA,
AdmissionAuthorization,
AdmissionPreparation,
AdmissionSpendError,
authorize_admission,
derive_admission_authorization_digest,
derive_admission_preparation_digest,
validate_pre_label_spend,
)
__all__ = [
"REPORT_SCHEMA_KEYS",
"AdmissionManifest",
"AdmissionManifestError",
"validate_admission_manifest",
"validate_report_schema",
"ADMISSION_AUTHORIZATION_SCHEMA",
"ADMISSION_CASE_RELEASE_PAYLOAD_SCHEMA",
"ADMISSION_CASE_RELEASE_SCHEMA",
"ADMISSION_PREPARATION_SCHEMA",
"ADMISSION_REPLAY_SCHEMA",
"ADMISSION_REPORT_SCHEMA",
"ADMISSION_VALIDATION_CONTEXT_SCHEMA",
"RELEASED_CASE_BINDING_SCHEMA",
"REPOSITORY_POPULATION_MANIFEST_SCHEMA",
"AdmissionAuthorization",
"AdmissionCaseRelease",
"AdmissionCaseReleaseError",
"AdmissionCaseResult",
"AdmissionCaseRole",
"AdmissionContextError",
"AdmissionDecision",
"AdmissionFeasibilityError",
"AdmissionManifestV2",
"AdmissionMetricEvidence",
"AdmissionMetricsError",
"AdmissionPreparation",
"AdmissionReplay",
"AdmissionReportError",
"AdmissionReportV2",
"AdmissionReviewError",
"AdmissionSpendError",
"AdmissionThreshold",
"AdmissionValidationContext",
"CaseBudgetEvaluation",
"FeasibleCountAssignment",
"FittedComponentAuthorizationError",
"FittedComponentEvidence",
"GateKind",
"GateResult",
"PopulationCensus",
"ReleasedCaseBinding",
"ScopeCensus",
"ThresholdEvaluation",
"ThresholdFeasibility",
"admission_replay_value",
"admission_report_value",
"authorize_admission",
"authorize_fitted_component",
"create_admission_case_result",
"derive_admission_authorization_digest",
"derive_admission_case_release_digest",
"derive_admission_case_release_payload_digest",
"derive_admission_case_result_digest",
"derive_admission_preparation_digest",
"derive_admission_metrics",
"derive_admission_validation_context_digest",
"derive_analysis_identity",
"derive_case_coverage_digest",
"derive_census_digest",
"derive_census_payload_digest",
"derive_fact_schema_digest",
"derive_manifest_digest",
"derive_manifest_payload_digest",
"derive_outcome_mapping_digest",
"derive_released_case_binding_digest",
"derive_scoped_pair_key",
"derive_semantic_vocabulary_digest",
"derive_threshold_set_digest",
"evaluate_threshold_feasibility",
"make_admission_validation_context",
"score_admission",
"validate_admission_case_release",
"validate_admission_case_shape",
"validate_admission_context",
"validate_admission_replay",
"validate_pre_label_spend",
"validate_reviewed_admission_payloads",
]
+1 -1
View File
@@ -19,7 +19,7 @@ from openultrasast.core.measurement import DenominatorPolicy, ScopeKey
from openultrasast.core.proof import ProofAttachment
from openultrasast.core.readiness import ReadinessResult
from openultrasast.core.validation import EvidenceValidationInput
from openultrasast.core.verdict_v2 import BoundedSatisfaction, BoundedSilence, DefectClaim
from openultrasast.core.verdict import BoundedSatisfaction, BoundedSilence, DefectClaim
ADMISSION_CASE_RELEASE_SCHEMA = "openultrasast.detection-core.admission-case-release.v2"
ADMISSION_CASE_RELEASE_PAYLOAD_SCHEMA = "openultrasast.detection-core.admission-case-release-payload.v2"
+1 -1
View File
@@ -36,7 +36,7 @@ from openultrasast.core.model import ModelStatus
from openultrasast.core.proof import ProofAttachment, attach_differential_proof
from openultrasast.core.readiness import ReadinessResult
from openultrasast.core.validation import validate_evidence_input
from openultrasast.core.verdict_v2 import DefectClaim, derive_verdict_outputs
from openultrasast.core.verdict import DefectClaim, derive_verdict_outputs
class AdmissionMetricsError(ValueError):
+338 -3
View File
@@ -1,5 +1,340 @@
"""Temporary explicit import shim for the pre-v2 consumer contract."""
"""Closed v2 push-consumer edge for validated bounded verdicts."""
from openultrasast.core.legacy_v1.contracts import PushRequest, PushVerdict, consume_verdict
from __future__ import annotations
__all__ = ["PushRequest", "PushVerdict", "consume_verdict"]
import time
from collections.abc import Sequence
from dataclasses import dataclass, replace
from openultrasast.core import canonical, verdict
from openultrasast.core.budget import BudgetDerivation, BudgetGateFailure, ChangeComplexity, check_budget
from openultrasast.core.evidence import AnalyzedScope, EvidenceRecord, SourceRegion, derive_packet_digest, validate_scope
from openultrasast.core.family import FamilyContract, family_contract_digest
from openultrasast.core.family_catalog import resolve_contract
from openultrasast.core.proof import ProofAttachment, ProofTrustPolicy, attach_differential_proof
from openultrasast.core.provenance import PopulationCustodyPort
from openultrasast.core.semantics import SourceLocation
from openultrasast.core.validation import EvidenceValidationContext, EvidenceValidationInput, validate_evidence_input
from openultrasast.core.verdict import (
BoundedSatisfaction,
DefectClaim,
VerdictOutputs,
derive_verdict_outputs,
)
PUSH_REQUEST_SCHEMA = "openultrasast.detection-core.push-request.v2"
CONSUMER_COVERAGE_SCHEMA = "openultrasast.detection-core.consumer-coverage.v2"
_monotonic_ns = time.monotonic_ns
class ConsumerContractError(ValueError):
"""A raw push request cannot produce a bounded v2 verdict."""
@dataclass(frozen=True)
class PushRequest:
request_id: str
scope: AnalyzedScope
workload_class: str
complexity: ChangeComplexity
budget: BudgetDerivation
@dataclass(frozen=True)
class PushVerdict:
request_digest: str
defects: tuple[DefectClaim, ...]
satisfactions: tuple[BoundedSatisfaction, ...]
silence: tuple[verdict.BoundedSilence, ...]
input_record_digests: tuple[str, ...]
complexity: ChangeComplexity
total_reply_ms: int
budget_derivation_digest: str
def _content_ref_value(value: canonical.ContentRef) -> canonical.JsonValue:
return {
"artifact_id": value.artifact_id,
"content_digest": value.content_digest,
"media_type": value.media_type,
"byte_length": value.byte_length,
"retention_policy_digest": value.retention_policy_digest,
"storage_receipt_digest": value.storage_receipt_digest,
}
def _scope_value(value: AnalyzedScope) -> canonical.JsonValue:
return {
"base_revision": value.base_revision,
"head_revision": value.head_revision,
"change_digest": value.change_digest,
"regions": tuple({"path": region.path, "start_line": region.start_line, "end_line": region.end_line} for region in value.regions),
}
def _request_value(request: PushRequest) -> canonical.JsonValue:
complexity = request.complexity
return {
"schema_version": PUSH_REQUEST_SCHEMA,
"scope": _scope_value(request.scope),
"workload_class": request.workload_class,
"complexity": {
"change_id": complexity.change_id,
"workload_class": complexity.workload_class,
"changed_regions": complexity.changed_regions,
"transition_count": complexity.transition_count,
"changed_bytes": complexity.changed_bytes,
"measurement": _content_ref_value(complexity.measurement),
},
"budget_derivation_digest": request.budget.derivation_digest,
}
def derive_push_request_id(request: PushRequest) -> str:
if not isinstance(request, PushRequest):
raise ConsumerContractError("request must be a PushRequest")
return canonical.content_digest(_request_value(request))
def make_push_request(
*,
scope: AnalyzedScope,
workload_class: str,
complexity: ChangeComplexity,
budget: BudgetDerivation,
) -> PushRequest:
provisional = PushRequest("0" * 64, scope, workload_class, complexity, budget)
return PushRequest(derive_push_request_id(provisional), scope, workload_class, complexity, budget)
def _validate_request(request: PushRequest) -> None:
if not isinstance(request, PushRequest):
raise ConsumerContractError("request must be a PushRequest")
try:
request.scope.__post_init__()
request.complexity.__post_init__()
request.budget.__post_init__()
except (AttributeError, TypeError, ValueError) as exc:
raise ConsumerContractError(f"push request is invalid: {exc}") from exc
if request.request_id != derive_push_request_id(request):
raise ConsumerContractError("push request identity cannot be re-derived")
if request.workload_class != request.complexity.workload_class or request.workload_class != request.budget.workload_class:
raise ConsumerContractError("push workload class does not match complexity and budget")
if request.complexity.change_id != request.scope.change_digest:
raise ConsumerContractError("complexity change identity does not match request scope")
if request.complexity.changed_regions != len(request.scope.regions):
raise ConsumerContractError("complexity changed-region count does not match request scope")
def _validated_records(
values: Sequence[EvidenceValidationInput],
context: EvidenceValidationContext,
custody: PopulationCustodyPort,
resolver: canonical.ContentResolver,
) -> tuple[EvidenceRecord, ...]:
selected = tuple(values)
if not selected:
raise ConsumerContractError("push inputs must be nonempty")
if not all(isinstance(value, EvidenceValidationInput) for value in selected):
raise ConsumerContractError("every push input must be an EvidenceValidationInput")
input_ids = tuple(value.input_digest for value in selected)
if len(set(input_ids)) != len(input_ids):
raise ConsumerContractError("duplicate validation input identity")
try:
return tuple(validate_evidence_input(value, context, custody, resolver) for value in selected)
except (AttributeError, LookupError, TypeError, ValueError) as exc:
raise ConsumerContractError(f"evidence validation failed: {exc}") from exc
def _contract(context: EvidenceValidationContext, record: EvidenceRecord) -> FamilyContract:
packet = record.packet
try:
return resolve_contract(context.family_catalog, packet.family_id, packet.family_contract_digest, packet.ecosystem)
except (LookupError, TypeError, ValueError) as exc:
raise ConsumerContractError(f"record family cannot be resolved: {exc}") from exc
def _coverage_digest(request: PushRequest, contract: FamilyContract, records: tuple[EvidenceRecord, ...]) -> str:
return canonical.content_digest(
{
"schema_version": CONSUMER_COVERAGE_SCHEMA,
"request_id": request.request_id,
"family_contract_digest": family_contract_digest(contract),
"record_ids": tuple(sorted(record.record_id for record in records)),
}
)
def _derive_outputs(
request: PushRequest,
records: tuple[EvidenceRecord, ...],
context: EvidenceValidationContext,
) -> VerdictOutputs:
grouped: dict[str, tuple[FamilyContract, list[EvidenceRecord]]] = {}
for record in records:
try:
validate_scope(record.packet, request.scope)
except (TypeError, ValueError) as exc:
raise ConsumerContractError(f"record scope is outside the push request: {exc}") from exc
contract = _contract(context, record)
digest = record.packet.family_contract_digest
if digest not in grouped:
grouped[digest] = (contract, [])
grouped[digest][1].append(record)
defects: list[DefectClaim] = []
satisfactions: list[BoundedSatisfaction] = []
silence: list[verdict.BoundedSilence] = []
try:
for digest in sorted(grouped):
contract, members = grouped[digest]
selected = tuple(members)
outputs = derive_verdict_outputs(selected, contract, _coverage_digest(request, contract, selected))
defects.extend(outputs.defects)
satisfactions.extend(outputs.satisfactions)
silence.extend(outputs.silence)
except (TypeError, ValueError) as exc:
raise ConsumerContractError(f"bounded verdict derivation failed: {exc}") from exc
return VerdictOutputs(tuple(defects), tuple(satisfactions), tuple(silence))
def _region_within(allowed: AnalyzedScope, candidate: SourceRegion) -> bool:
return any(
candidate.path == region.path and candidate.start_line >= region.start_line and candidate.end_line <= region.end_line
for region in allowed.regions
)
def _proof_target_index(claims: tuple[DefectClaim, ...]) -> dict[tuple[str, str, SourceLocation], int]:
keys: dict[tuple[str, str, SourceLocation], int] = {}
for index, claim in enumerate(claims):
key = (claim.record.packet.family_id, derive_packet_digest(claim.record.packet), claim.location)
if key in keys:
raise ConsumerContractError("proof target is ambiguous across defect claims")
keys[key] = index
return keys
def _apply_attachment(
request: PushRequest,
result: list[DefectClaim],
keys: dict[tuple[str, str, SourceLocation], int],
consumed: set[int],
attachment: ProofAttachment,
trust: ProofTrustPolicy,
resolver: canonical.ContentResolver,
) -> None:
if not isinstance(attachment, ProofAttachment):
raise ConsumerContractError("every proof attachment must use the v2 proof contract")
key = (attachment.family_id, attachment.packet_digest, attachment.location)
if key not in keys:
raise ConsumerContractError("proof attachment does not match one exact defect claim")
index = keys[key]
if index in consumed:
raise ConsumerContractError("multiple proof attachments target one defect claim")
if not _region_within(request.scope, result[index].change_anchor):
raise ConsumerContractError("proof claim anchor is outside the push request scope")
result[index] = attach_differential_proof(result[index], attachment, trust, resolver)
consumed.add(index)
def _apply_proofs(
request: PushRequest,
claims: tuple[DefectClaim, ...],
attachments: Sequence[ProofAttachment],
trust: ProofTrustPolicy | None,
resolver: canonical.ContentResolver,
) -> tuple[DefectClaim, ...]:
selected = tuple(attachments)
if not selected:
return claims
if not isinstance(trust, ProofTrustPolicy):
raise ConsumerContractError("nonempty proof attachments require a proof trust policy")
keys = _proof_target_index(claims)
result = list(claims)
consumed: set[int] = set()
try:
for attachment in selected:
_apply_attachment(request, result, keys, consumed, attachment, trust, resolver)
except ConsumerContractError:
raise
except (AttributeError, LookupError, TypeError, ValueError) as exc:
raise ConsumerContractError(f"proof validation failed: {exc}") from exc
return tuple(result)
def _total_reply_ms(elapsed_before_entry_ms: int, started_ns: int) -> int:
if isinstance(elapsed_before_entry_ms, bool) or not isinstance(elapsed_before_entry_ms, int) or elapsed_before_entry_ms < 0:
raise ConsumerContractError("elapsed-before-entry measurement must be a non-negative integer")
finished_ns = _monotonic_ns()
ticks = (started_ns, finished_ns)
if any(isinstance(tick, bool) or not isinstance(tick, int) or tick < 0 for tick in ticks):
raise ConsumerContractError("monotonic core timing ticks must be non-negative integers")
if finished_ns < started_ns:
raise ConsumerContractError("monotonic core timing measurement moved backwards")
core_elapsed_ms = (finished_ns - started_ns + 999_999) // 1_000_000
total_reply_ms = elapsed_before_entry_ms + core_elapsed_ms
if total_reply_ms <= 0:
raise ConsumerContractError("combined elapsed measurement must be a strictly positive integer")
return total_reply_ms
def _validate_budget_artifacts(request: PushRequest, resolver: canonical.ContentResolver) -> None:
try:
check_budget(request.complexity, 1, request.budget, resolver)
except (BudgetGateFailure, AttributeError, LookupError, TypeError, ValueError) as exc:
raise ConsumerContractError(f"measured budget validation failed: {exc}") from exc
def _enforce_validated_deadline(request: PushRequest, total_reply_ms: int) -> None:
if total_reply_ms > request.budget.budget_ms:
raise ConsumerContractError(
f"measured budget gate failed: budget:{request.complexity.change_id}:"
f"{total_reply_ms}ms>{request.budget.budget_ms}ms "
f"class={request.budget.workload_class} derivation={request.budget.derivation_digest}"
)
def consume_records(
request: PushRequest,
inputs: Sequence[EvidenceValidationInput],
validation_context: EvidenceValidationContext,
custody: PopulationCustodyPort,
elapsed_before_entry_ms: int,
resolver: canonical.ContentResolver,
attachments: Sequence[ProofAttachment] = (),
proof_trust: ProofTrustPolicy | None = None,
) -> PushVerdict:
started_ns = _monotonic_ns()
_validate_request(request)
records = _validated_records(inputs, validation_context, custody, resolver)
transition_count = sum(len(record.packet.transitions) for record in records)
if request.complexity.transition_count != transition_count:
raise ConsumerContractError("complexity transition count does not match validated records")
outputs = _derive_outputs(request, records, validation_context)
defects = _apply_proofs(request, outputs.defects, attachments, proof_trust, resolver)
if not defects and not outputs.satisfactions and not outputs.silence:
raise ConsumerContractError("push verdict cannot be an unbounded clean result")
_validate_budget_artifacts(request, resolver)
provisional = PushVerdict(
request.request_id,
defects,
outputs.satisfactions,
outputs.silence,
tuple(sorted(record.record_id for record in records)),
request.complexity,
0,
request.budget.derivation_digest,
)
total_reply_ms = _total_reply_ms(elapsed_before_entry_ms, started_ns)
_enforce_validated_deadline(request, total_reply_ms)
return replace(provisional, total_reply_ms=total_reply_ms)
__all__ = [
"ConsumerContractError",
"PushRequest",
"PushVerdict",
"consume_records",
"derive_push_request_id",
"make_push_request",
]
-341
View File
@@ -1,341 +0,0 @@
"""Closed v2 push-consumer edge for validated bounded verdicts."""
from __future__ import annotations
import time
from collections.abc import Sequence
from dataclasses import dataclass, replace
from openultrasast.core import canonical
from openultrasast.core.budget import BudgetDerivation, BudgetGateFailure, ChangeComplexity, check_budget
from openultrasast.core.evidence import AnalyzedScope, EvidenceRecord, SourceRegion, derive_packet_digest, validate_scope
from openultrasast.core.family import FamilyContract, family_contract_digest
from openultrasast.core.family_catalog import resolve_contract
from openultrasast.core.proof import ProofAttachment, ProofTrustPolicy, attach_differential_proof
from openultrasast.core.provenance import PopulationCustodyPort
from openultrasast.core.semantics import SourceLocation
from openultrasast.core.validation import EvidenceValidationContext, EvidenceValidationInput, validate_evidence_input
from openultrasast.core.verdict_v2 import (
BoundedSatisfaction,
BoundedSilence,
DefectClaim,
VerdictOutputs,
derive_verdict_outputs,
)
PUSH_REQUEST_SCHEMA = "openultrasast.detection-core.push-request.v2"
CONSUMER_COVERAGE_SCHEMA = "openultrasast.detection-core.consumer-coverage.v2"
_monotonic_ns = time.monotonic_ns
class ConsumerContractError(ValueError):
"""A raw push request cannot produce a bounded v2 verdict."""
@dataclass(frozen=True)
class PushRequest:
request_id: str
scope: AnalyzedScope
workload_class: str
complexity: ChangeComplexity
budget: BudgetDerivation
@dataclass(frozen=True)
class PushVerdict:
request_digest: str
defects: tuple[DefectClaim, ...]
satisfactions: tuple[BoundedSatisfaction, ...]
silence: tuple[BoundedSilence, ...]
input_record_digests: tuple[str, ...]
complexity: ChangeComplexity
total_reply_ms: int
budget_derivation_digest: str
def _content_ref_value(value: canonical.ContentRef) -> canonical.JsonValue:
return {
"artifact_id": value.artifact_id,
"content_digest": value.content_digest,
"media_type": value.media_type,
"byte_length": value.byte_length,
"retention_policy_digest": value.retention_policy_digest,
"storage_receipt_digest": value.storage_receipt_digest,
}
def _scope_value(value: AnalyzedScope) -> canonical.JsonValue:
return {
"base_revision": value.base_revision,
"head_revision": value.head_revision,
"change_digest": value.change_digest,
"regions": tuple({"path": region.path, "start_line": region.start_line, "end_line": region.end_line} for region in value.regions),
}
def _request_value(request: PushRequest) -> canonical.JsonValue:
complexity = request.complexity
return {
"schema_version": PUSH_REQUEST_SCHEMA,
"scope": _scope_value(request.scope),
"workload_class": request.workload_class,
"complexity": {
"change_id": complexity.change_id,
"workload_class": complexity.workload_class,
"changed_regions": complexity.changed_regions,
"transition_count": complexity.transition_count,
"changed_bytes": complexity.changed_bytes,
"measurement": _content_ref_value(complexity.measurement),
},
"budget_derivation_digest": request.budget.derivation_digest,
}
def derive_push_request_id(request: PushRequest) -> str:
if not isinstance(request, PushRequest):
raise ConsumerContractError("request must be a PushRequest")
return canonical.content_digest(_request_value(request))
def make_push_request(
*,
scope: AnalyzedScope,
workload_class: str,
complexity: ChangeComplexity,
budget: BudgetDerivation,
) -> PushRequest:
provisional = PushRequest("0" * 64, scope, workload_class, complexity, budget)
return PushRequest(derive_push_request_id(provisional), scope, workload_class, complexity, budget)
def _validate_request(request: PushRequest) -> None:
if not isinstance(request, PushRequest):
raise ConsumerContractError("request must be a PushRequest")
try:
request.scope.__post_init__()
request.complexity.__post_init__()
request.budget.__post_init__()
except (AttributeError, TypeError, ValueError) as exc:
raise ConsumerContractError(f"push request is invalid: {exc}") from exc
if request.request_id != derive_push_request_id(request):
raise ConsumerContractError("push request identity cannot be re-derived")
if request.workload_class != request.complexity.workload_class or request.workload_class != request.budget.workload_class:
raise ConsumerContractError("push workload class does not match complexity and budget")
if request.complexity.change_id != request.scope.change_digest:
raise ConsumerContractError("complexity change identity does not match request scope")
if request.complexity.changed_regions != len(request.scope.regions):
raise ConsumerContractError("complexity changed-region count does not match request scope")
def _validated_records(
values: Sequence[EvidenceValidationInput],
context: EvidenceValidationContext,
custody: PopulationCustodyPort,
resolver: canonical.ContentResolver,
) -> tuple[EvidenceRecord, ...]:
selected = tuple(values)
if not selected:
raise ConsumerContractError("push inputs must be nonempty")
if not all(isinstance(value, EvidenceValidationInput) for value in selected):
raise ConsumerContractError("every push input must be an EvidenceValidationInput")
input_ids = tuple(value.input_digest for value in selected)
if len(set(input_ids)) != len(input_ids):
raise ConsumerContractError("duplicate validation input identity")
try:
return tuple(validate_evidence_input(value, context, custody, resolver) for value in selected)
except (AttributeError, LookupError, TypeError, ValueError) as exc:
raise ConsumerContractError(f"evidence validation failed: {exc}") from exc
def _contract(context: EvidenceValidationContext, record: EvidenceRecord) -> FamilyContract:
packet = record.packet
try:
return resolve_contract(context.family_catalog, packet.family_id, packet.family_contract_digest, packet.ecosystem)
except (LookupError, TypeError, ValueError) as exc:
raise ConsumerContractError(f"record family cannot be resolved: {exc}") from exc
def _coverage_digest(request: PushRequest, contract: FamilyContract, records: tuple[EvidenceRecord, ...]) -> str:
return canonical.content_digest(
{
"schema_version": CONSUMER_COVERAGE_SCHEMA,
"request_id": request.request_id,
"family_contract_digest": family_contract_digest(contract),
"record_ids": tuple(sorted(record.record_id for record in records)),
}
)
def _derive_outputs(
request: PushRequest,
records: tuple[EvidenceRecord, ...],
context: EvidenceValidationContext,
) -> VerdictOutputs:
grouped: dict[str, tuple[FamilyContract, list[EvidenceRecord]]] = {}
for record in records:
try:
validate_scope(record.packet, request.scope)
except (TypeError, ValueError) as exc:
raise ConsumerContractError(f"record scope is outside the push request: {exc}") from exc
contract = _contract(context, record)
digest = record.packet.family_contract_digest
if digest not in grouped:
grouped[digest] = (contract, [])
grouped[digest][1].append(record)
defects: list[DefectClaim] = []
satisfactions: list[BoundedSatisfaction] = []
silence: list[BoundedSilence] = []
try:
for digest in sorted(grouped):
contract, members = grouped[digest]
selected = tuple(members)
outputs = derive_verdict_outputs(selected, contract, _coverage_digest(request, contract, selected))
defects.extend(outputs.defects)
satisfactions.extend(outputs.satisfactions)
silence.extend(outputs.silence)
except (TypeError, ValueError) as exc:
raise ConsumerContractError(f"bounded verdict derivation failed: {exc}") from exc
return VerdictOutputs(tuple(defects), tuple(satisfactions), tuple(silence))
def _region_within(allowed: AnalyzedScope, candidate: SourceRegion) -> bool:
return any(
candidate.path == region.path and candidate.start_line >= region.start_line and candidate.end_line <= region.end_line
for region in allowed.regions
)
def _proof_target_index(claims: tuple[DefectClaim, ...]) -> dict[tuple[str, str, SourceLocation], int]:
keys: dict[tuple[str, str, SourceLocation], int] = {}
for index, claim in enumerate(claims):
key = (claim.record.packet.family_id, derive_packet_digest(claim.record.packet), claim.location)
if key in keys:
raise ConsumerContractError("proof target is ambiguous across defect claims")
keys[key] = index
return keys
def _apply_attachment(
request: PushRequest,
result: list[DefectClaim],
keys: dict[tuple[str, str, SourceLocation], int],
consumed: set[int],
attachment: ProofAttachment,
trust: ProofTrustPolicy,
resolver: canonical.ContentResolver,
) -> None:
if not isinstance(attachment, ProofAttachment):
raise ConsumerContractError("every proof attachment must use the v2 proof contract")
key = (attachment.family_id, attachment.packet_digest, attachment.location)
if key not in keys:
raise ConsumerContractError("proof attachment does not match one exact defect claim")
index = keys[key]
if index in consumed:
raise ConsumerContractError("multiple proof attachments target one defect claim")
if not _region_within(request.scope, result[index].change_anchor):
raise ConsumerContractError("proof claim anchor is outside the push request scope")
result[index] = attach_differential_proof(result[index], attachment, trust, resolver)
consumed.add(index)
def _apply_proofs(
request: PushRequest,
claims: tuple[DefectClaim, ...],
attachments: Sequence[ProofAttachment],
trust: ProofTrustPolicy | None,
resolver: canonical.ContentResolver,
) -> tuple[DefectClaim, ...]:
selected = tuple(attachments)
if not selected:
return claims
if not isinstance(trust, ProofTrustPolicy):
raise ConsumerContractError("nonempty proof attachments require a proof trust policy")
keys = _proof_target_index(claims)
result = list(claims)
consumed: set[int] = set()
try:
for attachment in selected:
_apply_attachment(request, result, keys, consumed, attachment, trust, resolver)
except ConsumerContractError:
raise
except (AttributeError, LookupError, TypeError, ValueError) as exc:
raise ConsumerContractError(f"proof validation failed: {exc}") from exc
return tuple(result)
def _total_reply_ms(elapsed_before_entry_ms: int, started_ns: int) -> int:
if isinstance(elapsed_before_entry_ms, bool) or not isinstance(elapsed_before_entry_ms, int) or elapsed_before_entry_ms < 0:
raise ConsumerContractError("elapsed-before-entry measurement must be a non-negative integer")
finished_ns = _monotonic_ns()
ticks = (started_ns, finished_ns)
if any(isinstance(tick, bool) or not isinstance(tick, int) or tick < 0 for tick in ticks):
raise ConsumerContractError("monotonic core timing ticks must be non-negative integers")
if finished_ns < started_ns:
raise ConsumerContractError("monotonic core timing measurement moved backwards")
core_elapsed_ms = (finished_ns - started_ns + 999_999) // 1_000_000
total_reply_ms = elapsed_before_entry_ms + core_elapsed_ms
if total_reply_ms <= 0:
raise ConsumerContractError("combined elapsed measurement must be a strictly positive integer")
return total_reply_ms
def _validate_budget_artifacts(request: PushRequest, resolver: canonical.ContentResolver) -> None:
try:
check_budget(request.complexity, 1, request.budget, resolver)
except (BudgetGateFailure, AttributeError, LookupError, TypeError, ValueError) as exc:
raise ConsumerContractError(f"measured budget validation failed: {exc}") from exc
def _enforce_validated_deadline(request: PushRequest, total_reply_ms: int) -> None:
if total_reply_ms > request.budget.budget_ms:
raise ConsumerContractError(
f"measured budget gate failed: budget:{request.complexity.change_id}:"
f"{total_reply_ms}ms>{request.budget.budget_ms}ms "
f"class={request.budget.workload_class} derivation={request.budget.derivation_digest}"
)
def consume_records(
request: PushRequest,
inputs: Sequence[EvidenceValidationInput],
validation_context: EvidenceValidationContext,
custody: PopulationCustodyPort,
elapsed_before_entry_ms: int,
resolver: canonical.ContentResolver,
attachments: Sequence[ProofAttachment] = (),
proof_trust: ProofTrustPolicy | None = None,
) -> PushVerdict:
started_ns = _monotonic_ns()
_validate_request(request)
records = _validated_records(inputs, validation_context, custody, resolver)
transition_count = sum(len(record.packet.transitions) for record in records)
if request.complexity.transition_count != transition_count:
raise ConsumerContractError("complexity transition count does not match validated records")
outputs = _derive_outputs(request, records, validation_context)
defects = _apply_proofs(request, outputs.defects, attachments, proof_trust, resolver)
if not defects and not outputs.satisfactions and not outputs.silence:
raise ConsumerContractError("push verdict cannot be an unbounded clean result")
_validate_budget_artifacts(request, resolver)
provisional = PushVerdict(
request.request_id,
defects,
outputs.satisfactions,
outputs.silence,
tuple(sorted(record.record_id for record in records)),
request.complexity,
0,
request.budget.derivation_digest,
)
total_reply_ms = _total_reply_ms(elapsed_before_entry_ms, started_ns)
_enforce_validated_deadline(request, total_reply_ms)
return replace(provisional, total_reply_ms=total_reply_ms)
__all__ = [
"ConsumerContractError",
"PushRequest",
"PushVerdict",
"consume_records",
"derive_push_request_id",
"make_push_request",
]
@@ -1,92 +0,0 @@
"""Explicit temporary exports for the closed detection-core v1 namespace."""
from openultrasast.core.legacy_v1.contracts import (
REPORT_SCHEMA_KEYS,
AdmissionManifest,
AdmissionManifestError,
BoundedSilence,
Budget,
BudgetGateFailure,
Complexity,
Coverage,
FactPackError,
FittedComponentBlocked,
Guard,
Hop,
InstrumentFailure,
Judgment,
LegacyFactPack,
LegacyProbeResult,
ProvenanceMismatch,
ProvenClaim,
PushRequest,
PushVerdict,
ResolutionOutcome,
ShortcutDetected,
TraceRecord,
Witness,
admit_fact_pack,
admit_pack_file,
as_clean,
assert_no_shortcut,
check_budget,
check_reads,
consume_verdict,
derive_budget,
load_pack_file,
require_admission_evidence,
reversed_pair_probe,
stamp,
validate_admission_manifest,
validate_report_schema,
verify_fact_pack,
verify_stamp,
)
FactPack = LegacyFactPack
ProbeResult = LegacyProbeResult
__all__ = [
"REPORT_SCHEMA_KEYS",
"AdmissionManifest",
"AdmissionManifestError",
"BoundedSilence",
"Budget",
"BudgetGateFailure",
"Complexity",
"Coverage",
"FactPack",
"FactPackError",
"FittedComponentBlocked",
"Guard",
"Hop",
"Judgment",
"LegacyFactPack",
"LegacyProbeResult",
"InstrumentFailure",
"ProvenClaim",
"PushRequest",
"PushVerdict",
"ProvenanceMismatch",
"ProbeResult",
"ResolutionOutcome",
"ShortcutDetected",
"TraceRecord",
"Witness",
"admit_fact_pack",
"admit_pack_file",
"as_clean",
"assert_no_shortcut",
"check_budget",
"check_reads",
"consume_verdict",
"derive_budget",
"load_pack_file",
"require_admission_evidence",
"reversed_pair_probe",
"stamp",
"validate_admission_manifest",
"validate_report_schema",
"verify_fact_pack",
"verify_stamp",
]
@@ -1,439 +0,0 @@
"""Closed temporary owner of detection-core v1 contracts and behavior."""
from __future__ import annotations
import enum
from collections.abc import Callable, Mapping, Sequence
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
class AdmissionManifestError(ValueError):
"""A legacy admission manifest was refused."""
class FactPackError(ValueError):
"""A legacy fact pack is unreviewed or inconsistent."""
class InstrumentFailure(ValueError):
"""A legacy run did not read every tracked input."""
class BudgetGateFailure(ValueError):
"""A legacy change exceeded its measured budget."""
class ProvenanceMismatch(ValueError):
"""A legacy recorded digest differs from its re-derived identity."""
class ShortcutDetected(ValueError):
"""A legacy component separates reversed surface shapes."""
class FittedComponentBlocked(ValueError):
"""A legacy fitted component lacks release evidence."""
class ResolutionOutcome(enum.Enum):
COMPLETE = "complete"
INCOMPLETE = "incomplete"
UNRESOLVED = "unresolved"
class Judgment(enum.Enum):
UNGUARDED = "unguarded"
DISCHARGED = "discharged"
INDETERMINATE = "indeterminate"
@dataclass(frozen=True)
class ProvenClaim:
trace_id: str
witness: str
path: str
line: int
family: str
@dataclass(frozen=True)
class BoundedSilence:
path: str
reason: str
coverage: dict[str, Any]
@dataclass(frozen=True)
class Hop:
function: str
call_line: int | None
complete: bool
@dataclass(frozen=True)
class Witness:
kind: str
provenance: str
relation: str
@dataclass(frozen=True)
class Guard:
kind: str
effect: str
@dataclass(frozen=True)
class TraceRecord:
entry: str
hops: tuple[Hop, ...]
operation_kind: str
resource: str
authority: str
witnesses: tuple[Witness, ...] = ()
guards: tuple[Guard, ...] = ()
completeness_reasons: tuple[str, ...] = ()
judgment: Judgment = Judgment.INDETERMINATE
digests: dict[str, str] = field(default_factory=dict)
def record(self) -> dict[str, Any]:
from openultrasast.core.legacy_v1 import writer
return writer.trace_record(self)
def canonical(self) -> bytes:
from openultrasast.core.legacy_v1 import writer
return writer.trace_bytes(self)
@dataclass(frozen=True)
class PushRequest:
base: str
head: str
budget_class: str
@dataclass(frozen=True)
class PushVerdict:
claims: tuple[ProvenClaim, ...]
silence: tuple[BoundedSilence, ...]
digests: dict[str, str]
@dataclass(frozen=True)
class AdmissionManifest:
produced_by: str
reviewed: bool
label_isolated: bool
spent: bool
population_role: str
analyzer_revision: str
fact_set_digest: str
acquisition_record_digest: str
configuration_digest: str
population_digest: str
labels: tuple[str, ...]
families: tuple[str, ...] = ()
@dataclass(frozen=True)
class LegacyFactPack:
family: str
facts: tuple[dict[str, Any], ...]
review_digest: str
fact_set_digest: str
@dataclass(frozen=True)
class Coverage:
entry_points: int
frameworks_recognised: dict[str, int] = field(default_factory=dict)
calls: int = 0
complete: int = 0
incomplete: int = 0
unresolved: int = 0
unchecked_reasons: dict[str, int] = field(default_factory=dict)
def validate(self) -> None:
buckets = (self.complete, self.incomplete, self.unresolved)
if min(buckets) < 0:
raise ValueError(f"negative resolution bucket in {buckets}")
if sum(buckets) != self.calls:
raise ValueError(
f"resolution buckets ({self.complete}+{self.incomplete}+{self.unresolved}) must equal the call count ({self.calls})"
)
@dataclass(frozen=True)
class Complexity:
regions: int
calls: int
changed_bytes: int
@dataclass(frozen=True)
class Budget:
workload_class: str
seconds: float
n: int
percentile: float
hardware: str
def describe(self) -> str:
return f"class={self.workload_class} n={self.n} p{int(self.percentile * 100)} hardware={self.hardware} budget={self.seconds:g}s"
@dataclass(frozen=True)
class LegacyProbeResult:
separated: bool
fix_score: float
reversed_score: float
REPORT_SCHEMA_KEYS = frozenset(
{
"manifest",
"families",
"pairs",
"pair_correctness",
"fix_recall",
"verified_location_recall",
"flagged_ordinary_per_100",
"precision",
"indeterminate_rate",
"coverage",
"runtime",
"instrument_failures",
"failed_gates",
"decision",
}
)
_REQUIRED_MANIFEST_FIELDS = (
"produced_by",
"reviewed",
"label_isolated",
"spent",
"population_role",
"analyzer_revision",
"fact_set_digest",
"acquisition_record_digest",
"configuration_digest",
"population_digest",
"labels",
)
_BENCHMARK_PRODUCER = "valid-detection-benchmark"
_FORBIDDEN_LABEL_MARKERS = ("vulnerability", "fix-location", "ordinary-change")
def as_clean(judgment: Judgment) -> Judgment:
if judgment is Judgment.INDETERMINATE:
raise ValueError("indeterminate never converts to a clean judgment")
return judgment
def consume_verdict(
request: PushRequest,
record: TraceRecord,
claims: Sequence[ProvenClaim],
silence: Sequence[BoundedSilence],
) -> PushVerdict:
del request
for claim in claims:
if not claim.witness or not claim.path or claim.line <= 0:
raise ValueError(f"proven claim lacks witness or exact location: {claim}")
for entry in silence:
if not entry.reason:
raise ValueError(f"bounded silence lacks a coverage reason: {entry}")
return PushVerdict(tuple(claims), tuple(silence), dict(record.digests))
def validate_admission_manifest(
payload: dict[str, object],
*,
expected_acquisition_digest: str | None = None,
) -> AdmissionManifest:
if not all(field in payload for field in _REQUIRED_MANIFEST_FIELDS):
raise AdmissionManifestError("malformed: missing required manifest fields")
if payload["produced_by"] != _BENCHMARK_PRODUCER:
raise AdmissionManifestError(f"producer: only {_BENCHMARK_PRODUCER} manifests are consumed")
if payload["reviewed"] is not True:
raise AdmissionManifestError("unreviewed: manifest lacks external review")
if payload["label_isolated"] is not True:
raise AdmissionManifestError("label-isolated: isolation attestation missing")
raw_labels = payload["labels"]
if not isinstance(raw_labels, (tuple, list)):
raise AdmissionManifestError("label-exposure: manifest carries admission labels")
if any(marker in str(label) for label in raw_labels for marker in _FORBIDDEN_LABEL_MARKERS):
raise AdmissionManifestError("label-exposure: manifest carries admission labels")
if expected_acquisition_digest is not None and payload["acquisition_record_digest"] != expected_acquisition_digest:
raise AdmissionManifestError("acquisition-output-changed: digest mismatch")
if payload["spent"] is True:
raise AdmissionManifestError("spent: population already consumed")
raw_families = payload.get("families", ())
if not isinstance(raw_families, (tuple, list)):
raise AdmissionManifestError("malformed: families must be a sequence")
return _manifest(payload, raw_labels, raw_families)
def _manifest(payload: dict[str, object], labels: Sequence[object], families: Sequence[object]) -> AdmissionManifest:
return AdmissionManifest(
produced_by=str(payload["produced_by"]),
reviewed=True,
label_isolated=True,
spent=False,
population_role=str(payload["population_role"]),
analyzer_revision=str(payload["analyzer_revision"]),
fact_set_digest=str(payload["fact_set_digest"]),
acquisition_record_digest=str(payload["acquisition_record_digest"]),
configuration_digest=str(payload["configuration_digest"]),
population_digest=str(payload["population_digest"]),
labels=tuple(str(label) for label in labels),
families=tuple(str(family) for family in families),
)
def validate_report_schema(report: dict[str, Any]) -> None:
missing = sorted(REPORT_SCHEMA_KEYS - set(report))
if missing:
raise AdmissionManifestError(f"report schema: missing keys {missing}")
for rate_key in (
"pair_correctness",
"fix_recall",
"verified_location_recall",
"flagged_ordinary_per_100",
"precision",
"indeterminate_rate",
):
if not {"n", "count", "rate", "wilson95"} <= set(report[rate_key]):
raise AdmissionManifestError(f"report schema: {rate_key} lacks n/count/rate/wilson95")
def admit_fact_pack(family: str, facts: Sequence[Mapping[str, Any]], *, review_digest: str) -> LegacyFactPack:
from openultrasast.core.legacy_v1 import writer
if not review_digest:
raise FactPackError("unreviewed fact pack refused: no review digest")
normalized = tuple(dict(fact) for fact in facts)
return LegacyFactPack(family, normalized, review_digest, writer.fact_pack_digest(family, normalized))
def verify_fact_pack(pack: LegacyFactPack, facts: Sequence[Mapping[str, Any]]) -> None:
from openultrasast.core.legacy_v1 import writer
normalized = tuple(dict(fact) for fact in facts)
if writer.fact_pack_digest(pack.family, normalized) != pack.fact_set_digest:
raise FactPackError(f"fact content does not match the stamped digest of pack {pack.family!r}")
def load_pack_file(path: str | Path) -> LegacyFactPack:
from openultrasast.core.legacy_v1 import writer
payload = writer.read_json(Path(path))
review = payload.get("review", {})
if not isinstance(review, dict):
raise FactPackError(f"pack {path} lacks a verifiable review attestation")
note = review.get("note", "")
if not isinstance(note, str) or not note or review.get("digest") != writer.text_digest(note):
raise FactPackError(f"pack {path} lacks a verifiable review attestation")
facts = payload.get("facts")
if not isinstance(facts, list) or not all(isinstance(fact, dict) for fact in facts):
raise FactPackError(f"pack {path}: facts must be a list of objects")
return admit_fact_pack(str(payload.get("family", "")), facts, review_digest=str(review["digest"]))
def admit_pack_file(path: str | Path, *, review_digest: str) -> LegacyFactPack:
pack = load_pack_file(path)
if review_digest != pack.review_digest:
raise FactPackError(f"pack {path}: supplied review digest does not match the embedded attestation")
return pack
_STAMP_FIELDS = (
"analyzer_revision",
"fact_set_digest",
"acquisition_record_digest",
"configuration_digest",
"population_digest",
"input_digest",
)
def check_reads(reads: Mapping[str, int]) -> None:
zero = sorted(path for path, size in reads.items() if size <= 0)
if zero:
raise InstrumentFailure(f"zero-byte read of tracked input: {', '.join(zero)}")
def _quantile(values: list[float], percentile: float) -> float:
position = (len(values) - 1) * percentile
lower = int(position)
upper = min(lower + 1, len(values) - 1)
weight = position - lower
return values[lower] * (1 - weight) + values[upper] * weight
def derive_budget(samples: Sequence[tuple[Complexity, float]], *, workload_class: str, percentile: float, hardware: str) -> Budget:
if not samples:
raise ValueError("budget derivation needs at least one measured sample")
if not 0.0 < percentile < 1.0:
raise ValueError(f"percentile {percentile} outside (0, 1)")
seconds = sorted(float(item[1]) for item in samples)
return Budget(workload_class, _quantile(seconds, percentile), len(samples), percentile, hardware)
def check_budget(change_id: str, seconds: float, budget: Budget) -> None:
if seconds > budget.seconds:
raise BudgetGateFailure(f"budget:{change_id}:{seconds:g}s>{budget.seconds:g}s ({budget.describe()})")
def stamp(
*,
analyzer_revision: str,
fact_set_digest: str,
acquisition_record_digest: str,
configuration_digest: str,
population_digest: str,
change_id: str,
) -> dict[str, str]:
from openultrasast.core.legacy_v1 import writer
return {
"analyzer_revision": analyzer_revision,
"fact_set_digest": fact_set_digest,
"acquisition_record_digest": acquisition_record_digest,
"configuration_digest": configuration_digest,
"population_digest": population_digest,
"input_digest": writer.input_digest(change_id, population_digest),
}
def verify_stamp(recorded: Mapping[str, object], rederived: Mapping[str, object]) -> None:
for field_name in _STAMP_FIELDS:
if recorded.get(field_name) != rederived.get(field_name):
raise ProvenanceMismatch(f"{field_name}: recorded {recorded.get(field_name)!r} != rederived {rederived.get(field_name)!r}")
def reversed_pair_probe(
score_fn: Callable[[Mapping[str, Any]], float], fix_change: Mapping[str, Any], reversed_change: Mapping[str, Any]
) -> LegacyProbeResult:
fix_score = float(score_fn(fix_change))
reversed_score = float(score_fn(reversed_change))
return LegacyProbeResult(fix_score != reversed_score, fix_score, reversed_score)
def assert_no_shortcut(
score_fn: Callable[[Mapping[str, Any]], float], fix_change: Mapping[str, Any], reversed_change: Mapping[str, Any]
) -> None:
result = reversed_pair_probe(score_fn, fix_change, reversed_change)
if result.separated:
raise ShortcutDetected(f"shape shortcut: fix={result.fix_score} vs reversed={result.reversed_score}")
def require_admission_evidence(*, holdout_passed: bool, admission_decision: str) -> None:
if not holdout_passed:
raise FittedComponentBlocked("fitted component lacks per-repository holdout evidence")
if admission_decision != "GO":
raise FittedComponentBlocked(f"fitted component admission decision is {admission_decision!r}, not GO")
@@ -1,46 +0,0 @@
"""The sole temporary detection-core v1 JSON serialization capability."""
from __future__ import annotations
import hashlib
import json
from collections.abc import Mapping, Sequence
from dataclasses import asdict
from pathlib import Path
from typing import Any, cast
from openultrasast.core.legacy_v1 import contracts
def trace_record(value: contracts.TraceRecord) -> dict[str, Any]:
payload: dict[str, Any] = asdict(value)
payload["judgment"] = value.judgment.value
return cast(dict[str, Any], json.loads(json.dumps(payload, sort_keys=True)))
def trace_bytes(value: contracts.TraceRecord) -> bytes:
return json.dumps(trace_record(value), sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()
def fact_pack_digest(family: str, facts: Sequence[Mapping[str, Any]]) -> str:
encoded = json.dumps([family, tuple(dict(fact) for fact in facts)], sort_keys=True, separators=(",", ":")).encode()
return hashlib.sha256(encoded).hexdigest()[:12]
def text_digest(value: str) -> str:
return hashlib.sha256(value.encode()).hexdigest()
def input_digest(change_id: str, population_digest: str) -> str:
payload = json.dumps([population_digest, change_id], sort_keys=True, separators=(",", ":")).encode()
return hashlib.sha256(payload).hexdigest()[:12]
def read_json(path: Path) -> dict[str, Any]:
value = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise contracts.FactPackError(f"pack {path}: root must be an object")
return value
__all__ = ["fact_pack_digest", "input_digest", "read_json", "text_digest", "trace_bytes", "trace_record"]
+1 -1
View File
@@ -8,7 +8,7 @@ from enum import Enum
from openultrasast.core import canonical
from openultrasast.core.semantics import SourceLocation
from openultrasast.core.verdict_v2 import DefectClaim
from openultrasast.core.verdict import DefectClaim
DIFFERENTIAL_PROOF_SCHEMA = "openultrasast.detection-core.differential-proof.v2"
_DIGEST = re.compile(r"[0-9a-f]{64}")
-5
View File
@@ -1,5 +0,0 @@
"""Temporary explicit import shim for the pre-v2 trace contract."""
from openultrasast.core.legacy_v1.contracts import Guard, Hop, TraceRecord, Witness
__all__ = ["Guard", "Hop", "TraceRecord", "Witness"]
+269 -3
View File
@@ -1,5 +1,271 @@
"""Temporary explicit import shim for the pre-v2 verdict contract."""
"""Bounded v2 verdict outputs derived from complete evidence records."""
from openultrasast.core.legacy_v1.contracts import BoundedSilence, Judgment, ProvenClaim, as_clean
from __future__ import annotations
__all__ = ["BoundedSilence", "Judgment", "ProvenClaim", "as_clean"]
import re
from collections.abc import Sequence
from dataclasses import dataclass
from openultrasast.core.evidence import (
AnalyzedScope,
EvidenceBasis,
EvidenceRecord,
EvidenceValidationError,
NeutralOutcome,
ScopeValidationError,
SourceRegion,
derive_record_id,
validate_scope,
)
from openultrasast.core.family import FamilyContract, FamilyContractError, ResidualQuestion, validate_contract, validate_record
from openultrasast.core.semantics import Observation, RelationStatus, SemanticRelation, SourceLocation
_DIGEST = re.compile(r"[0-9a-f]{64}")
class VerdictContractError(ValueError):
"""Validated evidence cannot be represented as a bounded verdict."""
@dataclass(frozen=True)
class DefectClaim:
record: EvidenceRecord
basis: EvidenceBasis
location: SourceLocation
change_anchor: SourceRegion
def __post_init__(self) -> None:
if not isinstance(self.record, EvidenceRecord) or self.record.decision.outcome is not NeutralOutcome.VIOLATION:
raise VerdictContractError("defect claim requires a violation evidence record")
try:
self.record.__post_init__()
validate_scope(self.record.packet, self.record.packet.scope)
except (TypeError, ValueError) as exc:
raise VerdictContractError(f"defect claim evidence record is invalid: {exc}") from exc
if not isinstance(self.basis, EvidenceBasis) or self.basis is not self.record.decision.basis:
raise VerdictContractError("defect claim basis must equal its evidence decision basis")
if not isinstance(self.location, SourceLocation) or not isinstance(self.change_anchor, SourceRegion):
raise VerdictContractError("defect claim requires an exact location and change anchor")
members: tuple[SemanticRelation | Observation, ...] = (
*self.record.packet.transitions,
*self.record.packet.observations,
)
if self.record.packet.completeness_reasons or any(item.status is not RelationStatus.COMPLETE for item in members):
raise VerdictContractError("defect claim requires complete evidence")
if self.location not in tuple(item.location for item in members):
raise VerdictContractError("defect location must name an exact evidence location")
if self.change_anchor not in self.record.decision.bounded_scope.regions:
raise VerdictContractError("defect change anchor must belong to the bounded decision scope")
if not _region_contains(self.change_anchor, self.location):
raise VerdictContractError("defect location is outside its change anchor")
@dataclass(frozen=True)
class BoundedSatisfaction:
question_ids: tuple[str, ...]
records: tuple[EvidenceRecord, ...]
scope: AnalyzedScope
def __post_init__(self) -> None:
_text_tuple(self.question_ids, "satisfaction question")
if (
not isinstance(self.records, tuple)
or not self.records
or not all(isinstance(record, EvidenceRecord) for record in self.records)
):
raise VerdictContractError("bounded satisfaction requires evidence records")
if not isinstance(self.scope, AnalyzedScope):
raise VerdictContractError("bounded satisfaction requires an AnalyzedScope")
if any(record.decision.outcome is not NeutralOutcome.SATISFIED for record in self.records):
raise VerdictContractError("bounded satisfaction accepts only satisfied evidence records")
if any(record.decision.bounded_scope != self.scope for record in self.records):
raise VerdictContractError("satisfaction scope must equal every supporting record scope")
@dataclass(frozen=True)
class BoundedSilence:
question_ids: tuple[str, ...]
reasons: tuple[str, ...]
evidence_record_digests: tuple[str, ...]
coverage_digest: str
def __post_init__(self) -> None:
_text_tuple(self.question_ids, "silence question")
_text_tuple(self.reasons, "silence reason")
_digest_tuple(self.evidence_record_digests, "silence evidence record")
_digest(self.coverage_digest, "coverage")
@dataclass(frozen=True)
class VerdictOutputs:
defects: tuple[DefectClaim, ...]
satisfactions: tuple[BoundedSatisfaction, ...]
silence: tuple[BoundedSilence, ...]
def __post_init__(self) -> None:
values = (
(self.defects, DefectClaim, "defects"),
(self.satisfactions, BoundedSatisfaction, "satisfactions"),
(self.silence, BoundedSilence, "silence"),
)
for items, item_type, field in values:
if not isinstance(items, tuple) or not all(isinstance(item, item_type) for item in items):
raise VerdictContractError(f"{field} must be a tuple of {item_type.__name__}")
def _digest(value: object, field: str) -> None:
if not isinstance(value, str) or _DIGEST.fullmatch(value) is None:
raise VerdictContractError(f"{field} must be a full lowercase SHA-256 digest")
def _text_tuple(value: object, field: str) -> None:
if not isinstance(value, tuple) or not value or not all(isinstance(item, str) and item for item in value):
raise VerdictContractError(f"{field} identities must be a nonempty tuple")
if len(set(value)) != len(value):
raise VerdictContractError(f"duplicate {field} identity")
def _digest_tuple(value: object, field: str) -> None:
_text_tuple(value, field)
assert isinstance(value, tuple)
for item in value:
_digest(item, field)
def _region_contains(region: SourceRegion, location: SourceLocation) -> bool:
return location.path == region.path and region.start_line <= location.line <= region.end_line
def _scope_contains(scope: AnalyzedScope, location: SourceLocation) -> bool:
return any(_region_contains(region, location) for region in scope.regions)
def _members(record: EvidenceRecord, role: str) -> tuple[SemanticRelation | Observation, ...]:
transitions = tuple(item for item in record.packet.transitions if item.role == role)
observations = tuple(item for item in record.packet.observations if item.role == role)
return (*transitions, *observations)
def _question_state(record: EvidenceRecord, question: ResidualQuestion) -> tuple[bool, tuple[str, ...]]:
reasons: set[str] = set()
if record.decision.family_code not in question.allowed_decisions:
reasons.add(f"decision_not_declared_for_question:{record.decision.family_code}")
for role in question.required_roles:
members = _members(record, role)
if not members:
reasons.add(f"missing_required_role:{role}")
elif not any(
item.status is RelationStatus.COMPLETE and _scope_contains(record.decision.bounded_scope, item.location) for item in members
):
relation_reasons = {reason for item in members if isinstance(item, SemanticRelation) for reason in item.reasons}
complete_outside = any(item.status is RelationStatus.COMPLETE for item in members)
fallback = f"evidence_outside_bounded_scope:{role}" if complete_outside else f"incomplete_required_role:{role}"
reasons.update(relation_reasons or record.packet.completeness_reasons or (fallback,))
if record.decision.outcome is NeutralOutcome.INDETERMINATE:
reasons.update(record.decision.reasons)
complete = not reasons and record.decision.outcome is not NeutralOutcome.INDETERMINATE
return complete, tuple(sorted(reasons))
def _location(record: EvidenceRecord, question: ResidualQuestion) -> tuple[SourceLocation, SourceRegion]:
candidates = tuple(
item.location for role in question.required_roles for item in _members(record, role) if item.status is RelationStatus.COMPLETE
)
ordered = sorted(candidates, key=lambda item: (item.path, item.line, -1 if item.column is None else item.column, item.symbol or ""))
for location in ordered:
anchors = tuple(region for region in record.decision.bounded_scope.regions if _region_contains(region, location))
if anchors:
anchor = min(anchors, key=lambda item: (item.end_line - item.start_line, item.path, item.start_line))
return location, anchor
raise VerdictContractError("complete defect evidence has no exact location inside its bounded change scope")
def _validate_inputs(records: Sequence[EvidenceRecord], contract: FamilyContract, coverage_digest: str) -> tuple[EvidenceRecord, ...]:
_digest(coverage_digest, "coverage")
try:
validate_contract(contract)
except (TypeError, ValueError) as exc:
raise VerdictContractError(f"family contract is invalid: {exc}") from exc
values = tuple(records)
if not values or not all(isinstance(record, EvidenceRecord) for record in values):
raise VerdictContractError("records must be a nonempty sequence of EvidenceRecord values")
identities: set[str] = set()
try:
for record in values:
record.__post_init__()
if record.record_id != derive_record_id(record.packet, record.decision):
raise EvidenceValidationError("record identity cannot be re-derived")
if record.record_id in identities:
raise EvidenceValidationError("duplicate evidence record identity")
identities.add(record.record_id)
validate_scope(record.packet, record.packet.scope)
validate_record(contract, record)
except (TypeError, ValueError, EvidenceValidationError, ScopeValidationError, FamilyContractError) as exc:
raise VerdictContractError(f"evidence record validation failed: {exc}") from exc
return values
def _silence(
question: ResidualQuestion,
reasons: set[str],
record_ids: set[str],
coverage_digest: str,
) -> BoundedSilence | None:
if not reasons:
return None
return BoundedSilence(
(question.question_id,),
tuple(sorted(reasons)),
tuple(sorted(record_ids)),
coverage_digest,
)
def derive_verdict_outputs(
records: Sequence[EvidenceRecord],
contract: FamilyContract,
coverage_digest: str,
) -> VerdictOutputs:
"""Derive bounded outputs without converting incomplete evidence to safety."""
validated = _validate_inputs(records, contract, coverage_digest)
defects: dict[str, DefectClaim] = {}
satisfactions: dict[str, set[str]] = {}
silence: list[BoundedSilence] = []
for question in contract.residual_questions:
reasons: set[str] = set()
undecided: set[str] = set()
for record in validated:
complete, record_reasons = _question_state(record, question)
if not complete:
reasons.update(record_reasons)
undecided.add(record.record_id)
continue
if record.decision.outcome is NeutralOutcome.VIOLATION:
if record.record_id not in defects:
location, anchor = _location(record, question)
defects[record.record_id] = DefectClaim(record, record.decision.basis, location, anchor)
elif record.decision.outcome is NeutralOutcome.SATISFIED:
satisfactions.setdefault(record.record_id, set()).add(question.question_id)
bounded = _silence(question, reasons, undecided, coverage_digest)
if bounded is not None:
silence.append(bounded)
records_by_id = {record.record_id: record for record in validated}
satisfied = tuple(
BoundedSatisfaction(tuple(sorted(question_ids)), (records_by_id[record_id],), records_by_id[record_id].decision.bounded_scope)
for record_id, question_ids in sorted(satisfactions.items())
)
return VerdictOutputs(
tuple(defects[identity] for identity in sorted(defects)),
satisfied,
tuple(sorted(silence, key=lambda item: item.question_ids)),
)
__all__ = [
"BoundedSatisfaction",
"BoundedSilence",
"DefectClaim",
"VerdictContractError",
"VerdictOutputs",
"derive_verdict_outputs",
]
-271
View File
@@ -1,271 +0,0 @@
"""Bounded v2 verdict outputs derived from complete evidence records."""
from __future__ import annotations
import re
from collections.abc import Sequence
from dataclasses import dataclass
from openultrasast.core.evidence import (
AnalyzedScope,
EvidenceBasis,
EvidenceRecord,
EvidenceValidationError,
NeutralOutcome,
ScopeValidationError,
SourceRegion,
derive_record_id,
validate_scope,
)
from openultrasast.core.family import FamilyContract, FamilyContractError, ResidualQuestion, validate_contract, validate_record
from openultrasast.core.semantics import Observation, RelationStatus, SemanticRelation, SourceLocation
_DIGEST = re.compile(r"[0-9a-f]{64}")
class VerdictContractError(ValueError):
"""Validated evidence cannot be represented as a bounded verdict."""
@dataclass(frozen=True)
class DefectClaim:
record: EvidenceRecord
basis: EvidenceBasis
location: SourceLocation
change_anchor: SourceRegion
def __post_init__(self) -> None:
if not isinstance(self.record, EvidenceRecord) or self.record.decision.outcome is not NeutralOutcome.VIOLATION:
raise VerdictContractError("defect claim requires a violation evidence record")
try:
self.record.__post_init__()
validate_scope(self.record.packet, self.record.packet.scope)
except (TypeError, ValueError) as exc:
raise VerdictContractError(f"defect claim evidence record is invalid: {exc}") from exc
if not isinstance(self.basis, EvidenceBasis) or self.basis is not self.record.decision.basis:
raise VerdictContractError("defect claim basis must equal its evidence decision basis")
if not isinstance(self.location, SourceLocation) or not isinstance(self.change_anchor, SourceRegion):
raise VerdictContractError("defect claim requires an exact location and change anchor")
members: tuple[SemanticRelation | Observation, ...] = (
*self.record.packet.transitions,
*self.record.packet.observations,
)
if self.record.packet.completeness_reasons or any(item.status is not RelationStatus.COMPLETE for item in members):
raise VerdictContractError("defect claim requires complete evidence")
if self.location not in tuple(item.location for item in members):
raise VerdictContractError("defect location must name an exact evidence location")
if self.change_anchor not in self.record.decision.bounded_scope.regions:
raise VerdictContractError("defect change anchor must belong to the bounded decision scope")
if not _region_contains(self.change_anchor, self.location):
raise VerdictContractError("defect location is outside its change anchor")
@dataclass(frozen=True)
class BoundedSatisfaction:
question_ids: tuple[str, ...]
records: tuple[EvidenceRecord, ...]
scope: AnalyzedScope
def __post_init__(self) -> None:
_text_tuple(self.question_ids, "satisfaction question")
if (
not isinstance(self.records, tuple)
or not self.records
or not all(isinstance(record, EvidenceRecord) for record in self.records)
):
raise VerdictContractError("bounded satisfaction requires evidence records")
if not isinstance(self.scope, AnalyzedScope):
raise VerdictContractError("bounded satisfaction requires an AnalyzedScope")
if any(record.decision.outcome is not NeutralOutcome.SATISFIED for record in self.records):
raise VerdictContractError("bounded satisfaction accepts only satisfied evidence records")
if any(record.decision.bounded_scope != self.scope for record in self.records):
raise VerdictContractError("satisfaction scope must equal every supporting record scope")
@dataclass(frozen=True)
class BoundedSilence:
question_ids: tuple[str, ...]
reasons: tuple[str, ...]
evidence_record_digests: tuple[str, ...]
coverage_digest: str
def __post_init__(self) -> None:
_text_tuple(self.question_ids, "silence question")
_text_tuple(self.reasons, "silence reason")
_digest_tuple(self.evidence_record_digests, "silence evidence record")
_digest(self.coverage_digest, "coverage")
@dataclass(frozen=True)
class VerdictOutputs:
defects: tuple[DefectClaim, ...]
satisfactions: tuple[BoundedSatisfaction, ...]
silence: tuple[BoundedSilence, ...]
def __post_init__(self) -> None:
values = (
(self.defects, DefectClaim, "defects"),
(self.satisfactions, BoundedSatisfaction, "satisfactions"),
(self.silence, BoundedSilence, "silence"),
)
for items, item_type, field in values:
if not isinstance(items, tuple) or not all(isinstance(item, item_type) for item in items):
raise VerdictContractError(f"{field} must be a tuple of {item_type.__name__}")
def _digest(value: object, field: str) -> None:
if not isinstance(value, str) or _DIGEST.fullmatch(value) is None:
raise VerdictContractError(f"{field} must be a full lowercase SHA-256 digest")
def _text_tuple(value: object, field: str) -> None:
if not isinstance(value, tuple) or not value or not all(isinstance(item, str) and item for item in value):
raise VerdictContractError(f"{field} identities must be a nonempty tuple")
if len(set(value)) != len(value):
raise VerdictContractError(f"duplicate {field} identity")
def _digest_tuple(value: object, field: str) -> None:
_text_tuple(value, field)
assert isinstance(value, tuple)
for item in value:
_digest(item, field)
def _region_contains(region: SourceRegion, location: SourceLocation) -> bool:
return location.path == region.path and region.start_line <= location.line <= region.end_line
def _scope_contains(scope: AnalyzedScope, location: SourceLocation) -> bool:
return any(_region_contains(region, location) for region in scope.regions)
def _members(record: EvidenceRecord, role: str) -> tuple[SemanticRelation | Observation, ...]:
transitions = tuple(item for item in record.packet.transitions if item.role == role)
observations = tuple(item for item in record.packet.observations if item.role == role)
return (*transitions, *observations)
def _question_state(record: EvidenceRecord, question: ResidualQuestion) -> tuple[bool, tuple[str, ...]]:
reasons: set[str] = set()
if record.decision.family_code not in question.allowed_decisions:
reasons.add(f"decision_not_declared_for_question:{record.decision.family_code}")
for role in question.required_roles:
members = _members(record, role)
if not members:
reasons.add(f"missing_required_role:{role}")
elif not any(
item.status is RelationStatus.COMPLETE and _scope_contains(record.decision.bounded_scope, item.location) for item in members
):
relation_reasons = {reason for item in members if isinstance(item, SemanticRelation) for reason in item.reasons}
complete_outside = any(item.status is RelationStatus.COMPLETE for item in members)
fallback = f"evidence_outside_bounded_scope:{role}" if complete_outside else f"incomplete_required_role:{role}"
reasons.update(relation_reasons or record.packet.completeness_reasons or (fallback,))
if record.decision.outcome is NeutralOutcome.INDETERMINATE:
reasons.update(record.decision.reasons)
complete = not reasons and record.decision.outcome is not NeutralOutcome.INDETERMINATE
return complete, tuple(sorted(reasons))
def _location(record: EvidenceRecord, question: ResidualQuestion) -> tuple[SourceLocation, SourceRegion]:
candidates = tuple(
item.location for role in question.required_roles for item in _members(record, role) if item.status is RelationStatus.COMPLETE
)
ordered = sorted(candidates, key=lambda item: (item.path, item.line, -1 if item.column is None else item.column, item.symbol or ""))
for location in ordered:
anchors = tuple(region for region in record.decision.bounded_scope.regions if _region_contains(region, location))
if anchors:
anchor = min(anchors, key=lambda item: (item.end_line - item.start_line, item.path, item.start_line))
return location, anchor
raise VerdictContractError("complete defect evidence has no exact location inside its bounded change scope")
def _validate_inputs(records: Sequence[EvidenceRecord], contract: FamilyContract, coverage_digest: str) -> tuple[EvidenceRecord, ...]:
_digest(coverage_digest, "coverage")
try:
validate_contract(contract)
except (TypeError, ValueError) as exc:
raise VerdictContractError(f"family contract is invalid: {exc}") from exc
values = tuple(records)
if not values or not all(isinstance(record, EvidenceRecord) for record in values):
raise VerdictContractError("records must be a nonempty sequence of EvidenceRecord values")
identities: set[str] = set()
try:
for record in values:
record.__post_init__()
if record.record_id != derive_record_id(record.packet, record.decision):
raise EvidenceValidationError("record identity cannot be re-derived")
if record.record_id in identities:
raise EvidenceValidationError("duplicate evidence record identity")
identities.add(record.record_id)
validate_scope(record.packet, record.packet.scope)
validate_record(contract, record)
except (TypeError, ValueError, EvidenceValidationError, ScopeValidationError, FamilyContractError) as exc:
raise VerdictContractError(f"evidence record validation failed: {exc}") from exc
return values
def _silence(
question: ResidualQuestion,
reasons: set[str],
record_ids: set[str],
coverage_digest: str,
) -> BoundedSilence | None:
if not reasons:
return None
return BoundedSilence(
(question.question_id,),
tuple(sorted(reasons)),
tuple(sorted(record_ids)),
coverage_digest,
)
def derive_verdict_outputs(
records: Sequence[EvidenceRecord],
contract: FamilyContract,
coverage_digest: str,
) -> VerdictOutputs:
"""Derive bounded outputs without converting incomplete evidence to safety."""
validated = _validate_inputs(records, contract, coverage_digest)
defects: dict[str, DefectClaim] = {}
satisfactions: dict[str, set[str]] = {}
silence: list[BoundedSilence] = []
for question in contract.residual_questions:
reasons: set[str] = set()
undecided: set[str] = set()
for record in validated:
complete, record_reasons = _question_state(record, question)
if not complete:
reasons.update(record_reasons)
undecided.add(record.record_id)
continue
if record.decision.outcome is NeutralOutcome.VIOLATION:
if record.record_id not in defects:
location, anchor = _location(record, question)
defects[record.record_id] = DefectClaim(record, record.decision.basis, location, anchor)
elif record.decision.outcome is NeutralOutcome.SATISFIED:
satisfactions.setdefault(record.record_id, set()).add(question.question_id)
bounded = _silence(question, reasons, undecided, coverage_digest)
if bounded is not None:
silence.append(bounded)
records_by_id = {record.record_id: record for record in validated}
satisfied = tuple(
BoundedSatisfaction(tuple(sorted(question_ids)), (records_by_id[record_id],), records_by_id[record_id].decision.bounded_scope)
for record_id, question_ids in sorted(satisfactions.items())
)
return VerdictOutputs(
tuple(defects[identity] for identity in sorted(defects)),
satisfied,
tuple(sorted(silence, key=lambda item: item.question_ids)),
)
__all__ = [
"BoundedSatisfaction",
"BoundedSilence",
"DefectClaim",
"VerdictContractError",
"VerdictOutputs",
"derive_verdict_outputs",
]
+17 -3
View File
@@ -7,6 +7,8 @@ import subprocess
from dataclasses import dataclass
from pathlib import Path
from core_migration_scope import CUTOVER_DELETIONS
REPO = Path(__file__).resolve().parents[1]
_EXCLUDED_PREFIXES = ("benchmarks/fixtures/", "benchmarks/pairs/", "tests/fixtures/")
_LEGACY_IMPORTS: dict[str, frozenset[str] | None] = {
@@ -47,7 +49,7 @@ _LEGACY_IMPORTS: dict[str, frozenset[str] | None] = {
}
),
"openultrasast.core.budget": frozenset({"Budget", "Complexity"}),
"openultrasast.core.consumer": None,
"openultrasast.core.consumer": frozenset({"consume_verdict"}),
"openultrasast.core.coverage": frozenset({"Coverage", "InstrumentFailure", "ResolutionOutcome", "check_reads"}),
"openultrasast.core.factpack": frozenset({"FactPackError", "admit_fact_pack", "admit_pack_file", "load_pack_file", "verify_fact_pack"}),
"openultrasast.core.probes": frozenset(
@@ -62,7 +64,7 @@ _LEGACY_IMPORTS: dict[str, frozenset[str] | None] = {
),
"openultrasast.core.provenance": frozenset({"ProvenanceMismatch", "stamp", "verify_stamp"}),
"openultrasast.core.trace": None,
"openultrasast.core.verdict": None,
"openultrasast.core.verdict": frozenset({"Judgment", "ProvenClaim", "as_clean"}),
}
_TOP_LEVEL_COMPATIBILITY = _LEGACY_IMPORTS["openultrasast.core"] or frozenset()
@@ -80,10 +82,22 @@ class ReaderAuditReport:
def tracked_python() -> tuple[Path, ...]:
completed = subprocess.run(["git", "ls-files", "*.py"], cwd=REPO, check=True, capture_output=True, text=True)
deleted = set(
subprocess.run(
["git", "ls-files", "--deleted", "--", "*.py"],
cwd=REPO,
check=True,
capture_output=True,
text=True,
).stdout.splitlines()
)
unexpected = deleted - CUTOVER_DELETIONS
if unexpected:
raise LegacyReaderAuditError(f"unexpected tracked Python deletion: {min(unexpected)}")
return tuple(
REPO / relative
for relative in completed.stdout.splitlines()
if relative.endswith(".py") and not relative.startswith(_EXCLUDED_PREFIXES)
if relative.endswith(".py") and relative not in deleted and not relative.startswith(_EXCLUDED_PREFIXES)
)
+18 -6
View File
@@ -18,6 +18,7 @@ from core_migration_scope import (
COMPATIBILITY_PATHS,
COMPATIBILITY_SHIMS,
FROZEN_TOP_LEVEL_V1,
REPLACED_SHIM_MEMBERS,
REPO,
MigrationScopeError,
authoritative_cutover_scope,
@@ -29,9 +30,7 @@ from core_migration_scope import (
relative_path,
scope_identity,
)
from core_migration_scope import (
static_top_level_v1 as _static_top_level_v1,
)
from core_migration_scope import static_top_level_exports as _static_top_level_exports
class LegacyBoundaryError(ValueError):
@@ -71,9 +70,9 @@ class _Bindings:
import_functions: frozenset[str]
def static_top_level_v1(root: Path = REPO) -> tuple[str, ...]:
def static_top_level_exports(root: Path = REPO) -> tuple[str, ...]:
try:
return _static_top_level_v1(root)
return _static_top_level_exports(root)
except MigrationScopeError as exc:
raise LegacyBoundaryError(str(exc)) from exc
@@ -215,6 +214,19 @@ def _classify_import_from(
legacy.add(_site(relative, node, parents, "compatibility_shim_import", shim))
if module in COMPATIBILITY_SHIMS:
legacy.add(_site(relative, node, parents, "compatibility_shim_import", module))
_record_replaced_shim_members(relative, node, parents, module, names, legacy)
def _record_replaced_shim_members(
relative: str,
node: ast.ImportFrom,
parents: dict[ast.AST, ast.AST],
module: str,
names: set[str],
legacy: set[str],
) -> None:
for name in sorted(names.intersection(REPLACED_SHIM_MEMBERS.get(module, frozenset()))):
legacy.add(_site(relative, node, parents, "retired_shim_member_import", f"{module}.{name}"))
def _reference_sites(
@@ -449,5 +461,5 @@ __all__ = [
"boundary_python",
"inspect_legacy_boundary",
"require_cutover_ready",
"static_top_level_v1",
"static_top_level_exports",
]
+29 -1
View File
@@ -14,6 +14,7 @@ from pathlib import Path
from benchmark_migration_evidence import BENCHMARK_TERMINAL_EVIDENCE, BenchmarkTerminalEvidence
from consumer_migration_evidence import CONSUMER_TERMINAL_EVIDENCE, REVIEWED_SUCCESSOR_MODULES, ConsumerTerminalEvidence
from core_migration_scope import CUTOVER_DELETIONS
from openultrasast.core.legacy import LegacyArtifact, decode_legacy_artifact
@@ -830,16 +831,43 @@ def _approved_terminal_source_closure(entry: LegacyInventoryEntry, *, root: Path
return True
if _benchmark_terminal_source_closure(entry, root=root, tree=tree):
return True
transfer = _APPROVED_MIXED_TRANSFERS.get(entry.entry_id)
if (
transfer is not None
and entry.disposition is not MigrationDisposition.OPEN
and entry.entry_id == entry_identity(entry)
and _stable_entry_fields(entry) == transfer[0]
and _cutover_complete(root)
and not any(_exact_anchor_present(anchor, tree) for anchor in entry.capability_anchors)
):
return True
approved = _APPROVED_TERMINAL_SOURCE_CLOSURES.get(entry.entry_id) is entry.disposition
return approved and entry.entry_id == entry_identity(entry)
def _cutover_complete(root: Path) -> bool:
if any((root / relative).exists() for relative in CUTOVER_DELETIONS):
return False
public = tuple(root / f"src/openultrasast/core/{name}.py" for name in ("__init__", "admission", "consumer", "verdict"))
return all(path.is_file() and path.stat().st_size > 0 for path in public)
def _approved_cutover_deletion(entry: LegacyInventoryEntry, *, root: Path) -> bool:
if entry.disposition is MigrationDisposition.OPEN or entry.path not in CUTOVER_DELETIONS or entry.entry_id != entry_identity(entry):
return False
return _cutover_complete(root)
def validate_inventory_sources(inventory: LegacyMigrationInventory, *, root: Path) -> None:
"""Prove sources are readable and every open migration target remains present."""
for entry in inventory.entries:
path = root / entry.path
try:
content = path.read_bytes()
except FileNotFoundError as exc:
if _approved_cutover_deletion(entry, root=root):
continue
raise LegacyInventoryError(f"inventory source is not readable: {entry.path}") from exc
except OSError as exc:
raise LegacyInventoryError(f"inventory source is not readable: {entry.path}") from exc
if not content:
@@ -849,8 +877,8 @@ def validate_inventory_sources(inventory: LegacyMigrationInventory, *, root: Pat
except (SyntaxError, UnicodeDecodeError) as exc:
raise LegacyInventoryError(f"inventory source is not parseable: {entry.path}") from exc
is_shim = _explicit_legacy_shim(tree, entry.symbol)
moved_owner = _moved_mixed_v1_owner(entry, root=root)
terminal_closure = _approved_terminal_source_closure(entry, root=root, tree=tree)
moved_owner = False if terminal_closure else _moved_mixed_v1_owner(entry, root=root)
if entry.symbol not in _defined_symbols(tree) and not is_shim and not moved_owner and not terminal_closure:
raise LegacyInventoryError(f"inventory target is absent: {entry.path}:{entry.symbol}")
missing = _missing_anchors(entry, tree)
+36 -7
View File
@@ -15,10 +15,14 @@ COMPATIBILITY_PATHS = frozenset(
src/openultrasast/core/admission.py src/openultrasast/core/consumer.py
src/openultrasast/core/trace.py src/openultrasast/core/verdict.py""".split() # noqa: SIM905
)
COMPATIBILITY_SHIMS = frozenset(
"""openultrasast.core._legacy_factpack openultrasast.core.admission
openultrasast.core.consumer openultrasast.core.trace openultrasast.core.verdict""".split() # noqa: SIM905
)
COMPATIBILITY_SHIMS = frozenset(["openultrasast.core._legacy_factpack", "openultrasast.core.trace"])
REPLACED_SHIM_MEMBERS = {
"openultrasast.core.admission": frozenset(
{"REPORT_SCHEMA_KEYS", "AdmissionManifest", "AdmissionManifestError", "validate_admission_manifest", "validate_report_schema"}
),
"openultrasast.core.consumer": frozenset({"consume_verdict"}),
"openultrasast.core.verdict": frozenset({"Judgment", "ProvenClaim", "as_clean"}),
}
FROZEN_TOP_LEVEL_V1 = tuple(
"""REPORT_SCHEMA_KEYS AdmissionManifest AdmissionManifestError BoundedSilence Budget BudgetGateFailure
Complexity Coverage FactPack FactPackError FittedComponentBlocked Guard Hop InstrumentFailure Judgment ProbeResult
@@ -27,6 +31,17 @@ FROZEN_TOP_LEVEL_V1 = tuple(
require_admission_evidence reversed_pair_probe stamp validate_admission_manifest validate_report_schema
verify_fact_pack verify_stamp""".split() # noqa: SIM905
)
CUTOVER_DELETIONS = frozenset(
{
"src/openultrasast/core/_legacy_factpack.py",
"src/openultrasast/core/consumer_v2.py",
"src/openultrasast/core/legacy_v1/__init__.py",
"src/openultrasast/core/legacy_v1/contracts.py",
"src/openultrasast/core/legacy_v1/writer.py",
"src/openultrasast/core/trace.py",
"src/openultrasast/core/verdict_v2.py",
}
)
class MigrationScopeError(ValueError):
@@ -47,6 +62,19 @@ def boundary_python(root: Path = REPO) -> tuple[Path, ...]:
commands = (("git", "ls-files", "*.py"), ("git", "ls-files", "--others", "--exclude-standard", "--", "*.py"))
outputs = (subprocess.run(command, cwd=root, check=True, capture_output=True, text=True).stdout for command in commands)
relatives = {value for output in outputs for value in output.splitlines() if _owned_python(value)}
deleted = set(
subprocess.run(
("git", "ls-files", "--deleted", "--", "*.py"),
cwd=root,
check=True,
capture_output=True,
text=True,
).stdout.splitlines()
)
unexpected = deleted - CUTOVER_DELETIONS
if unexpected:
raise MigrationScopeError(f"unexpected tracked Python deletion: {min(unexpected)}")
relatives.difference_update(deleted)
return tuple(root / relative for relative in sorted(relatives))
@@ -149,8 +177,8 @@ def relative_path(path: Path, root: Path) -> str:
return path.as_posix()
def static_top_level_v1(root: Path = REPO) -> tuple[str, ...]:
"""Read the migration shim's literal ``__all__`` without importing it."""
def static_top_level_exports(root: Path = REPO) -> tuple[str, ...]:
"""Read the public package's literal ``__all__`` without importing it."""
path = root / "src/openultrasast/core/__init__.py"
try:
content = path.read_bytes()
@@ -185,6 +213,7 @@ __all__ = [
"FROZEN_TOP_LEVEL_V1",
"COMPATIBILITY_PATHS",
"COMPATIBILITY_SHIMS",
"CUTOVER_DELETIONS",
"MigrationScopeError",
"REPO",
"authoritative_cutover_scope",
@@ -197,5 +226,5 @@ __all__ = [
"relative_path",
"scope_identity_error",
"scope_identity",
"static_top_level_v1",
"static_top_level_exports",
]
+1 -1
View File
@@ -50,7 +50,7 @@ from openultrasast.core.family_catalog import FamilyCatalog, FamilyCatalogEntry,
from openultrasast.core.measurement import DenominatorPolicy, IntervalMethod, MetricId, ScopeKey, standard_metric_rules
from openultrasast.core.readiness import ReadinessResult
from openultrasast.core.semantics import RelationStatus
from openultrasast.core.verdict_v2 import derive_verdict_outputs
from openultrasast.core.verdict import derive_verdict_outputs
class _InputManifestSubclass(InputManifest):
+2 -2
View File
@@ -19,10 +19,10 @@ from test_core_validation_v2 import (
_with_fact_set,
)
import openultrasast.core.consumer_v2 as consumer_v2
import openultrasast.core.consumer as consumer_v2
from openultrasast.core import budget, canonical, evidence, validation
from openultrasast.core._proof_attestation import proof_attestation_bytes
from openultrasast.core.consumer_v2 import ConsumerContractError, PushRequest, consume_records, make_push_request
from openultrasast.core.consumer import ConsumerContractError, PushRequest, consume_records, make_push_request
from openultrasast.core.family import FamilyContract
from openultrasast.core.proof import (
DIFFERENTIAL_PROOF_SCHEMA,
+165
View File
@@ -0,0 +1,165 @@
"""Final deletion-backed gate for the detection-core v1 migration."""
from __future__ import annotations
import ast
import hashlib
import json
import subprocess
import sys
from pathlib import Path
from core_migration_boundary import BoundaryPhase, boundary_python, inspect_legacy_boundary, require_cutover_ready
from core_migration_inventory import load_migration_inventory, validate_historical_artifacts
REPO = Path(__file__).resolve().parents[1]
CORE = REPO / "src/openultrasast/core"
INVENTORY = REPO / "tests/fixtures/detection_core/v1/migration_inventory.json"
REMOVED_PATHS = (
CORE / "legacy_v1",
CORE / "trace.py",
CORE / "_legacy_factpack.py",
CORE / "consumer_v2.py",
CORE / "verdict_v2.py",
)
CANONICAL_V2_MODULES = ("admission", "consumer", "verdict")
OLD_TOP_LEVEL = (
"AdmissionManifest",
"Budget",
"Coverage",
"FactPack",
"PushRequest",
"PushVerdict",
"TraceRecord",
)
def _module_exports(path: Path) -> tuple[str, ...]:
content = path.read_bytes()
assert content, f"public-surface instrument must read nonzero bytes from {path}"
tree = ast.parse(content, filename=path.relative_to(REPO).as_posix())
declarations = [
node
for node in tree.body
if isinstance(node, ast.Assign) and any(isinstance(target, ast.Name) and target.id == "__all__" for target in node.targets)
]
assert len(declarations) == 1
value = declarations[0].value
assert isinstance(value, (ast.List, ast.Tuple))
assert all(isinstance(item, ast.Constant) and isinstance(item.value, str) for item in value.elts)
return tuple(item.value for item in value.elts)
def _fresh_import_probe(*module_names: str) -> dict[str, object]:
program = """
import importlib
import json
import sys
core = importlib.import_module("openultrasast" + ".core")
result = {
"all": tuple(core.__all__),
"old_top_level": tuple(name for name in sys.argv[1].split(",") if hasattr(core, name)),
"modules": {},
}
for name in sys.argv[2:]:
try:
module = importlib.import_module(name)
except ModuleNotFoundError:
result["modules"][name] = None
else:
result["modules"][name] = tuple(module.__all__)
print(json.dumps(result, sort_keys=True))
"""
completed = subprocess.run(
[sys.executable, "-c", program, ",".join(OLD_TOP_LEVEL), *module_names],
cwd=REPO,
check=True,
capture_output=True,
text=True,
env={"PYTHONPATH": str(REPO / "src")},
)
assert completed.stdout, "fresh import probe must produce nonzero output"
value = json.loads(completed.stdout)
assert isinstance(value, dict)
return value
def test_cutover_deletes_every_v1_write_and_temporary_shim_path() -> None:
for path in REMOVED_PATHS:
assert not path.exists(), f"cutover residue remains: {path.relative_to(REPO)}"
for name in CANONICAL_V2_MODULES:
path = CORE / f"{name}.py"
assert path.is_file()
assert path.stat().st_size > 0
def test_fresh_interpreter_rejects_old_imports_and_loads_canonical_v2() -> None:
old_modules = (
"openultrasast.core.legacy_v1",
"openultrasast.core.trace",
"openultrasast.core._legacy_factpack",
"openultrasast.core." + "consumer_v2",
"openultrasast.core." + "verdict_v2",
)
canonical = tuple(f"openultrasast.core.{name}" for name in CANONICAL_V2_MODULES)
result = _fresh_import_probe(*old_modules, *canonical)
assert result["old_top_level"] == []
assert set(CANONICAL_V2_MODULES) <= set(result["all"])
modules = result["modules"]
assert isinstance(modules, dict)
assert all(modules[name] is None for name in old_modules)
assert "AdmissionManifestV2" in modules[canonical[0]]
assert "consume_records" in modules[canonical[1]]
assert "derive_verdict_outputs" in modules[canonical[2]]
def test_public_v2_surface_has_no_frozen_v1_names() -> None:
exports = _module_exports(CORE / "__init__.py")
assert set(CANONICAL_V2_MODULES) <= set(exports)
assert set(OLD_TOP_LEVEL).isdisjoint(exports)
def test_repository_post_deletion_boundary_is_nonzero_and_exact_zero() -> None:
paths = boundary_python()
inventory = load_migration_inventory(INVENTORY)
report = inspect_legacy_boundary(
paths,
frozen_inventory=inventory,
candidate_inventory=inventory,
expected_scope=paths,
root=REPO,
phase=BoundaryPhase.POST_DELETION,
)
assert report.files_read == len(paths) > 0
assert report.bytes_read > 0
assert report.open_inventory_entries == ()
assert report.legacy_import_sites == ()
assert report.legacy_writer_sites == ()
assert report.consumer_legacy_import_sites == ()
assert report.consumer_legacy_writer_sites == ()
assert report.compatibility_implementation_sites == ()
assert report.forbidden_dynamic_sites == ()
require_cutover_ready(
report,
frozen_inventory=inventory,
candidate_inventory=inventory,
root=REPO,
)
def test_historical_decoder_remains_exact_read_only_and_outside_public_v2() -> None:
inventory_bytes = INVENTORY.read_bytes()
assert inventory_bytes, "historical gate must read nonzero inventory bytes"
inventory = load_migration_inventory(INVENTORY)
artifacts = validate_historical_artifacts(inventory, root=REPO)
assert len(artifacts) == 2
assert all(artifact.content for artifact in artifacts)
assert all(hashlib.sha256(artifact.content).hexdigest() == artifact.content_digest for artifact in artifacts)
legacy_exports = _module_exports(CORE / "legacy.py")
assert {"write", "serialize", "to_v2", "compare_identity"}.isdisjoint(legacy_exports)
assert "legacy" not in _module_exports(CORE / "__init__.py")
+16 -5
View File
@@ -14,7 +14,7 @@ from core_migration_boundary import (
boundary_python,
inspect_legacy_boundary,
require_cutover_ready,
static_top_level_v1,
static_top_level_exports,
)
from core_migration_inventory import (
LegacyInventoryError,
@@ -214,7 +214,9 @@ def test_cutover_refuses_empty_scope_and_migration_compatibility(tmp_path: Path)
def test_complete_frozen_top_level_surface_is_reported_and_star_is_forbidden(tmp_path: Path) -> None:
assert static_top_level_v1(REPO) == FROZEN_TOP_LEVEL_V1
exports = static_top_level_exports(REPO)
assert exports == ("admission", "consumer", "verdict")
assert set(exports).isdisjoint(FROZEN_TOP_LEVEL_V1)
assert len(FROZEN_TOP_LEVEL_V1) == 38
imports = ", ".join(FROZEN_TOP_LEVEL_V1)
attributes = "\n".join(f"value_{index} = core_api.{name}" for index, name in enumerate(FROZEN_TOP_LEVEL_V1))
@@ -283,16 +285,25 @@ def test_repository_scope_proves_nonzero_inputs_before_reporting_sites() -> None
paths = boundary_python()
frozen = load_migration_inventory(INVENTORY)
report = inspect_legacy_boundary(paths, frozen_inventory=frozen, root=REPO)
report = inspect_legacy_boundary(
paths,
frozen_inventory=frozen,
candidate_inventory=frozen,
expected_scope=paths,
root=REPO,
phase=BoundaryPhase.POST_DELETION,
)
assert report.files_read == len(paths) > 0
assert report.bytes_read > 0
assert report.legacy_import_sites
assert report.legacy_import_sites == ()
assert report.legacy_writer_sites == ()
assert report.open_inventory_entries == ()
assert report.consumer_legacy_import_sites == ()
assert report.consumer_legacy_writer_sites == ()
assert report.forbidden_dynamic_sites == ()
assert report.compatibility_implementation_sites
assert report.compatibility_implementation_sites == ()
_require_cutover(report, frozen=frozen, candidate=frozen, root=REPO)
def test_cutover_rejects_positive_but_incomplete_repository_scope() -> None:
+44 -68
View File
@@ -1,4 +1,4 @@
"""Structural ownership tests for the temporary v1 compatibility implementation."""
"""Structural ownership tests for the final v2-only core namespace."""
from __future__ import annotations
@@ -7,7 +7,7 @@ from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
CORE = REPO / "src/openultrasast/core"
PURE_COMPATIBILITY_MODULES = ("admission.py", "consumer.py", "trace.py", "verdict.py", "_legacy_factpack.py")
REMOVED_COMPATIBILITY = ("trace.py", "_legacy_factpack.py", "consumer_v2.py", "verdict_v2.py")
MIXED_LEGACY_NAMES = {
"budget": {"Budget", "Complexity"},
"coverage": {"Coverage", "ResolutionOutcome", "check_reads"},
@@ -38,76 +38,58 @@ def _imported(tree: ast.Module) -> set[str]:
return {alias.asname or alias.name for node in tree.body if isinstance(node, (ast.Import, ast.ImportFrom)) for alias in node.names}
def test_legacy_contract_values_have_one_compatibility_owner() -> None:
contracts = _tree(CORE / "legacy_v1/contracts.py")
expected = {
"AdmissionManifest",
"AdmissionManifestError",
"BoundedSilence",
"Budget",
"BudgetGateFailure",
"Complexity",
"Coverage",
"FactPackError",
"FittedComponentBlocked",
"Guard",
"Hop",
"InstrumentFailure",
"Judgment",
"LegacyFactPack",
"LegacyProbeResult",
"ProvenClaim",
"ProvenanceMismatch",
"PushRequest",
"PushVerdict",
"ResolutionOutcome",
"ShortcutDetected",
"TraceRecord",
"Witness",
}
assert expected <= _defined(contracts)
def test_pure_compatibility_modules_define_no_contract_or_serializer() -> None:
for name in PURE_COMPATIBILITY_MODULES:
tree = _tree(CORE / name)
assert not _defined(tree)
assert not any(isinstance(node, (ast.Import, ast.ImportFrom)) and "json" in ast.unparse(node) for node in tree.body)
assert any(isinstance(node, ast.ImportFrom) and node.module == "openultrasast.core.legacy_v1.contracts" for node in tree.body)
def test_writer_is_the_only_compatibility_json_serializer() -> None:
namespace = CORE / "legacy_v1"
paths = sorted(namespace.glob("*.py"))
assert {path.name for path in paths} == {"__init__.py", "contracts.py", "writer.py"}
json_importers = [
path.name
for path in paths
if any(isinstance(node, (ast.Import, ast.ImportFrom)) and "json" in ast.unparse(node) for node in _tree(path).body)
def _literal_all(tree: ast.Module) -> tuple[str, ...]:
values = [
node.value
for node in tree.body
if isinstance(node, ast.Assign) and any(isinstance(target, ast.Name) and target.id == "__all__" for target in node.targets)
]
assert json_importers == ["writer.py"]
assert len(values) == 1 and isinstance(values[0], (ast.List, ast.Tuple))
assert all(isinstance(item, ast.Constant) and isinstance(item.value, str) for item in values[0].elts)
return tuple(item.value for item in values[0].elts)
def test_namespace_and_v2_dependency_directions_are_closed() -> None:
for path in (CORE / "legacy_v1").glob("*.py"):
imports = {node.module for node in ast.walk(_tree(path)) if isinstance(node, ast.ImportFrom) and node.module}
assert all(module.startswith("openultrasast.core.legacy_v1") for module in imports if module.startswith("openultrasast"))
compatibility = {*PURE_COMPATIBILITY_MODULES, "legacy.py", "__init__.py"}
for path in CORE.glob("*.py"):
if path.name not in compatibility:
imports = {node.module for node in ast.walk(_tree(path)) if isinstance(node, ast.ImportFrom) and node.module}
assert not any(module.startswith("openultrasast.core.legacy_v1") for module in imports)
def test_compatibility_namespace_writer_and_temporary_shims_are_absent() -> None:
assert not (CORE / "legacy_v1").exists()
for name in REMOVED_COMPATIBILITY:
assert not (CORE / name).exists()
def test_historical_decoder_stays_independent_from_compatibility_namespace() -> None:
imports = {node.module for node in ast.walk(_tree(CORE / "legacy.py")) if isinstance(node, ast.ImportFrom)}
def test_canonical_v2_replacements_own_the_public_paths() -> None:
consumer = _tree(CORE / "consumer.py")
verdict = _tree(CORE / "verdict.py")
admission = _tree(CORE / "admission.py")
assert {"PushRequest", "PushVerdict", "consume_records"} <= _defined(consumer)
assert {"BoundedSilence", "DefectClaim", "derive_verdict_outputs"} <= _defined(verdict)
assert {"AdmissionManifestV2", "authorize_admission", "score_admission"} <= set(_literal_all(admission))
def test_core_modules_have_no_legacy_namespace_dependency() -> None:
paths = tuple(sorted(CORE.glob("*.py")))
assert paths and all(path.read_bytes() for path in paths)
for path in paths:
tree = _tree(path)
imports = {node.module for node in ast.walk(tree) if isinstance(node, ast.ImportFrom) and node.module}
imported_modules = {alias.name for node in ast.walk(tree) if isinstance(node, ast.Import) for alias in node.names}
assert not any(module.startswith("openultrasast.core.legacy_v1") for module in imports | imported_modules)
def test_historical_decoder_stays_independent_and_read_only() -> None:
tree = _tree(CORE / "legacy.py")
imports = {node.module for node in ast.walk(tree) if isinstance(node, ast.ImportFrom)}
exports = set(_literal_all(tree))
assert "openultrasast.core.legacy_v1" not in imports
assert {"write", "serialize", "to_v2", "compare_identity"}.isdisjoint(exports)
def test_top_level_v1_api_is_only_a_closed_namespace_shim() -> None:
def test_top_level_api_publishes_only_canonical_v2_modules() -> None:
tree = _tree(CORE / "__init__.py")
modules = {node.module for node in tree.body if isinstance(node, ast.ImportFrom)}
assert modules == {"openultrasast.core.legacy_v1"}
assert modules == {"openultrasast.core"}
assert _literal_all(tree) == ("admission", "consumer", "verdict")
assert not _defined(tree)
@@ -115,9 +97,3 @@ def test_mixed_v2_modules_expose_no_legacy_compatibility_names() -> None:
for module_name, legacy_names in MIXED_LEGACY_NAMES.items():
tree = _tree(CORE / f"{module_name}.py")
assert legacy_names.isdisjoint(_defined(tree) | _imported(tree))
def test_closed_namespace_is_the_only_owner_of_moved_mixed_names() -> None:
contracts = _tree(CORE / "legacy_v1/contracts.py")
expected = set().union(*MIXED_LEGACY_NAMES.values())
assert expected <= _defined(contracts)
+1 -1
View File
@@ -46,7 +46,7 @@ from openultrasast.core.semantics import (
make_observation,
make_relation,
)
from openultrasast.core.verdict_v2 import DefectClaim
from openultrasast.core.verdict import DefectClaim
def _digest(value: str) -> str:
+1 -1
View File
@@ -42,7 +42,7 @@ from openultrasast.core.semantics import (
SourceLocation,
make_relation,
)
from openultrasast.core.verdict_v2 import DefectClaim, VerdictContractError, derive_verdict_outputs
from openultrasast.core.verdict import DefectClaim, VerdictContractError, derive_verdict_outputs
def _digest(value: str) -> str: