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_ - _Requirements: 14.3, 15.3_
- _Boundary: Migration Tests and Operational Tooling_ - _Boundary: Migration Tests and Operational Tooling_
- _Depends: 10.4, 10.5_ - _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. - 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. - 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. - 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; from openultrasast.core import admission, consumer, verdict
runners and consumers adopt coverage, budget, provenance, and admission contracts.
"""
from openultrasast.core.legacy_v1 import ( __all__ = ["admission", "consumer", "verdict"]
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",
]
@@ -59,7 +59,7 @@ from openultrasast.core.semantics import (
SourceLocation, SourceLocation,
) )
from openultrasast.core.validation import EvidenceValidationInput 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], ...] = ( _DATACLASSES: tuple[type[object], ...] = (
ScopeKey, 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, ProofVerificationReceipt,
derive_producer_identity_digest, 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: 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 ( from openultrasast.core.admission_case import (
REPORT_SCHEMA_KEYS, ADMISSION_CASE_RELEASE_PAYLOAD_SCHEMA,
AdmissionManifest, ADMISSION_CASE_RELEASE_SCHEMA,
AdmissionManifestError, RELEASED_CASE_BINDING_SCHEMA,
validate_admission_manifest, AdmissionCaseRelease,
validate_report_schema, 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__ = [ __all__ = [
"REPORT_SCHEMA_KEYS", "ADMISSION_AUTHORIZATION_SCHEMA",
"AdmissionManifest", "ADMISSION_CASE_RELEASE_PAYLOAD_SCHEMA",
"AdmissionManifestError", "ADMISSION_CASE_RELEASE_SCHEMA",
"validate_admission_manifest", "ADMISSION_PREPARATION_SCHEMA",
"validate_report_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.proof import ProofAttachment
from openultrasast.core.readiness import ReadinessResult from openultrasast.core.readiness import ReadinessResult
from openultrasast.core.validation import EvidenceValidationInput 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_SCHEMA = "openultrasast.detection-core.admission-case-release.v2"
ADMISSION_CASE_RELEASE_PAYLOAD_SCHEMA = "openultrasast.detection-core.admission-case-release-payload.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.proof import ProofAttachment, attach_differential_proof
from openultrasast.core.readiness import ReadinessResult from openultrasast.core.readiness import ReadinessResult
from openultrasast.core.validation import validate_evidence_input 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): 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 import canonical
from openultrasast.core.semantics import SourceLocation 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" DIFFERENTIAL_PROOF_SCHEMA = "openultrasast.detection-core.differential-proof.v2"
_DIGEST = re.compile(r"[0-9a-f]{64}") _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 dataclasses import dataclass
from pathlib import Path from pathlib import Path
from core_migration_scope import CUTOVER_DELETIONS
REPO = Path(__file__).resolve().parents[1] REPO = Path(__file__).resolve().parents[1]
_EXCLUDED_PREFIXES = ("benchmarks/fixtures/", "benchmarks/pairs/", "tests/fixtures/") _EXCLUDED_PREFIXES = ("benchmarks/fixtures/", "benchmarks/pairs/", "tests/fixtures/")
_LEGACY_IMPORTS: dict[str, frozenset[str] | None] = { _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.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.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.factpack": frozenset({"FactPackError", "admit_fact_pack", "admit_pack_file", "load_pack_file", "verify_fact_pack"}),
"openultrasast.core.probes": frozenset( "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.provenance": frozenset({"ProvenanceMismatch", "stamp", "verify_stamp"}),
"openultrasast.core.trace": None, "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() _TOP_LEVEL_COMPATIBILITY = _LEGACY_IMPORTS["openultrasast.core"] or frozenset()
@@ -80,10 +82,22 @@ class ReaderAuditReport:
def tracked_python() -> tuple[Path, ...]: def tracked_python() -> tuple[Path, ...]:
completed = subprocess.run(["git", "ls-files", "*.py"], cwd=REPO, check=True, capture_output=True, text=True) 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( return tuple(
REPO / relative REPO / relative
for relative in completed.stdout.splitlines() 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_PATHS,
COMPATIBILITY_SHIMS, COMPATIBILITY_SHIMS,
FROZEN_TOP_LEVEL_V1, FROZEN_TOP_LEVEL_V1,
REPLACED_SHIM_MEMBERS,
REPO, REPO,
MigrationScopeError, MigrationScopeError,
authoritative_cutover_scope, authoritative_cutover_scope,
@@ -29,9 +30,7 @@ from core_migration_scope import (
relative_path, relative_path,
scope_identity, scope_identity,
) )
from core_migration_scope import ( from core_migration_scope import static_top_level_exports as _static_top_level_exports
static_top_level_v1 as _static_top_level_v1,
)
class LegacyBoundaryError(ValueError): class LegacyBoundaryError(ValueError):
@@ -71,9 +70,9 @@ class _Bindings:
import_functions: frozenset[str] 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: try:
return _static_top_level_v1(root) return _static_top_level_exports(root)
except MigrationScopeError as exc: except MigrationScopeError as exc:
raise LegacyBoundaryError(str(exc)) from 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)) legacy.add(_site(relative, node, parents, "compatibility_shim_import", shim))
if module in COMPATIBILITY_SHIMS: if module in COMPATIBILITY_SHIMS:
legacy.add(_site(relative, node, parents, "compatibility_shim_import", module)) 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( def _reference_sites(
@@ -449,5 +461,5 @@ __all__ = [
"boundary_python", "boundary_python",
"inspect_legacy_boundary", "inspect_legacy_boundary",
"require_cutover_ready", "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 benchmark_migration_evidence import BENCHMARK_TERMINAL_EVIDENCE, BenchmarkTerminalEvidence
from consumer_migration_evidence import CONSUMER_TERMINAL_EVIDENCE, REVIEWED_SUCCESSOR_MODULES, ConsumerTerminalEvidence 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 from openultrasast.core.legacy import LegacyArtifact, decode_legacy_artifact
@@ -830,16 +831,43 @@ def _approved_terminal_source_closure(entry: LegacyInventoryEntry, *, root: Path
return True return True
if _benchmark_terminal_source_closure(entry, root=root, tree=tree): if _benchmark_terminal_source_closure(entry, root=root, tree=tree):
return True 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 approved = _APPROVED_TERMINAL_SOURCE_CLOSURES.get(entry.entry_id) is entry.disposition
return approved and entry.entry_id == entry_identity(entry) 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: def validate_inventory_sources(inventory: LegacyMigrationInventory, *, root: Path) -> None:
"""Prove sources are readable and every open migration target remains present.""" """Prove sources are readable and every open migration target remains present."""
for entry in inventory.entries: for entry in inventory.entries:
path = root / entry.path path = root / entry.path
try: try:
content = path.read_bytes() 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: except OSError as exc:
raise LegacyInventoryError(f"inventory source is not readable: {entry.path}") from exc raise LegacyInventoryError(f"inventory source is not readable: {entry.path}") from exc
if not content: if not content:
@@ -849,8 +877,8 @@ def validate_inventory_sources(inventory: LegacyMigrationInventory, *, root: Pat
except (SyntaxError, UnicodeDecodeError) as exc: except (SyntaxError, UnicodeDecodeError) as exc:
raise LegacyInventoryError(f"inventory source is not parseable: {entry.path}") from exc raise LegacyInventoryError(f"inventory source is not parseable: {entry.path}") from exc
is_shim = _explicit_legacy_shim(tree, entry.symbol) 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) 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: 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}") raise LegacyInventoryError(f"inventory target is absent: {entry.path}:{entry.symbol}")
missing = _missing_anchors(entry, tree) 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/admission.py src/openultrasast/core/consumer.py
src/openultrasast/core/trace.py src/openultrasast/core/verdict.py""".split() # noqa: SIM905 src/openultrasast/core/trace.py src/openultrasast/core/verdict.py""".split() # noqa: SIM905
) )
COMPATIBILITY_SHIMS = frozenset( COMPATIBILITY_SHIMS = frozenset(["openultrasast.core._legacy_factpack", "openultrasast.core.trace"])
"""openultrasast.core._legacy_factpack openultrasast.core.admission REPLACED_SHIM_MEMBERS = {
openultrasast.core.consumer openultrasast.core.trace openultrasast.core.verdict""".split() # noqa: SIM905 "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( FROZEN_TOP_LEVEL_V1 = tuple(
"""REPORT_SCHEMA_KEYS AdmissionManifest AdmissionManifestError BoundedSilence Budget BudgetGateFailure """REPORT_SCHEMA_KEYS AdmissionManifest AdmissionManifestError BoundedSilence Budget BudgetGateFailure
Complexity Coverage FactPack FactPackError FittedComponentBlocked Guard Hop InstrumentFailure Judgment ProbeResult 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 require_admission_evidence reversed_pair_probe stamp validate_admission_manifest validate_report_schema
verify_fact_pack verify_stamp""".split() # noqa: SIM905 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): 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")) 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) 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)} 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)) 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() return path.as_posix()
def static_top_level_v1(root: Path = REPO) -> tuple[str, ...]: def static_top_level_exports(root: Path = REPO) -> tuple[str, ...]:
"""Read the migration shim's literal ``__all__`` without importing it.""" """Read the public package's literal ``__all__`` without importing it."""
path = root / "src/openultrasast/core/__init__.py" path = root / "src/openultrasast/core/__init__.py"
try: try:
content = path.read_bytes() content = path.read_bytes()
@@ -185,6 +213,7 @@ __all__ = [
"FROZEN_TOP_LEVEL_V1", "FROZEN_TOP_LEVEL_V1",
"COMPATIBILITY_PATHS", "COMPATIBILITY_PATHS",
"COMPATIBILITY_SHIMS", "COMPATIBILITY_SHIMS",
"CUTOVER_DELETIONS",
"MigrationScopeError", "MigrationScopeError",
"REPO", "REPO",
"authoritative_cutover_scope", "authoritative_cutover_scope",
@@ -197,5 +226,5 @@ __all__ = [
"relative_path", "relative_path",
"scope_identity_error", "scope_identity_error",
"scope_identity", "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.measurement import DenominatorPolicy, IntervalMethod, MetricId, ScopeKey, standard_metric_rules
from openultrasast.core.readiness import ReadinessResult from openultrasast.core.readiness import ReadinessResult
from openultrasast.core.semantics import RelationStatus 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): class _InputManifestSubclass(InputManifest):
+2 -2
View File
@@ -19,10 +19,10 @@ from test_core_validation_v2 import (
_with_fact_set, _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 import budget, canonical, evidence, validation
from openultrasast.core._proof_attestation import proof_attestation_bytes 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.family import FamilyContract
from openultrasast.core.proof import ( from openultrasast.core.proof import (
DIFFERENTIAL_PROOF_SCHEMA, 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, boundary_python,
inspect_legacy_boundary, inspect_legacy_boundary,
require_cutover_ready, require_cutover_ready,
static_top_level_v1, static_top_level_exports,
) )
from core_migration_inventory import ( from core_migration_inventory import (
LegacyInventoryError, 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: 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 assert len(FROZEN_TOP_LEVEL_V1) == 38
imports = ", ".join(FROZEN_TOP_LEVEL_V1) imports = ", ".join(FROZEN_TOP_LEVEL_V1)
attributes = "\n".join(f"value_{index} = core_api.{name}" for index, name in enumerate(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() paths = boundary_python()
frozen = load_migration_inventory(INVENTORY) 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.files_read == len(paths) > 0
assert report.bytes_read > 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.open_inventory_entries == ()
assert report.consumer_legacy_import_sites == () assert report.consumer_legacy_import_sites == ()
assert report.consumer_legacy_writer_sites == () assert report.consumer_legacy_writer_sites == ()
assert report.forbidden_dynamic_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: 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 from __future__ import annotations
@@ -7,7 +7,7 @@ from pathlib import Path
REPO = Path(__file__).resolve().parents[1] REPO = Path(__file__).resolve().parents[1]
CORE = REPO / "src/openultrasast/core" 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 = { MIXED_LEGACY_NAMES = {
"budget": {"Budget", "Complexity"}, "budget": {"Budget", "Complexity"},
"coverage": {"Coverage", "ResolutionOutcome", "check_reads"}, "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} 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: def _literal_all(tree: ast.Module) -> tuple[str, ...]:
contracts = _tree(CORE / "legacy_v1/contracts.py") values = [
expected = { node.value
"AdmissionManifest", for node in tree.body
"AdmissionManifestError", if isinstance(node, ast.Assign) and any(isinstance(target, ast.Name) and target.id == "__all__" for target in node.targets)
"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)
] ]
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: def test_compatibility_namespace_writer_and_temporary_shims_are_absent() -> None:
for path in (CORE / "legacy_v1").glob("*.py"): assert not (CORE / "legacy_v1").exists()
imports = {node.module for node in ast.walk(_tree(path)) if isinstance(node, ast.ImportFrom) and node.module} for name in REMOVED_COMPATIBILITY:
assert all(module.startswith("openultrasast.core.legacy_v1") for module in imports if module.startswith("openultrasast")) assert not (CORE / name).exists()
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_historical_decoder_stays_independent_from_compatibility_namespace() -> None: def test_canonical_v2_replacements_own_the_public_paths() -> None:
imports = {node.module for node in ast.walk(_tree(CORE / "legacy.py")) if isinstance(node, ast.ImportFrom)} 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 "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") tree = _tree(CORE / "__init__.py")
modules = {node.module for node in tree.body if isinstance(node, ast.ImportFrom)} 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) 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(): for module_name, legacy_names in MIXED_LEGACY_NAMES.items():
tree = _tree(CORE / f"{module_name}.py") tree = _tree(CORE / f"{module_name}.py")
assert legacy_names.isdisjoint(_defined(tree) | _imported(tree)) 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_observation,
make_relation, make_relation,
) )
from openultrasast.core.verdict_v2 import DefectClaim from openultrasast.core.verdict import DefectClaim
def _digest(value: str) -> str: def _digest(value: str) -> str:
+1 -1
View File
@@ -42,7 +42,7 @@ from openultrasast.core.semantics import (
SourceLocation, SourceLocation,
make_relation, 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: def _digest(value: str) -> str: