feat(observatory): attest recording equipment and capture profiles
This commit is contained in:
@@ -124,8 +124,8 @@ class ObservationOnlyAuthority:
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class K1RecordedSourceRequirements:
|
||||
"""Typed capability matcher for one class of recorded K1 sessions."""
|
||||
class RecordedSourceRequirements:
|
||||
"""Typed capability matcher for one exact recorded capture contract."""
|
||||
|
||||
plugin_id: str
|
||||
archive_id: str
|
||||
@@ -206,6 +206,11 @@ class K1RecordedSourceRequirements:
|
||||
}
|
||||
|
||||
|
||||
# Compatibility name retained for promoted package readers. Canonical JSON
|
||||
# and all digests remain byte-for-byte unchanged.
|
||||
K1RecordedSourceRequirements = RecordedSourceRequirements
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PortableSourceAdapterIdentity:
|
||||
adapter_id: str
|
||||
|
||||
@@ -26,6 +26,7 @@ from k1link.observatory.source_admission import (
|
||||
PortableRecordedSourceCapability,
|
||||
PortableSourceAdmissionError,
|
||||
)
|
||||
from k1link.sessions.equipment import EquipmentCaptureRegistry
|
||||
from k1link.sessions.models import SessionSummary
|
||||
|
||||
PORTABLE_LABORATORY_SETUP_CATALOG_SCHEMA: Final = (
|
||||
@@ -93,12 +94,14 @@ type PortableSourceCapabilityProbe = Callable[[str], PortableRecordedSourceCapab
|
||||
class _SourceCompatibility:
|
||||
compatible: bool
|
||||
capability: PortableRecordedSourceCapability | None
|
||||
reason_code: str
|
||||
reason: str
|
||||
|
||||
def as_dict(self) -> dict[str, object]:
|
||||
return {
|
||||
"outcome": "pass" if self.compatible else "blocked",
|
||||
"compatible": self.compatible,
|
||||
"reason_code": self.reason_code,
|
||||
"reason": self.reason,
|
||||
}
|
||||
|
||||
@@ -112,12 +115,14 @@ class PortableSetupProjector:
|
||||
registry: PortableRunDefinitionRegistry,
|
||||
capability_probe: PortableDefinitionCapabilityProbe,
|
||||
dispatch_available: bool = False,
|
||||
equipment_capture_registry: EquipmentCaptureRegistry | None = None,
|
||||
) -> None:
|
||||
if not hasattr(capability_probe, "probe"):
|
||||
raise PortableSetupProjectionError("portable source capability probe is unavailable")
|
||||
self._registry = registry
|
||||
self._capability_probe = capability_probe
|
||||
self._dispatch_available = dispatch_available
|
||||
self._equipment_capture_registry = equipment_capture_registry
|
||||
for definition in registry.definitions:
|
||||
_validate_model_presentation(definition)
|
||||
if definition.authority.as_dict() != _OBSERVATION_ONLY_AUTHORITY:
|
||||
@@ -159,7 +164,7 @@ class PortableSetupProjector:
|
||||
except PortableRunDefinitionRegistryError as exc:
|
||||
raise PortableSetupProjectionError("portable setup is unavailable") from exc
|
||||
presentation = _presentation(definition)
|
||||
compatibility = self._probe_source(definition, source.session_id, presentation)
|
||||
compatibility = self._probe_source(definition, source, presentation)
|
||||
executor = definition.executor
|
||||
submission_allowed = (
|
||||
compatibility.compatible and executor.ready and self._dispatch_available
|
||||
@@ -204,9 +209,51 @@ class PortableSetupProjector:
|
||||
def _probe_source(
|
||||
self,
|
||||
definition: PortableRunDefinition,
|
||||
source_session_id: str,
|
||||
source: SessionSummary,
|
||||
presentation: dict[str, str],
|
||||
) -> _SourceCompatibility:
|
||||
source_session_id = source.session_id
|
||||
if self._equipment_capture_registry is not None:
|
||||
expected_profile = (
|
||||
self._equipment_capture_registry.compatible_profile_for_requirements(
|
||||
definition.source_requirements.as_dict()
|
||||
)
|
||||
)
|
||||
if expected_profile is None:
|
||||
raise PortableSetupProjectionError(
|
||||
"portable source requirements have no capture profile allowlist"
|
||||
)
|
||||
attestation = source.capture_attestation
|
||||
if attestation is None:
|
||||
return _SourceCompatibility(
|
||||
compatible=False,
|
||||
capability=None,
|
||||
reason_code="capture-attestation-missing",
|
||||
reason="У записи нет проверенной привязки к оборудованию и профилю записи.",
|
||||
)
|
||||
if (
|
||||
attestation.equipment_model_id
|
||||
!= expected_profile.equipment.equipment_model_id
|
||||
or attestation.equipment_model_sha256
|
||||
!= expected_profile.equipment.equipment_model_sha256
|
||||
):
|
||||
return _SourceCompatibility(
|
||||
compatible=False,
|
||||
capability=None,
|
||||
reason_code="equipment-model-mismatch",
|
||||
reason="Модель оборудования записи не поддерживается этим профилем.",
|
||||
)
|
||||
if (
|
||||
attestation.capture_profile_id != expected_profile.capture_profile_id
|
||||
or attestation.capture_profile_sha256
|
||||
!= expected_profile.capture_profile_sha256
|
||||
):
|
||||
return _SourceCompatibility(
|
||||
compatible=False,
|
||||
capability=None,
|
||||
reason_code="capture-profile-not-allowed",
|
||||
reason="Профиль записи не входит в точный список совместимости.",
|
||||
)
|
||||
try:
|
||||
capability = self._capability_probe.probe(
|
||||
source_session_id=source_session_id,
|
||||
@@ -217,6 +264,7 @@ class PortableSetupProjector:
|
||||
return _SourceCompatibility(
|
||||
compatible=False,
|
||||
capability=None,
|
||||
reason_code="source-contract-mismatch",
|
||||
reason=presentation["incompatible"],
|
||||
)
|
||||
if not isinstance(capability, PortableRecordedSourceCapability):
|
||||
@@ -230,6 +278,7 @@ class PortableSetupProjector:
|
||||
return _SourceCompatibility(
|
||||
compatible=True,
|
||||
capability=capability,
|
||||
reason_code="source-compatible",
|
||||
reason=presentation["compatible"],
|
||||
)
|
||||
|
||||
|
||||
@@ -76,8 +76,8 @@ class PortableSourceAdmissionStaleError(PortableSourceAdmissionIntegrityError):
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class RecordedK1SourceRequirements:
|
||||
"""Typed matcher owned by a portable RunDefinition.
|
||||
class RecordedSourceRequirements:
|
||||
"""Typed matcher owned by a portable recorded-source RunDefinition.
|
||||
|
||||
``camera_init_sha256`` is the exact ISO-BMFF initialization segment for the
|
||||
admitted codec/resolution profile. Width and height are asserted by that
|
||||
@@ -173,6 +173,11 @@ class RecordedK1SourceRequirements:
|
||||
return _sha256(self.adapter_document())
|
||||
|
||||
|
||||
# Compatibility import for already promoted packages. New code uses the
|
||||
# equipment-neutral name; the serialized contract is unchanged.
|
||||
RecordedK1SourceRequirements = RecordedSourceRequirements
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PortableRecordedSourceCapability:
|
||||
"""Cheap catalog/summary attestation used by compatibility surfaces.
|
||||
|
||||
Reference in New Issue
Block a user