ak.profile.media_service_binding.v1
ak.profile.media_service_binding.v1
role
"server" description
"Transport-agnostic media backend binding framework. Requires ak.realm.media_service to declare a multi-focus list with backend type discriminator; defines normative token exchange, session focus selection, participant binding, E2EE key injection via MLS-Exporter label ak.rtc-frame-key/v1, and recording artifact pipeline. Specific backends (livekit / arkret_native) declare an additional sub-profile. See zh/crypto-media/media-service-binding.md §2 / §3 / §5–§8 and zh/crypto-media/call-state.md §4." enforcement_phases
[
"conformance",
"startup_claim_guard"
] operation_requirements
[
{
"direction": "provide",
"operation_id": "ak.self.call.media.exchange.issue_token.v1",
"binding_kind": "http_json"
}
] depends_on
[
"ak.profile.webrtc_media.v1"
] required_profiles
[
"ak.profile.webrtc_media.v1"
] required_event_kinds
[
"ak.realm.media_service",
"ak.call.state"
] rejected_event_kinds
[] required_schemas
[
"ak.schema.event.v1"
] required_fixtures
[
"privacy-security-fixture.json"
] optional_extensions
[
"focus_health_endpoint",
"cascade_group",
"recording_via_blob_pipeline"
] feature_discovery
{
"required": [
"media_service_foci",
"focus_token_exchange_ttl",
"participant_binding_scheme",
"session_focus_persistence",
"mls_exporter_label_ak_rtc_frame_key_v1",
"recording_exporter_label_ak_rtc_recording_key_v1"
],
"unsupported_optional": "MUST fail closed with unknown_focus_type for unsupported foci[].focus_kind values; MUST NOT infer a focus when foci[] is missing"
} additional_requirements
{
"token_ttl_must": "Token issuer MUST cap backend_token / participant_binding expires_at at 600s after issuance; SHOULD use ≤ 300s.",
"issuer_did_binding_must": "service_signature.kid and participant_binding.issuer_kid MUST resolve to a service DID declared in ak.realm.media_service.service_id of the current MLS epoch; otherwise reject with token_issuer_unauthorised.",
"session_focus_must": "ak.call.state.session_focus is write-once per call; in-session focus migration is not supported in v1. Token issuer MUST reject focus_id != committed session_focus (focus_mismatch).",
"participant_binding_must": "ak.call.state.roster_delta join participant_binding MUST be verified by both reducer (on event acceptance) and client (on backend ParticipantConnected event). Failure surfaces as participant_binding_invalid (reducer) or participant_id_unrecognised (client).",
"e2ee_key_source_must": "Client binding adapter MUST derive frame keys from Arkret MLS-Exporter with label ak.rtc-frame-key/v1 (length=19 bytes, Context=canonical_json({realm_id, call_id, focus_id, epoch_id, participant_id, device_id}), KDF.Nh=32 bytes), where participant_id/device_id come from the verified call roster participant value and participant_binding. Empty Context, missing sender fields, or epoch-only binding MUST be rejected with e2ee_key_source_unauthorised. MUST NOT accept keys from backend cloud key escrow; reject with e2ee_key_source_unauthorised.",
"recording_pipeline_must": "Backend-generated recording artifacts MUST strand through Arkret authenticated blob upload (media-and-blob.md) with recording_initiator_capability_ref. Direct backend-to-storage paths are forbidden (recording_artifact_pipeline_bypassed).",
"recording_key_source_must": "Recording artifact encryption keys MUST derive from Arkret MLS-Exporter label ak.rtc-recording-key/v1 with Context=canonical_json({realm_id, call_id, focus_id, recording_id, media_service_id, recording_start_event_id}) and 32 output bytes. The SFrame label ak.rtc-frame-key/v1 and empty Context MUST be rejected for recording artifacts."
}