feat(detection-core): derive authenticated admission metrics

This commit is contained in:
Marius Ciepluch
2026-11-01 10:37:37 +01:00
parent 44516cf29f
commit c8ce1c9e63
12 changed files with 1188 additions and 20 deletions
+1 -1
View File
@@ -272,7 +272,7 @@
- Done when the reproduced vulnerable/fixed payload swap and every release-field mutation fail before any admission metric is returned. - Done when the reproduced vulnerable/fixed payload swap and every release-field mutation fail before any admission metric is returned.
- _Requirements: 12.2, 12.5, 12.6, 13.4, 15.1_ - _Requirements: 12.2, 12.5, 12.6, 13.4, 15.1_
- _Boundary: Admission Case Release_ - _Boundary: Admission Case Release_
- [ ] 8.8 Derive scoped admission metrics from canonical case evidence - [x] 8.8 Derive scoped admission metrics from canonical case evidence
- Re-run shared evidence validation, instrument, budget, verdict, and proof validation for every case. - Re-run shared evidence validation, instrument, budget, verdict, and proof validation for every case.
- Consume only cases that exactly match the authenticated post-spend release; use its roles and scoped pairs while reconciling aggregate counts to the reviewed census. - Consume only cases that exactly match the authenticated post-spend release; use its roles and scoped pairs while reconciling aggregate counts to the reviewed census.
- Score quality only over pre-registered family/ecosystem scopes while retaining whole-population coverage. - Score quality only over pre-registered family/ecosystem scopes while retaining whole-population coverage.
@@ -0,0 +1,65 @@
"""Canonical admission funnel derivation from validated case evidence."""
from __future__ import annotations
from collections import Counter
from openultrasast.core.admission_case import AdmissionCaseResult
from openultrasast.core.coverage import CoverageFunnel, ReasonCount, RelationStatusCount
from openultrasast.core.evidence import EvidenceRecord, NeutralOutcome
from openultrasast.core.semantics import RelationStatus, SemanticRelation
def _relation_reason(relation: SemanticRelation) -> str:
return "|".join(relation.reasons)
def derive_case_funnel(
case: AdmissionCaseResult,
records: tuple[EvidenceRecord, ...],
finding_count: int,
) -> CoverageFunnel:
transitions = tuple(item for record in records for item in record.packet.transitions)
observations = tuple(item for record in records for item in record.packet.observations)
statuses = Counter(item.status for item in transitions)
reasons = Counter(_relation_reason(item) for item in transitions if item.status is not RelationStatus.COMPLETE)
return CoverageFunnel(
inputs_read=len(case.input_receipts),
questions_formed=len(records),
sites_considered=len(transitions) + len(observations),
sites_recognized=sum(item.status is RelationStatus.COMPLETE for item in transitions)
+ sum(item.status is RelationStatus.COMPLETE for item in observations),
transitions_materialized=len(transitions),
security_objects_constructed=len({record.packet.security_object_id for record in records}),
constraints_evaluated=len(records),
determinate_outcomes=sum(record.decision.outcome is not NeutralOutcome.INDETERMINATE for record in records),
findings_emitted=finding_count,
relation_status_counts=tuple(RelationStatusCount(status, statuses[status]) for status in RelationStatus),
unresolved_by_reason=tuple(ReasonCount(reason, count) for reason, count in sorted(reasons.items())),
)
def aggregate_funnels(funnels: tuple[CoverageFunnel, ...]) -> CoverageFunnel:
statuses = tuple(
RelationStatusCount(
status,
sum(next(item.count for item in funnel.relation_status_counts if item.status is status) for funnel in funnels),
)
for status in RelationStatus
)
reasons: Counter[str] = Counter()
for funnel in funnels:
reasons.update({item.reason: item.count for item in funnel.unresolved_by_reason})
return CoverageFunnel(
inputs_read=sum(item.inputs_read for item in funnels),
questions_formed=sum(item.questions_formed for item in funnels),
sites_considered=sum(item.sites_considered for item in funnels),
sites_recognized=sum(item.sites_recognized for item in funnels),
transitions_materialized=sum(item.transitions_materialized for item in funnels),
security_objects_constructed=sum(item.security_objects_constructed for item in funnels),
constraints_evaluated=sum(item.constraints_evaluated for item in funnels),
determinate_outcomes=sum(item.determinate_outcomes for item in funnels),
findings_emitted=sum(item.findings_emitted for item in funnels),
relation_status_counts=statuses,
unresolved_by_reason=tuple(ReasonCount(reason, count) for reason, count in sorted(reasons.items())),
)
+3 -3
View File
@@ -193,9 +193,9 @@ def _validate_scopes_and_sets(manifest: AdmissionManifestV2, denominator: Denomi
if baseline.family_contract_digests != tuple(sorted(entries)) or baseline.family_contract_set_digest != catalog.catalog_digest: if baseline.family_contract_digests != tuple(sorted(entries)) or baseline.family_contract_set_digest != catalog.catalog_digest:
raise AdmissionContextError("baseline family catalog set does not match canonical catalog objects") raise AdmissionContextError("baseline family catalog set does not match canonical catalog objects")
census_scopes = tuple(row.scope for row in manifest.census.rows) census_scopes = tuple(row.scope for row in manifest.census.rows)
if baseline.claimed_scopes != census_scopes: if any(scope not in census_scopes for scope in baseline.claimed_scopes):
raise AdmissionContextError("claimed scopes do not match the reviewed census") raise AdmissionContextError("every claimed scope must have one reviewed census row")
for scope in baseline.claimed_scopes: for scope in census_scopes:
_contract_for_scope(catalog, scope) _contract_for_scope(catalog, scope)
expected_fact_set = derive_fact_set_digest(evidence.fact_packs) expected_fact_set = derive_fact_set_digest(evidence.fact_packs)
if baseline.fact_schema_digest != derive_fact_schema_digest() or baseline.fact_set_digest != expected_fact_set: if baseline.fact_schema_digest != derive_fact_schema_digest() or baseline.fact_set_digest != expected_fact_set:
+15 -11
View File
@@ -88,7 +88,7 @@ def _validate_row(row: object) -> ScopeCensus:
return row return row
def _capacities(census: PopulationCensus) -> tuple[tuple[ScopeKey, ...], _Capacities]: def _capacities(census: PopulationCensus, selected_scopes: tuple[ScopeKey, ...]) -> _Capacities:
if type(census) is not PopulationCensus or not census.rows: if type(census) is not PopulationCensus or not census.rows:
raise AdmissionFeasibilityError("census must be a nonempty PopulationCensus") raise AdmissionFeasibilityError("census must be a nonempty PopulationCensus")
if census.case_labels_exposed: if census.case_labels_exposed:
@@ -99,13 +99,16 @@ def _capacities(census: PopulationCensus) -> tuple[tuple[ScopeKey, ...], _Capaci
scopes = tuple(row.scope for row in rows) scopes = tuple(row.scope for row in rows)
if scopes != tuple(sorted(scopes)) or len(scopes) != len(set(scopes)): if scopes != tuple(sorted(scopes)) or len(scopes) != len(set(scopes)):
raise AdmissionFeasibilityError("census scopes must be unique and canonically ordered") raise AdmissionFeasibilityError("census scopes must be unique and canonically ordered")
return scopes, _Capacities( if any(scope not in scopes for scope in selected_scopes):
sum(row.pair_count for row in rows), raise AdmissionFeasibilityError("every claimed scope must have one reviewed census row")
sum(row.vulnerable_side_count for row in rows), selected = tuple(row for row in rows if row.scope in selected_scopes)
sum(row.fixed_side_count for row in rows), return _Capacities(
sum(row.ordinary_case_count for row in rows), sum(row.pair_count for row in selected),
sum(row.semantic_site_capacity for row in rows), sum(row.vulnerable_side_count for row in selected),
sum(row.model_call_capacity for row in rows), sum(row.fixed_side_count for row in selected),
sum(row.ordinary_case_count for row in selected),
sum(row.semantic_site_capacity for row in selected),
sum(row.model_call_capacity for row in selected),
) )
@@ -122,9 +125,10 @@ def _validated_inputs(
raise AdmissionFeasibilityError(str(exc)) from exc raise AdmissionFeasibilityError(str(exc)) from exc
if policy.policy_digest != manifest.denominator_policy_digest: if policy.policy_digest != manifest.denominator_policy_digest:
raise AdmissionFeasibilityError("denominator policy does not match the reviewed manifest") raise AdmissionFeasibilityError("denominator policy does not match the reviewed manifest")
scopes, capacities = _capacities(manifest.census) claimed_scopes = manifest.identity.claimed_scopes
if any(rule.scopes != scopes for rule in policy.rules): if any(rule.scopes != claimed_scopes for rule in policy.rules):
raise AdmissionFeasibilityError("denominator policy scope does not match the census") raise AdmissionFeasibilityError("denominator policy scope does not match the manifest claimed scopes")
capacities = _capacities(manifest.census, claimed_scopes)
return capacities, manifest.threshold_set_digest return capacities, manifest.threshold_set_digest
+397
View File
@@ -0,0 +1,397 @@
"""Canonical, scoped admission metric derivation from revalidated cases."""
from __future__ import annotations
from collections import Counter
from collections.abc import Sequence
from dataclasses import dataclass
from openultrasast.core import canonical
from openultrasast.core._admission_funnel import aggregate_funnels, derive_case_funnel
from openultrasast.core.admission_case import (
AdmissionCaseRelease,
AdmissionCaseResult,
AdmissionCaseRole,
ReleasedCaseBinding,
derive_admission_case_result_digest,
derive_case_coverage_digest,
validate_admission_case_release,
validate_admission_case_shape,
)
from openultrasast.core.admission_case import (
create_admission_case_result as create_admission_case_result,
)
from openultrasast.core.admission_context import AdmissionValidationContext
from openultrasast.core.admission_review import AdmissionManifestV2, ScopeCensus, validate_reviewed_admission_payloads
from openultrasast.core.admission_spend import AdmissionPreparation
from openultrasast.core.budget import check_budget
from openultrasast.core.coverage import CoverageFunnel, ReasonCount, validate_instrument
from openultrasast.core.custody import PopulationCustodyPort, PopulationUseReceipt
from openultrasast.core.evidence import EvidenceBasis, EvidenceRecord, NeutralOutcome, derive_packet_digest
from openultrasast.core.family import FamilyContract
from openultrasast.core.family_catalog import resolve_contract
from openultrasast.core.measurement import DenominatorPolicy, MetricId, MetricResult, ScopeKey, evaluate_metric, validate_denominator_policy
from openultrasast.core.measurement_record import BasisCount, OutcomeCount
from openultrasast.core.model import ModelStatus
from openultrasast.core.proof import ProofAttachment, attach_differential_proof
from openultrasast.core.readiness import ReadinessResult
from openultrasast.core.validation import validate_evidence_input
from openultrasast.core.verdict_v2 import DefectClaim, derive_verdict_outputs
class AdmissionMetricsError(ValueError):
"""Admission cases cannot produce trustworthy metrics."""
@dataclass(frozen=True)
class AdmissionMetricEvidence:
family_decision_vocabulary: tuple[str, ...]
family_decision_counts: tuple[ReasonCount, ...]
neutral_outcome_counts: tuple[OutcomeCount, ...]
evidence_basis_counts: tuple[BasisCount, ...]
metrics: tuple[MetricResult, ...]
stage_funnel: CoverageFunnel
model_outcome_counts: tuple[ReasonCount, ...]
model_failures: tuple[str, ...]
runtime_ms: int
whole_population_funnel: CoverageFunnel
whole_population_runtime_ms: int
in_scope_case_count: int
whole_population_case_count: int
case_result_digests: tuple[str, ...]
@dataclass(frozen=True)
class _ValidatedCase:
supplied: AdmissionCaseResult
binding: ReleasedCaseBinding
records: tuple[EvidenceRecord, ...]
claims: tuple[DefectClaim, ...]
funnel: CoverageFunnel
@dataclass(frozen=True)
class _DerivedCounts:
vocabulary: tuple[str, ...]
decisions: tuple[ReasonCount, ...]
outcomes: tuple[OutcomeCount, ...]
bases: tuple[BasisCount, ...]
metrics: tuple[MetricResult, ...]
model_outcomes: tuple[ReasonCount, ...]
model_failures: tuple[str, ...]
def _case_shape(case: AdmissionCaseResult) -> None:
try:
validate_admission_case_shape(case)
except (TypeError, ValueError) as exc:
raise AdmissionMetricsError(str(exc)) from exc
if not case.evidence_inputs:
raise AdmissionMetricsError("case requires evidence inputs")
def _contract(case: AdmissionCaseResult, records: tuple[EvidenceRecord, ...], context: AdmissionValidationContext) -> FamilyContract:
digests = {item.packet.family_contract_digest for item in records}
if len(digests) != 1:
raise AdmissionMetricsError("case records must use one exact family contract")
contract = resolve_contract(context.evidence.family_catalog, case.scope.family_id, next(iter(digests)), case.scope.ecosystem)
for record in records:
packet = record.packet
if (packet.family_id, packet.ecosystem) != (case.scope.family_id, case.scope.ecosystem):
raise AdmissionMetricsError("case scope does not match validated evidence")
return contract
def _apply_proofs(
claims: tuple[DefectClaim, ...],
attachments: tuple[ProofAttachment, ...],
context: AdmissionValidationContext,
resolver: canonical.ContentResolver,
) -> tuple[DefectClaim, ...]:
if not attachments:
return claims
if context.proof_trust_policy is None:
raise AdmissionMetricsError("proof attachments require the context-bound trust policy")
keys = {
(item.record.packet.family_id, derive_packet_digest(item.record.packet), item.location): index for index, item in enumerate(claims)
}
if len(keys) != len(claims):
raise AdmissionMetricsError("proof target is ambiguous")
result = list(claims)
consumed: set[int] = set()
for attachment in attachments:
key = (attachment.family_id, attachment.packet_digest, attachment.location)
if key not in keys or keys[key] in consumed:
raise AdmissionMetricsError("proof attachment does not target one exact claim")
index = keys[key]
result[index] = attach_differential_proof(result[index], attachment, context.proof_trust_policy, resolver)
consumed.add(index)
return tuple(result)
def _validated_records(
case: AdmissionCaseResult, context: AdmissionValidationContext, custody: PopulationCustodyPort, resolver: canonical.ContentResolver
) -> tuple[EvidenceRecord, ...]:
_case_shape(case)
try:
validate_instrument(case.input_manifest, case.input_receipts)
except (TypeError, ValueError) as exc:
raise AdmissionMetricsError(f"instrument validation failed: {exc}") from exc
try:
check_budget(case.complexity, case.total_reply_ms, case.budget, resolver)
except (TypeError, ValueError) as exc:
raise AdmissionMetricsError(f"budget validation failed: {exc}") from exc
try:
records = tuple(validate_evidence_input(item, context.evidence, custody, resolver) for item in case.evidence_inputs)
except (AttributeError, LookupError, TypeError, ValueError) as exc:
raise AdmissionMetricsError(f"evidence validation failed: {exc}") from exc
if len({item.record_id for item in records}) != len(records):
raise AdmissionMetricsError("evidence inputs contain duplicate record identities")
scopes = {record.packet.scope for record in records}
if len(scopes) != 1:
raise AdmissionMetricsError("case records must share one exact analyzed scope")
analyzed_scope = next(iter(scopes))
expected_complexity = (
analyzed_scope.change_digest,
len(analyzed_scope.regions),
sum(len(record.packet.transitions) for record in records),
)
actual_complexity = (case.complexity.change_id, case.complexity.changed_regions, case.complexity.transition_count)
if actual_complexity != expected_complexity:
raise AdmissionMetricsError("case complexity does not match revalidated evidence")
return records
def _validated_verdict(
case: AdmissionCaseResult,
records: tuple[EvidenceRecord, ...],
context: AdmissionValidationContext,
resolver: canonical.ContentResolver,
) -> tuple[tuple[DefectClaim, ...], CoverageFunnel]:
contract = _contract(case, records, context)
base_outputs = derive_verdict_outputs(records, contract, canonical.content_digest("admission-coverage-pending"))
funnel = derive_case_funnel(case, records, len(base_outputs.defects))
coverage_digest = derive_case_coverage_digest(case.case_id_digest, case.scope, funnel)
outputs = derive_verdict_outputs(records, contract, coverage_digest)
claims = _apply_proofs(outputs.defects, case.proof_attachments, context, resolver)
if (claims, outputs.satisfactions, outputs.silence) != (case.defect_claims, case.satisfactions, case.silence):
raise AdmissionMetricsError("supplied verdict differs from the revalidated canonical verdict")
funnel = derive_case_funnel(case, records, len(claims))
if case.coverage != funnel:
raise AdmissionMetricsError("coverage aggregate does not match revalidated case evidence")
if case.result_digest != derive_admission_case_result_digest(case):
raise AdmissionMetricsError("case result digest cannot be re-derived")
return claims, funnel
def _validate_case(
case: AdmissionCaseResult,
binding: ReleasedCaseBinding,
context: AdmissionValidationContext,
custody: PopulationCustodyPort,
resolver: canonical.ContentResolver,
) -> _ValidatedCase:
records = _validated_records(case, context, custody, resolver)
claims, funnel = _validated_verdict(case, records, context, resolver)
return _ValidatedCase(case, binding, records, claims, funnel)
def _validate_population(cases: tuple[_ValidatedCase, ...], rows: tuple[ScopeCensus, ...]) -> None:
by_scope = {row.scope: row for row in rows}
missing_scopes = tuple(sorted({item.binding.scope for item in cases if item.binding.scope not in by_scope}))
if missing_scopes:
raise AdmissionMetricsError("case scope has no reviewed census row")
if len(cases) != sum(row.total_cases for row in rows):
raise AdmissionMetricsError("case population count does not reconcile with the reviewed census")
for row in rows:
selected = tuple(item for item in cases if item.binding.scope == row.scope)
roles = Counter(item.binding.role for item in selected)
model_calls = sum(
value.model_result is not None for item in selected for value in item.supplied.evidence_inputs
)
actual = (
len(selected),
roles[AdmissionCaseRole.VULNERABLE],
roles[AdmissionCaseRole.FIXED],
roles[AdmissionCaseRole.ORDINARY],
sum(item.funnel.sites_considered for item in selected),
model_calls,
)
expected = (
row.total_cases,
row.vulnerable_side_count,
row.fixed_side_count,
row.ordinary_case_count,
row.semantic_site_capacity,
row.model_call_capacity,
)
if actual != expected:
raise AdmissionMetricsError("case counts do not reconcile with the reviewed census")
paired: dict[str, list[AdmissionCaseRole]] = {}
for item in selected:
binding = item.binding
if binding.role is AdmissionCaseRole.ORDINARY and binding.pair_id_digest is not None:
raise AdmissionMetricsError("ordinary case cannot carry a pair identity")
if binding.pair_id_digest is not None:
paired.setdefault(binding.pair_id_digest, []).append(binding.role)
valid = {AdmissionCaseRole.VULNERABLE, AdmissionCaseRole.FIXED}
if len(paired) != row.pair_count or any(len(values) != 2 or set(values) != valid for values in paired.values()):
raise AdmissionMetricsError("case pairs do not reconcile with the reviewed census")
def _vocabulary(context: AdmissionValidationContext, scopes: tuple[ScopeKey, ...]) -> tuple[str, ...]:
values: list[str] = []
for entry in context.evidence.family_catalog.entries:
if any(scope.family_id == entry.contract.family_id and scope.ecosystem in entry.contract.ecosystems for scope in scopes):
values.extend(rule.family_code for rule in entry.contract.decisions)
if not values or len(values) != len(set(values)):
raise AdmissionMetricsError("exact catalog decision vocabulary is empty or ambiguous")
return tuple(values)
def derive_scoped_pair_key(scope: ScopeKey, pair_id_digest: str) -> tuple[str, str, str]:
return scope.family_id, scope.ecosystem, pair_id_digest
def _metric_counts(cases: tuple[_ValidatedCase, ...], funnel: CoverageFunnel) -> dict[MetricId, tuple[int, int]]:
by_pair: dict[tuple[str, str, str], dict[AdmissionCaseRole, _ValidatedCase]] = {}
for item in cases:
if item.binding.pair_id_digest is not None:
key = derive_scoped_pair_key(item.binding.scope, item.binding.pair_id_digest)
by_pair.setdefault(key, {})[item.binding.role] = item
correct = sum(bool(pair[AdmissionCaseRole.VULNERABLE].claims) and not pair[AdmissionCaseRole.FIXED].claims for pair in by_pair.values())
vulnerable = tuple(item for item in cases if item.binding.role is AdmissionCaseRole.VULNERABLE)
fixed = tuple(item for item in cases if item.binding.role is AdmissionCaseRole.FIXED)
ordinary = tuple(item for item in cases if item.binding.role is AdmissionCaseRole.ORDINARY)
defects = tuple(item for item in cases if item.claims)
model_results = tuple(value.model_result for item in cases for value in item.supplied.evidence_inputs if value.model_result is not None)
return {
MetricId.PAIRED_CORRECTNESS: (correct, len(by_pair)),
MetricId.VULNERABLE_RECALL: (sum(bool(item.claims) for item in vulnerable), len(vulnerable)),
MetricId.FIXED_FALSE_ALARM_RATE: (sum(bool(item.claims) for item in fixed), len(fixed)),
MetricId.ORDINARY_FALSE_ALARMS_PER_100: (sum(bool(item.claims) for item in ordinary), len(ordinary)),
MetricId.PRECISION: (sum(bool(item.claims) for item in vulnerable), len(defects)),
MetricId.SEMANTIC_STAGE_COVERAGE: (funnel.sites_recognized, funnel.sites_considered),
MetricId.MODEL_SUCCESS_RATE: (sum(item.status is ModelStatus.SUCCEEDED for item in model_results), len(model_results)),
}
def _validated_cases(
manifest: AdmissionManifestV2,
release: AdmissionCaseRelease,
cases: tuple[AdmissionCaseResult, ...],
context: AdmissionValidationContext,
custody: PopulationCustodyPort,
resolver: canonical.ContentResolver,
) -> tuple[_ValidatedCase, ...]:
selected = cases
if not selected or len({item.case_id_digest for item in selected}) != len(selected):
raise AdmissionMetricsError("case population must be nonempty with unique identities")
bindings = {item.case_id_digest: item for item in release.bindings}
validated = tuple(_validate_case(item, bindings[item.case_id_digest], context, custody, resolver) for item in selected)
input_ids = tuple(value.input_digest for item in validated for value in item.supplied.evidence_inputs)
record_ids = tuple(record.record_id for item in validated for record in item.records)
if len(input_ids) != len(set(input_ids)) or len(record_ids) != len(set(record_ids)):
raise AdmissionMetricsError("case population repeats canonical evidence identities")
_validate_population(validated, manifest.census.rows)
return validated
def _derive_counts(
cases: tuple[_ValidatedCase, ...],
funnel: CoverageFunnel,
denominator: DenominatorPolicy,
context: AdmissionValidationContext,
scopes: tuple[ScopeKey, ...],
) -> _DerivedCounts:
counts = _metric_counts(cases, funnel)
metrics = tuple(
evaluate_metric(rule, numerator=counts[rule.metric_id][0], denominator=counts[rule.metric_id][1]) for rule in denominator.rules
)
records = tuple(record for item in cases for record in item.records)
vocabulary = _vocabulary(context, scopes)
by_decision = Counter(record.decision.family_code for record in records)
decisions = tuple(ReasonCount(name, by_decision[name]) for name in vocabulary)
by_outcome = Counter(record.decision.outcome for record in records)
outcomes = tuple(OutcomeCount(outcome, by_outcome[outcome]) for outcome in NeutralOutcome)
by_basis = Counter(claim.basis for item in cases for claim in item.claims)
bases = tuple(BasisCount(basis, by_basis[basis]) for basis in EvidenceBasis)
model_results = tuple(value.model_result for item in cases for value in item.supplied.evidence_inputs if value.model_result is not None)
model_counts = Counter("success" if item.status is ModelStatus.SUCCEEDED else "failure" for item in model_results)
model_outcomes = tuple(ReasonCount(name, model_counts[name]) for name in ("failure", "success") if model_counts[name])
failures = tuple(sorted({item.failure_reason for item in model_results if item.failure_reason is not None}))
return _DerivedCounts(vocabulary, decisions, outcomes, bases, metrics, model_outcomes, failures)
def _derive_validated_metrics(
manifest: AdmissionManifestV2,
denominator: DenominatorPolicy,
release: AdmissionCaseRelease,
cases: tuple[AdmissionCaseResult, ...],
context: AdmissionValidationContext,
custody: PopulationCustodyPort,
resolver: canonical.ContentResolver,
) -> AdmissionMetricEvidence:
validate_reviewed_admission_payloads(manifest.census, manifest, context.admission_review_policy, resolver)
validate_denominator_policy(denominator)
if manifest.validation_context_digest != context.context_digest:
raise AdmissionMetricsError("validation context does not match the reviewed manifest")
if denominator.policy_digest != manifest.denominator_policy_digest:
raise AdmissionMetricsError("denominator does not match the reviewed manifest")
validated = _validated_cases(manifest, release, cases, context, custody, resolver)
scopes = manifest.identity.claimed_scopes
in_scope = tuple(item for item in validated if item.binding.scope in scopes)
funnel = aggregate_funnels(tuple(item.funnel for item in in_scope))
whole_funnel = aggregate_funnels(tuple(item.funnel for item in validated))
counts = _derive_counts(in_scope, funnel, denominator, context, scopes)
return AdmissionMetricEvidence(
counts.vocabulary,
counts.decisions,
counts.outcomes,
counts.bases,
counts.metrics,
funnel,
counts.model_outcomes,
counts.model_failures,
sum(item.supplied.total_reply_ms for item in in_scope),
whole_funnel,
sum(item.supplied.total_reply_ms for item in validated),
len(in_scope),
len(validated),
tuple(sorted(item.supplied.result_digest for item in validated)),
)
def derive_admission_metrics(
manifest: AdmissionManifestV2,
denominator: DenominatorPolicy,
readiness: Sequence[ReadinessResult],
preparation: AdmissionPreparation,
label_release_receipt: PopulationUseReceipt,
case_release: AdmissionCaseRelease,
cases: Sequence[AdmissionCaseResult],
context: AdmissionValidationContext,
custody: PopulationCustodyPort,
resolver: canonical.ContentResolver,
) -> AdmissionMetricEvidence:
"""Revalidate immutable case evidence and derive only pre-registered quality metrics."""
try:
frozen_cases = tuple(cases)
frozen_readiness = tuple(readiness)
validate_admission_case_release(
manifest,
denominator,
frozen_readiness,
context,
preparation,
label_release_receipt,
case_release,
frozen_cases,
custody,
resolver,
)
return _derive_validated_metrics(manifest, denominator, case_release, frozen_cases, context, custody, resolver)
except AdmissionMetricsError:
raise
except (AttributeError, LookupError, TypeError, ValueError) as exc:
raise AdmissionMetricsError(f"admission metric derivation failed before metric production: {exc}") from exc
+2 -2
View File
@@ -217,8 +217,6 @@ def _validate_relation_status_count(value: RelationStatusCount) -> None:
def _validate_reason_count(value: ReasonCount) -> None: def _validate_reason_count(value: ReasonCount) -> None:
_require_text(value.reason, "reason") _require_text(value.reason, "reason")
_require_count(value.count, "reason count") _require_count(value.count, "reason count")
if value.count == 0:
raise ValueError("reason count must be positive")
def _validate_stage_expectation(value: StageExpectation) -> None: def _validate_stage_expectation(value: StageExpectation) -> None:
@@ -268,6 +266,8 @@ def _validate_reason_counts(funnel: CoverageFunnel) -> None:
raise ValueError("unresolved_by_reason must contain ReasonCount values") raise ValueError("unresolved_by_reason must contain ReasonCount values")
for item in funnel.unresolved_by_reason: for item in funnel.unresolved_by_reason:
_validate_reason_count(item) _validate_reason_count(item)
if item.count == 0:
raise ValueError("unresolved reason count must be positive")
reasons = tuple(item.reason for item in funnel.unresolved_by_reason) reasons = tuple(item.reason for item in funnel.unresolved_by_reason)
if len(reasons) != len(set(reasons)): if len(reasons) != len(set(reasons)):
raise ValueError("duplicate unresolved reason") raise ValueError("duplicate unresolved reason")
+2 -2
View File
@@ -267,8 +267,8 @@ def _validate_record_totals(value: MeasurementRecordV2, decisions: tuple[ReasonC
raise MeasurementRecordError("family decision counts must equal the question count") raise MeasurementRecordError("family decision counts must equal the question count")
if sum(item.count for item in value.neutral_outcome_counts) != question_count: if sum(item.count for item in value.neutral_outcome_counts) != question_count:
raise MeasurementRecordError("neutral outcome counts must equal the question count") raise MeasurementRecordError("neutral outcome counts must equal the question count")
if sum(item.count for item in value.evidence_basis_counts) != question_count: if sum(item.count for item in value.evidence_basis_counts) != value.stage_funnel.findings_emitted:
raise MeasurementRecordError("evidence basis counts must equal the question count") raise MeasurementRecordError("evidence basis counts must equal emitted findings")
determinate = sum(item.count for item in value.neutral_outcome_counts if item.outcome is not NeutralOutcome.INDETERMINATE) determinate = sum(item.count for item in value.neutral_outcome_counts if item.outcome is not NeutralOutcome.INDETERMINATE)
if determinate != value.stage_funnel.determinate_outcomes: if determinate != value.stage_funnel.determinate_outcomes:
raise MeasurementRecordError("determinate outcome counts must equal the funnel determinate count") raise MeasurementRecordError("determinate outcome counts must equal the funnel determinate count")
+20
View File
@@ -10,6 +10,7 @@ from detection_core_fixtures import InMemoryContentResolver, stored_artifact
from openultrasast.core import canonical from openultrasast.core import canonical
from openultrasast.core.admission_feasibility import ( from openultrasast.core.admission_feasibility import (
AdmissionFeasibilityError, AdmissionFeasibilityError,
FeasibleCountAssignment,
ThresholdFeasibility, ThresholdFeasibility,
evaluate_threshold_feasibility, evaluate_threshold_feasibility,
) )
@@ -80,6 +81,17 @@ def _policy(scope: ScopeKey | None = None) -> DenominatorPolicy:
return DenominatorPolicy(rules, IntervalMethod.WILSON_95, "") return DenominatorPolicy(rules, IntervalMethod.WILSON_95, "")
def _with_extra_census_row(census: PopulationCensus) -> PopulationCensus:
extra = ScopeCensus(ScopeKey("out-of-scope", "javascript"), 300, 100, 100, 100, 100, 500, 200)
rows = tuple(sorted((*census.rows, extra), key=lambda item: item.scope))
draft = replace(census, rows=rows, census_payload_digest="0" * 64, census_digest="0" * 64)
payload = derive_census_payload_digest(draft)
old = census.review
review = _attestation(payload, old.producer_identity, old.reviewer_identity, old.conflict_disclosure)
reviewed = replace(draft, census_payload_digest=payload, review=review)
return replace(reviewed, census_digest=derive_census_digest(reviewed))
def _thresholds(*values: AdmissionThreshold) -> tuple[AdmissionThreshold, ...]: def _thresholds(*values: AdmissionThreshold) -> tuple[AdmissionThreshold, ...]:
return tuple(sorted(values, key=lambda item: (item.metric_id.value, item.statistic.value))) return tuple(sorted(values, key=lambda item: (item.metric_id.value, item.statistic.value)))
@@ -219,6 +231,14 @@ def test_enumerates_capacity_witnesses_for_every_metric_family() -> None:
assert len(result.feasibility_digest) == 64 assert len(result.feasibility_digest) == 64
def test_extra_census_scope_does_not_change_claimed_scope_feasibility() -> None:
threshold = _threshold(MetricId.PAIRED_CORRECTNESS, Statistic.COUNT, Comparator.AT_LEAST, 12.0)
result = _evaluate(_with_extra_census_row(_census()), _policy(), (threshold,))
assert result.witnesses == (FeasibleCountAssignment(MetricId.PAIRED_CORRECTNESS, 12, 12),)
def test_one_assignment_must_jointly_satisfy_grouped_statistics() -> None: def test_one_assignment_must_jointly_satisfy_grouped_statistics() -> None:
thresholds = _thresholds( thresholds = _thresholds(
_threshold(MetricId.PAIRED_CORRECTNESS, Statistic.COUNT, Comparator.AT_LEAST, 10.0), _threshold(MetricId.PAIRED_CORRECTNESS, Statistic.COUNT, Comparator.AT_LEAST, 10.0),
+62
View File
@@ -0,0 +1,62 @@
"""Admission funnel fields are derived from canonical evidence, never runner counts."""
from __future__ import annotations
from dataclasses import replace
import pytest
from test_core_admission_metrics import _derive, _release_for, _run, _setup
from openultrasast.core.admission_metrics import AdmissionMetricsError, derive_admission_case_result_digest
from openultrasast.core.coverage import CoverageFunnel, ReasonCount, RelationStatusCount
from openultrasast.core.semantics import RelationStatus
def _mutate(value: CoverageFunnel, field: str) -> CoverageFunnel:
if field == "inputs_read":
return replace(value, inputs_read=2)
if field == "questions_formed":
return replace(value, questions_formed=2)
if field == "sites_considered":
return replace(value, sites_considered=3)
if field == "sites_recognized":
return replace(value, sites_recognized=1)
if field == "transitions_materialized":
counts = tuple(RelationStatusCount(status, 2 if status is RelationStatus.COMPLETE else 0) for status in RelationStatus)
return replace(value, transitions_materialized=2, relation_status_counts=counts)
if field == "security_objects_constructed":
return replace(value, security_objects_constructed=2)
if field == "constraints_evaluated":
return replace(value, constraints_evaluated=2)
if field == "determinate_outcomes":
return replace(value, determinate_outcomes=0)
if field == "findings_emitted":
return replace(value, findings_emitted=1)
counts = tuple(RelationStatusCount(status, int(status is RelationStatus.INCOMPLETE)) for status in RelationStatus)
return replace(value, relation_status_counts=counts, unresolved_by_reason=(ReasonCount("fabricated_incomplete_relation", 1),))
@pytest.mark.parametrize(
"field",
(
"inputs_read",
"questions_formed",
"sites_considered",
"sites_recognized",
"transitions_materialized",
"security_objects_constructed",
"constraints_evaluated",
"determinate_outcomes",
"findings_emitted",
"relation_accounting",
),
)
def test_every_case_funnel_field_is_rederived_from_canonical_evidence(field: str) -> None:
run = _run(_setup())
cases = run.setup.cases
changed = replace(cases[1], coverage=_mutate(cases[1].coverage, field))
changed = replace(changed, result_digest=derive_admission_case_result_digest(changed))
changed_cases = (cases[0], changed, cases[2])
with pytest.raises(AdmissionMetricsError, match="coverage aggregate"):
_derive(run, cases=changed_cases, release=_release_for(run, changed_cases))
+596
View File
@@ -0,0 +1,596 @@
"""Canonical admission cases derive scoped metrics without runner aggregates."""
from __future__ import annotations
from collections.abc import Iterator, Sequence
from dataclasses import replace
from typing import NamedTuple, TypeVar
import pytest
from detection_core_fixtures import InMemoryContentResolver
from test_core_admission_context import AdmissionCase, MultiPopulationCustody, _attestation, _resign_manifest
from test_core_admission_release import _binding, _resign_release
from test_core_admission_spend import _feasible_case, _prepare, _spend
from test_core_consumer_v2 import _proof, _request
from test_core_model import _record as model_record
from test_core_readiness import _chain
from test_core_validation_v2 import _named_inputs, _with_fact_set
from openultrasast.core import admission_context, canonical, provenance, validation
from openultrasast.core.admission_case import ADMISSION_CASE_RELEASE_SCHEMA, AdmissionCaseRelease
from openultrasast.core.admission_context import AdmissionValidationContext
from openultrasast.core.admission_metrics import (
AdmissionCaseResult,
AdmissionCaseRole,
AdmissionMetricsError,
create_admission_case_result,
derive_admission_case_result_digest,
derive_admission_metrics,
derive_scoped_pair_key,
)
from openultrasast.core.admission_review import (
AdmissionManifestV2,
PopulationCensus,
ScopeCensus,
derive_census_digest,
derive_census_payload_digest,
)
from openultrasast.core.admission_spend import AdmissionPreparation
from openultrasast.core.coverage import (
CoverageFunnel,
InputManifest,
InputReadReceipt,
RelationStatusCount,
derive_input_manifest_digest,
)
from openultrasast.core.custody import PopulationUseReceipt
from openultrasast.core.evidence import EvidenceBasis, EvidenceRecord, NeutralOutcome, derive_packet_digest
from openultrasast.core.family import FamilyContract, family_contract_digest
from openultrasast.core.family_catalog import FamilyCatalog, FamilyCatalogEntry, catalog_entry_key, derive_catalog_digest
from openultrasast.core.measurement import DenominatorPolicy, IntervalMethod, MetricId, ScopeKey, standard_metric_rules
from openultrasast.core.readiness import ReadinessResult
from openultrasast.core.semantics import RelationStatus
from openultrasast.core.verdict_v2 import derive_verdict_outputs
class _InputManifestSubclass(InputManifest):
pass
_T = TypeVar("_T")
class _TwoPassSequence(Sequence[_T]):
def __init__(self, first: tuple[_T, ...], second: tuple[_T, ...]) -> None:
self.first = first
self.second = second
self.traversals = 0
def __iter__(self) -> Iterator[_T]:
self.traversals += 1
return iter(self.first if self.traversals == 1 else self.second)
def __len__(self) -> int:
return len(self.first)
def __getitem__(self, index: int | slice) -> _T | tuple[_T, ...]:
return self.first[index]
class _MetricSetup(NamedTuple):
manifest: AdmissionManifestV2
denominator: DenominatorPolicy
cases: tuple[AdmissionCaseResult, ...]
context: AdmissionValidationContext
custody: MultiPopulationCustody
resolver: InMemoryContentResolver
readiness: tuple[ReadinessResult, ...]
class _MetricRun(NamedTuple):
setup: _MetricSetup
preparation: AdmissionPreparation
receipt: PopulationUseReceipt
release: AdmissionCaseRelease
def _run(setup: _MetricSetup) -> _MetricRun:
admission = AdmissionCase(
setup.manifest,
setup.denominator,
setup.readiness,
setup.context,
setup.custody,
setup.resolver,
)
preparation = _prepare(admission)
receipt = _spend(admission, preparation)
bindings = tuple(sorted((_binding(case) for case in setup.cases), key=lambda item: item.case_id_digest))
draft = AdmissionCaseRelease(
ADMISSION_CASE_RELEASE_SCHEMA,
setup.manifest.identity.population_digest,
setup.manifest.manifest_digest,
preparation.authorization.authorization_digest,
preparation.preparation_digest,
receipt.receipt_digest,
receipt.custodian_identity,
bindings,
"0" * 64,
setup.manifest.review,
"0" * 64,
)
release = _resign_release(draft, admission, receipt)
return _MetricRun(setup, preparation, receipt, release)
def _derive(
run: _MetricRun,
*,
cases: tuple[AdmissionCaseResult, ...] | None = None,
release: AdmissionCaseRelease | None = None,
) -> object:
setup = run.setup
selected_cases = setup.cases if cases is None else cases
selected_release = run.release if release is None else release
return derive_admission_metrics(
setup.manifest,
setup.denominator,
setup.readiness,
run.preparation,
run.receipt,
selected_release,
selected_cases,
setup.context,
setup.custody,
setup.resolver,
)
def _release_for(run: _MetricRun, cases: tuple[AdmissionCaseResult, ...]) -> AdmissionCaseRelease:
setup = run.setup
bindings = tuple(sorted((_binding(case) for case in cases), key=lambda item: item.case_id_digest))
draft = replace(run.release, bindings=bindings, release_payload_digest="0" * 64, release_digest="0" * 64)
admission = AdmissionCase(
setup.manifest,
setup.denominator,
setup.readiness,
setup.context,
setup.custody,
setup.resolver,
)
return _resign_release(draft, admission, run.receipt)
def _resolver(*values: InMemoryContentResolver) -> InMemoryContentResolver:
artifacts: dict[str, canonical.ResolvedContent] = {}
for value in values:
artifacts.update(value.artifacts)
return InMemoryContentResolver(artifacts)
def _rename_named_inputs(
named: provenance.NamedInputs,
resolver: InMemoryContentResolver,
prefix: str,
) -> tuple[provenance.NamedInputs, InMemoryContentResolver]:
artifacts: dict[str, canonical.ResolvedContent] = {}
items: list[provenance.NamedInput] = []
for item in named.inputs:
artifact_id = f"{prefix}-{item.content.artifact_id}"
reference = replace(item.content, artifact_id=artifact_id)
artifacts[artifact_id] = resolver.artifacts[item.content.artifact_id]
items.append(replace(item, content=reference))
values = tuple(items)
return provenance.NamedInputs(values, provenance.derive_named_inputs_digest(values)), InMemoryContentResolver(artifacts)
def _coverage(*, violation: bool) -> CoverageFunnel:
return CoverageFunnel(
1,
1,
2,
2,
1,
1,
1,
1,
int(violation),
(
RelationStatusCount(RelationStatus.COMPLETE, 1),
RelationStatusCount(RelationStatus.INCOMPLETE, 0),
RelationStatusCount(RelationStatus.UNRESOLVED, 0),
),
(),
)
def _instrument(name: str) -> tuple[InputManifest, tuple[InputReadReceipt, ...]]:
manifest = InputManifest((name,), derive_input_manifest_digest((name,)))
receipt = InputReadReceipt(name, f"/{name}.py", True, 10, canonical.content_digest(name))
return manifest, (receipt,)
def _case_result(
value: validation.EvidenceValidationInput,
role: AdmissionCaseRole,
pair: str | None,
resolver: InMemoryContentResolver,
contract: FamilyContract,
) -> tuple[AdmissionCaseResult, InMemoryContentResolver]:
request, combined = _request(value, resolver)
coverage = _coverage(violation=value.record.decision.outcome is NeutralOutcome.VIOLATION)
outputs = derive_verdict_outputs((value.record,), contract, canonical.content_digest("case-coverage"))
manifest, receipts = _instrument(role.value + (pair or ""))
result = create_admission_case_result(
case_id_digest=canonical.content_digest(role.value + (pair or "ordinary")),
scope=ScopeKey(value.record.packet.family_id, value.record.packet.ecosystem),
role=role,
pair_id_digest=pair,
evidence_inputs=(value,),
defect_claims=outputs.defects,
satisfactions=outputs.satisfactions,
silence=outputs.silence,
proof_attachments=(),
coverage=coverage,
input_manifest=manifest,
input_receipts=receipts,
complexity=request.complexity,
budget=request.budget,
total_reply_ms=10,
)
return result, combined
def _resign_census(census: PopulationCensus, row: ScopeCensus, population_digest: str) -> PopulationCensus:
return _resign_census_rows(census, (row,), population_digest)
def _resign_census_rows(
census: PopulationCensus,
rows: tuple[ScopeCensus, ...],
population_digest: str,
) -> PopulationCensus:
changed = replace(
census,
population_digest=population_digest,
rows=rows,
census_payload_digest="0" * 64,
census_digest="0" * 64,
)
payload = derive_census_payload_digest(changed)
old = census.review
review = _attestation(payload, old.producer_identity, old.reviewer_identity, old.conflict_disclosure)
changed = replace(changed, census_payload_digest=payload, review=review)
return replace(changed, census_digest=derive_census_digest(changed))
def _setup() -> _MetricSetup:
admission = _feasible_case()
contract = admission.context.evidence.family_catalog.entries[0].contract
vulnerable_record = _with_fact_set(model_record(contract), ())
vulnerable_named, vulnerable_resolver = _named_inputs(vulnerable_record, contract, ())
vulnerable = validation.make_evidence_validation_input(record=vulnerable_record, named_inputs=vulnerable_named)
safe_decision = replace(vulnerable_record.decision, family_code="safe", outcome=NeutralOutcome.SATISFIED)
safe_record = EvidenceRecord(vulnerable_record.packet, safe_decision, "")
safe_named, safe_resolver = _named_inputs(safe_record, contract, ())
safe = validation.make_evidence_validation_input(record=safe_record, named_inputs=safe_named)
ordinary_region = replace(safe_record.packet.scope.regions[0], end_line=19)
ordinary_scope = replace(safe_record.packet.scope, regions=(ordinary_region,))
ordinary_packet = replace(safe_record.packet, scope=ordinary_scope)
ordinary_decision = replace(
safe_record.decision,
bounded_scope=ordinary_scope,
packet_digest=derive_packet_digest(ordinary_packet),
)
ordinary_record = EvidenceRecord(ordinary_packet, ordinary_decision, "")
ordinary_named, ordinary_resolver = _named_inputs(ordinary_record, contract, ())
ordinary = validation.make_evidence_validation_input(record=ordinary_record, named_inputs=ordinary_named)
resolver = _resolver(admission.resolver, vulnerable_resolver, safe_resolver, ordinary_resolver)
pair = canonical.content_digest("pair-1")
vulnerable_case, resolver = _case_result(vulnerable, AdmissionCaseRole.VULNERABLE, pair, resolver, contract)
fixed_case, resolver = _case_result(safe, AdmissionCaseRole.FIXED, pair, resolver, contract)
ordinary_case, resolver = _case_result(ordinary, AdmissionCaseRole.ORDINARY, None, resolver, contract)
scope = vulnerable_case.scope
policy = DenominatorPolicy(standard_metric_rules((scope,)), IntervalMethod.WILSON_95, "")
row = ScopeCensus(scope, 3, 1, 1, 1, 1, 6, 0)
cases = (vulnerable_case, fixed_case, ordinary_case)
population_digest = admission.manifest.census.population_digest
census = _resign_census(admission.manifest.census, row, population_digest)
identity = replace(admission.manifest.identity, population_digest=population_digest)
manifest = _resign_manifest(
replace(admission.manifest, identity=identity, census=census),
census.review.conflict_disclosure,
)
return _MetricSetup(manifest, policy, cases, admission.context, admission.port, resolver, admission.readiness)
def test_metrics_are_derived_from_validated_cases_and_reviewed_counts() -> None:
run = _run(_setup())
result = _derive(run)
by_metric = {item.metric_id: item for item in result.metrics}
assert (result.in_scope_case_count, result.whole_population_case_count) == (3, 3)
assert by_metric[MetricId.PAIRED_CORRECTNESS].numerator == 1
assert by_metric[MetricId.VULNERABLE_RECALL].numerator == 1
assert by_metric[MetricId.FIXED_FALSE_ALARM_RATE].numerator == 0
assert by_metric[MetricId.ORDINARY_FALSE_ALARMS_PER_100].value == 0.0
assert by_metric[MetricId.PRECISION].value == 1.0
assert by_metric[MetricId.SEMANTIC_STAGE_COVERAGE].numerator == 6
assert by_metric[MetricId.MODEL_SUCCESS_RATE].value is None
assert result.family_decision_vocabulary == ("unsafe", "safe")
assert tuple((item.reason, item.count) for item in result.family_decision_counts) == (("unsafe", 1), ("safe", 2))
assert result.neutral_outcome_counts[0].count == 1
assert result.evidence_basis_counts[tuple(EvidenceBasis).index(EvidenceBasis.DETERMINISTIC)].count == 1
def test_caller_case_sequence_is_snapshotted_before_release_validation() -> None:
run = _run(_setup())
original = run.setup.cases
substituted = replace(original[0], total_reply_ms=original[0].total_reply_ms + 1)
substituted = replace(substituted, result_digest=derive_admission_case_result_digest(substituted))
switching = _TwoPassSequence(original, (substituted, *original[1:]))
result = derive_admission_metrics(
run.setup.manifest,
run.setup.denominator,
run.setup.readiness,
run.preparation,
run.receipt,
run.release,
switching,
run.setup.context,
run.setup.custody,
run.setup.resolver,
)
assert switching.traversals == 1
assert result.runtime_ms == sum(item.total_reply_ms for item in original)
assert result.case_result_digests == tuple(sorted(item.result_digest for item in original))
def test_caller_readiness_sequence_is_snapshotted_once() -> None:
run = _run(_setup())
switching = _TwoPassSequence(run.setup.readiness, run.setup.readiness[:-1])
result = derive_admission_metrics(
run.setup.manifest,
run.setup.denominator,
switching,
run.preparation,
run.receipt,
run.release,
run.setup.cases,
run.setup.context,
run.setup.custody,
run.setup.resolver,
)
assert switching.traversals == 1
assert result.in_scope_case_count == len(run.setup.cases)
def test_case_count_and_pair_drift_fail_before_metric_production() -> None:
run = _run(_setup())
cases = run.setup.cases
with pytest.raises(AdmissionMetricsError, match="release|binding|case population"):
_derive(run, cases=cases[:-1])
run = _run(_setup())
cases = run.setup.cases
changed = replace(cases[0], pair_id_digest=None)
changed = replace(changed, result_digest=derive_admission_case_result_digest(changed))
with pytest.raises(AdmissionMetricsError, match="release|binding|pair"):
_derive(run, cases=(changed, *cases[1:]))
def test_supplied_verdict_and_instrument_are_not_trusted() -> None:
run = _run(_setup())
cases = run.setup.cases
forged = replace(cases[0], defect_claims=())
forged = replace(forged, result_digest=derive_admission_case_result_digest(forged))
forged_cases = (forged, *cases[1:])
with pytest.raises(AdmissionMetricsError, match="verdict"):
_derive(run, cases=forged_cases, release=_release_for(run, forged_cases))
run = _run(_setup())
cases = run.setup.cases
unread = replace(cases[0].input_receipts[0], opened=False)
broken = replace(cases[0], input_receipts=(unread,))
broken = replace(broken, result_digest=derive_admission_case_result_digest(broken))
broken_cases = (broken, *cases[1:])
with pytest.raises(AdmissionMetricsError, match="instrument|tracked input"):
_derive(run, cases=broken_cases, release=_release_for(run, broken_cases))
def test_forged_evidence_and_budget_fail_before_metric_production() -> None:
run = _run(_setup())
cases = run.setup.cases
forged_input = replace(cases[0].evidence_inputs[0], input_digest="0" * 64)
forged = replace(cases[0], evidence_inputs=(forged_input,))
forged = replace(forged, result_digest=derive_admission_case_result_digest(forged))
forged_cases = (forged, *cases[1:])
with pytest.raises(AdmissionMetricsError, match="evidence"):
_derive(run, cases=forged_cases, release=_release_for(run, forged_cases))
run = _run(_setup())
cases = run.setup.cases
over_budget = replace(cases[0], total_reply_ms=cases[0].budget.budget_ms + 1)
over_budget = replace(over_budget, result_digest=derive_admission_case_result_digest(over_budget))
budget_cases = (over_budget, *cases[1:])
with pytest.raises(AdmissionMetricsError, match="budget"):
_derive(run, cases=budget_cases, release=_release_for(run, budget_cases))
def test_runner_case_digest_is_rederived() -> None:
run = _run(_setup())
cases = run.setup.cases
forged = replace(cases[0], result_digest="0" * 64)
with pytest.raises(AdmissionMetricsError, match="case result digest"):
_derive(run, cases=(forged, *cases[1:]))
def test_proof_attachments_are_revalidated_with_the_context_policy() -> None:
run = _run(_setup())
cases = run.setup.cases
attachment, _, proof_resolver = _proof(cases[0].evidence_inputs[0].record)
changed = replace(cases[0], proof_attachments=(attachment,))
changed = replace(changed, result_digest=derive_admission_case_result_digest(changed))
changed_cases = (changed, *cases[1:])
proof_run = run._replace(setup=run.setup._replace(resolver=_resolver(run.setup.resolver, proof_resolver)))
with pytest.raises(AdmissionMetricsError, match="proof.*trust policy"):
_derive(proof_run, cases=changed_cases, release=_release_for(proof_run, changed_cases))
def test_semantic_site_coverage_is_rederived_from_case_evidence() -> None:
run = _run(_setup())
cases = run.setup.cases
changed_coverage = replace(cases[0].coverage, sites_recognized=1)
changed = replace(cases[0], coverage=changed_coverage)
changed = replace(changed, result_digest=derive_admission_case_result_digest(changed))
changed_cases = (changed, *cases[1:])
with pytest.raises(AdmissionMetricsError, match="coverage aggregate"):
_derive(run, cases=changed_cases, release=_release_for(run, changed_cases))
def test_nested_case_graph_refuses_substituted_contract_types() -> None:
run = _run(_setup())
cases = run.setup.cases
original = cases[0].input_manifest
substituted = _InputManifestSubclass(original.tracked_input_ids, original.manifest_digest)
changed = replace(cases[0], input_manifest=substituted)
changed = replace(changed, result_digest=derive_admission_case_result_digest(changed))
with pytest.raises(AdmissionMetricsError, match="closed admission case"):
_derive(run, cases=(changed, *cases[1:]))
def test_pair_identity_is_namespaced_by_family_and_ecosystem() -> None:
digest = canonical.content_digest("shared-pair")
python = derive_scoped_pair_key(ScopeKey("family", "python"), digest)
javascript = derive_scoped_pair_key(ScopeKey("family", "javascript"), digest)
assert python != javascript
def _with_out_of_scope_case(setup: _MetricSetup) -> _MetricSetup:
contract = setup.context.evidence.family_catalog.entries[0].contract
expanded_contract = replace(contract, family_version="1-js", ecosystems=("javascript",))
javascript_entry = FamilyCatalogEntry(expanded_contract, family_contract_digest(expanded_contract))
entries = tuple(sorted((*setup.context.evidence.family_catalog.entries, javascript_entry), key=catalog_entry_key))
catalog = FamilyCatalog(entries, derive_catalog_digest(entries))
evidence_context = validation.make_evidence_validation_context(
family_catalog=catalog,
fact_packs=setup.context.evidence.fact_packs,
model_programs=setup.context.evidence.model_programs,
lineages=setup.context.evidence.lineages,
fact_review_policy=setup.context.evidence.fact_review_policy,
provenance_trust_policy=setup.context.evidence.provenance_trust_policy,
custody_policy=setup.context.evidence.custody_policy,
)
context = admission_context.make_admission_validation_context(
evidence=evidence_context,
admission_review_policy=setup.context.admission_review_policy,
proof_trust_policy=None,
)
outside_record = _with_fact_set(model_record(expanded_contract), ())
outside_packet = replace(outside_record.packet, ecosystem="javascript")
outside_decision = replace(outside_record.decision, packet_digest=derive_packet_digest(outside_packet))
outside_record = EvidenceRecord(outside_packet, outside_decision, "")
outside_named, outside_resolver = _named_inputs(outside_record, expanded_contract, ())
outside_named, outside_resolver = _rename_named_inputs(outside_named, outside_resolver, "javascript")
outside_input = validation.make_evidence_validation_input(record=outside_record, named_inputs=outside_named)
resolver = _resolver(setup.resolver, outside_resolver)
outside, resolver = _case_result(outside_input, AdmissionCaseRole.ORDINARY, None, resolver, expanded_contract)
outside = replace(outside, case_id_digest=canonical.content_digest("outside-javascript-case"))
outside = replace(outside, result_digest=derive_admission_case_result_digest(outside))
cases = (*setup.cases, outside)
population_digest = setup.manifest.census.population_digest
row = setup.manifest.census.rows[0]
outside_row = ScopeCensus(outside.scope, 1, 0, 0, 0, 1, 2, 0)
rows = tuple(sorted((row, outside_row), key=lambda item: item.scope))
census = _resign_census_rows(setup.manifest.census, rows, population_digest)
contract_digests = tuple(sorted(entry.contract_digest for entry in catalog.entries))
identity = replace(
setup.manifest.identity,
population_digest=population_digest,
family_contract_digests=contract_digests,
family_contract_set_digest=catalog.catalog_digest,
outcome_mapping_digest=admission_context.derive_outcome_mapping_digest(catalog),
)
program_digest = context.evidence.model_programs[0].declaration_digest
current_identity = admission_context.derive_analysis_identity(identity, catalog, identity.claimed_scopes[0], program_digest)
readiness = _chain(identity=current_identity)[:6]
manifest = _resign_manifest(
replace(
setup.manifest,
identity=identity,
census=census,
readiness_digests=tuple(sorted(item.result_digest for item in readiness)),
validation_context_digest=context.context_digest,
),
census.review.conflict_disclosure,
)
return _MetricSetup(manifest, setup.denominator, cases, context, setup.custody, resolver, readiness)
def test_whole_population_coverage_and_runtime_retain_out_of_scope_cases() -> None:
run = _run(_with_out_of_scope_case(_setup()))
result = _derive(run)
assert (result.in_scope_case_count, result.whole_population_case_count) == (3, 4)
assert (result.stage_funnel.questions_formed, result.whole_population_funnel.questions_formed) == (3, 4)
assert (result.runtime_ms, result.whole_population_runtime_ms) == (30, 40)
ordinary = next(item for item in result.metrics if item.metric_id is MetricId.ORDINARY_FALSE_ALARMS_PER_100)
assert ordinary.denominator == 1
def _with_census_rows(setup: _MetricSetup, rows: tuple[ScopeCensus, ...]) -> _MetricSetup:
census = _resign_census_rows(setup.manifest.census, rows, setup.manifest.census.population_digest)
manifest = _resign_manifest(
replace(setup.manifest, census=census),
census.review.conflict_disclosure,
)
return setup._replace(manifest=manifest)
def test_claimed_scope_must_have_a_reviewed_census_row() -> None:
setup = _setup()
unknown = ScopeCensus(ScopeKey("unknown-family", "ruby"), 1, 0, 0, 0, 1, 1, 0)
with pytest.raises(ValueError, match="claimed scope.*census row"):
_run(_with_census_rows(setup, (unknown,)))
def test_every_additional_census_scope_must_resolve_in_the_catalog() -> None:
setup = _setup()
unknown = ScopeCensus(ScopeKey("unknown-family", "ruby"), 1, 0, 0, 0, 1, 1, 0)
rows = tuple(sorted((*setup.manifest.census.rows, unknown), key=lambda item: item.scope))
with pytest.raises(ValueError, match="resolve one exact family contract"):
_run(_with_census_rows(setup, rows))
def test_released_case_without_a_census_row_is_refused_before_metrics() -> None:
setup = _with_out_of_scope_case(_setup())
claimed = tuple(row for row in setup.manifest.census.rows if row.scope in setup.manifest.identity.claimed_scopes)
run = _run(_with_census_rows(setup, claimed))
with pytest.raises(AdmissionMetricsError, match="case scope.*census row"):
_derive(run)
@pytest.mark.parametrize("field", ("semantic_site_capacity", "model_call_capacity"))
def test_released_case_aggregates_must_match_every_census_capacity(field: str) -> None:
setup = _setup()
row = setup.manifest.census.rows[0]
changed = replace(row, **{field: getattr(row, field) + 1})
run = _run(_with_census_rows(setup, (changed,)))
with pytest.raises(AdmissionMetricsError, match="case counts.*census"):
_derive(run)
+10
View File
@@ -74,6 +74,16 @@ def test_input_manifest_and_receipts_validate_exactly() -> None:
validate_instrument(_manifest(), _receipts()) validate_instrument(_manifest(), _receipts())
def test_zero_named_counts_are_allowed_for_measurements_but_not_unresolved_coverage() -> None:
with pytest.raises(ValueError, match="unresolved reason count must be positive"):
_funnel(
unresolved_by_reason=(
ReasonCount("missing_constraint", 2),
ReasonCount("unused_measurement_bucket", 0),
)
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
("opened", "byte_count", "digest", "match"), ("opened", "byte_count", "digest", "match"),
[ [
+15 -1
View File
@@ -80,7 +80,7 @@ def _record(**overrides: object) -> MeasurementRecordV2:
OutcomeCount(NeutralOutcome.INDETERMINATE, 0), OutcomeCount(NeutralOutcome.INDETERMINATE, 0),
), ),
"evidence_basis_counts": ( "evidence_basis_counts": (
BasisCount(EvidenceBasis.DETERMINISTIC, 2), BasisCount(EvidenceBasis.DETERMINISTIC, 1),
BasisCount(EvidenceBasis.MODEL_CORROBORATED, 0), BasisCount(EvidenceBasis.MODEL_CORROBORATED, 0),
BasisCount(EvidenceBasis.EXECUTION_BACKED, 0), BasisCount(EvidenceBasis.EXECUTION_BACKED, 0),
), ),
@@ -182,6 +182,7 @@ def test_clean_or_zero_looking_results_require_read_inputs_and_passed_stages() -
OutcomeCount(NeutralOutcome.SATISFIED, 2), OutcomeCount(NeutralOutcome.SATISFIED, 2),
OutcomeCount(NeutralOutcome.INDETERMINATE, 0), OutcomeCount(NeutralOutcome.INDETERMINATE, 0),
), ),
evidence_basis_counts=tuple(BasisCount(basis, 0) for basis in EvidenceBasis),
metrics=_metrics(model_numerator=2, model_denominator=2), metrics=_metrics(model_numerator=2, model_denominator=2),
stage_funnel=_funnel(findings=0), stage_funnel=_funnel(findings=0),
model_outcome_counts=(ReasonCount("success", 2),), model_outcome_counts=(ReasonCount("success", 2),),
@@ -359,6 +360,19 @@ def test_family_decision_counts_follow_declared_not_lexical_order() -> None:
assert tuple(item.reason for item in record.family_decision_counts) == record.family_decision_vocabulary assert tuple(item.reason for item in record.family_decision_counts) == record.family_decision_vocabulary
def test_family_decision_counts_retain_zero_entries_for_exact_vocabulary() -> None:
record = _record(
family_decision_vocabulary=("allow", "deny", "unknown"),
family_decision_counts=(ReasonCount("allow", 1), ReasonCount("deny", 1), ReasonCount("unknown", 0)),
)
assert tuple((item.reason, item.count) for item in record.family_decision_counts) == (
("allow", 1),
("deny", 1),
("unknown", 0),
)
def test_holdout_report_refuses_wrong_role_overlap_or_empty_manifests() -> None: def test_holdout_report_refuses_wrong_role_overlap_or_empty_manifests() -> None:
report = _report() report = _report()
with pytest.raises(MeasurementRecordError, match="repository_holdout"): with pytest.raises(MeasurementRecordError, match="repository_holdout"):