Skip to content

Commit 31a0f13

Browse files
committed
core: enforce shared provider branding policy
1 parent 19b3d8e commit 31a0f13

1 file changed

Lines changed: 58 additions & 18 deletions

File tree

scripts/normalize_core_media_policy.py

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
#!/usr/bin/env python3
2-
"""Enforce Core-wide media identity/presentation/security policy.
2+
"""Enforce Core-wide media identity/presentation/branding/security policy.
33
44
Provider-specific media repair is intentionally excluded from this normalizer.
5-
Identity, stream facts, presentation, platform compatibility and security are
6-
repository-wide Core concerns. Provider rows may still describe capability or
7-
official-domain discovery, but they must not own private copies of those Core
8-
layers.
5+
Identity, stream facts, presentation, branding, platform compatibility and security
6+
are repository-wide Core concerns. Provider rows may still describe capability or
7+
official-domain discovery, but they must not own private copies of those Core layers.
98
"""
109
from __future__ import annotations
1110

@@ -16,8 +15,11 @@
1615

1716
ROOT = Path(__file__).resolve().parents[1]
1817
OVERRIDES = ROOT / "provider-overrides.json"
18+
MANIFEST = ROOT / "manifest.json"
19+
PROVIDER_BRANDING = ROOT / "assets/providers/emojis.json"
1920
DESKTOP_COMPAT = ROOT / "scripts/publish_desktop_runtime_compat.py"
2021
GLOBAL_SECURITY_HOOK = "scripts/provider_patches/global_provider_security_hardening_v1.py"
22+
GLOBAL_BRANDING_HOOK = "scripts/provider_patches/global_provider_branding_v1.py"
2123
ALLOWED_SHARED_PURSTREAM_SCRIPTS = {
2224
"scripts/provider_patches/native_sync_fetch_target_order_v1.py",
2325
"scripts/provider_patches/runtime_capability_media_safety_v4.py",
@@ -46,21 +48,23 @@ def load() -> dict[str, Any]:
4648
return value
4749

4850

49-
def _normalize_global_security(value: dict[str, Any], changed: list[str]) -> None:
51+
def _normalize_global_tail(value: dict[str, Any], changed: list[str]) -> None:
5052
playback = value.get("playback_integrity_policy")
5153
if not isinstance(playback, dict):
5254
raise ValueError("playback_integrity_policy must be an object")
5355
hooks = playback.get("global_discovery_hooks") or []
5456
if not isinstance(hooks, list):
5557
raise ValueError("playback_integrity_policy.global_discovery_hooks must be an array")
56-
normalized = [str(path) for path in hooks if str(path).strip() and str(path) != GLOBAL_SECURITY_HOOK]
57-
# Keep security last in the legacy-tail stage. Pre/post HLS hooks are removed
58-
# from that tail by apply_provider_overrides.py, so this executes after media
59-
# recovery and directly before the controlled Core presentation wrapper.
60-
normalized.append(GLOBAL_SECURITY_HOOK)
58+
controlled = {GLOBAL_SECURITY_HOOK, GLOBAL_BRANDING_HOOK}
59+
normalized = [str(path) for path in hooks if str(path).strip() and str(path) not in controlled]
60+
# Pre/post HLS hooks are removed from this legacy-tail stage by
61+
# apply_provider_overrides.py. Security therefore executes after all media
62+
# recovery, branding then establishes the stable provider text identity, and
63+
# the separate controlled presentation wrapper executes last.
64+
normalized.extend([GLOBAL_SECURITY_HOOK, GLOBAL_BRANDING_HOOK])
6165
if normalized != hooks:
6266
playback["global_discovery_hooks"] = normalized
63-
changed.append("playback_integrity_policy.global_discovery_hooks:global_security")
67+
changed.append("playback_integrity_policy.global_discovery_hooks:security_branding_tail")
6468

6569

6670
def normalize(value: dict[str, Any]) -> tuple[dict[str, Any], list[str]]:
@@ -105,7 +109,7 @@ def normalize(value: dict[str, Any]) -> tuple[dict[str, Any], list[str]]:
105109
row["notes"] = notes
106110
changed.append("provider_patches.purstream.notes")
107111

108-
_normalize_global_security(value, changed)
112+
_normalize_global_tail(value, changed)
109113
return value, changed
110114

111115

@@ -119,6 +123,34 @@ def normalize_source_files(*, apply: bool) -> list[str]:
119123
return changed
120124

121125

126+
def _assert_branding_inventory() -> None:
127+
manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
128+
branding = json.loads(PROVIDER_BRANDING.read_text(encoding="utf-8"))
129+
if branding.get("policy") != "committed-provider-default-emoji":
130+
raise ValueError("provider branding policy must be committed-provider-default-emoji")
131+
rows = branding.get("providers")
132+
if not isinstance(rows, dict):
133+
raise ValueError("provider branding providers must be an object")
134+
manifest_ids = {
135+
str(row.get("id") or "").strip().casefold()
136+
for row in (manifest.get("scrapers") or [])
137+
if isinstance(row, dict) and str(row.get("id") or "").strip()
138+
}
139+
branding_ids = {str(value).strip().casefold() for value in rows}
140+
if branding_ids != manifest_ids:
141+
missing = sorted(manifest_ids - branding_ids)
142+
unknown = sorted(branding_ids - manifest_ids)
143+
raise ValueError(
144+
"provider branding coverage mismatch: "
145+
f"missing={','.join(missing) or 'none'} unknown={','.join(unknown) or 'none'}"
146+
)
147+
for provider_id, row in rows.items():
148+
if not isinstance(row, dict):
149+
raise ValueError(f"provider branding row must be an object: {provider_id}")
150+
if not str(row.get("name") or "").strip() or not str(row.get("emoji") or "").strip():
151+
raise ValueError(f"provider branding row requires clean name + emoji: {provider_id}")
152+
153+
122154
def assert_policy(value: dict[str, Any]) -> None:
123155
row = value["provider_patches"]["purstream"]
124156
scripts = {str(path) for path in (row.get("patch_scripts") or [])}
@@ -130,22 +162,29 @@ def assert_policy(value: dict[str, Any]) -> None:
130162
runtime = ROOT / "scripts/provider_patches/runtime_capability_media_safety_v4.py"
131163
presentation = ROOT / "scripts/provider_patches/global_stream_presentation_v1.py"
132164
security = ROOT / GLOBAL_SECURITY_HOOK
133-
if not runtime.is_file() or not presentation.is_file() or not security.is_file():
134-
raise ValueError("shared Core media/security implementation is missing")
165+
branding = ROOT / GLOBAL_BRANDING_HOOK
166+
if not runtime.is_file() or not presentation.is_file() or not security.is_file() or not branding.is_file():
167+
raise ValueError("shared Core media/branding/security implementation is missing")
135168
runtime_text = runtime.read_text(encoding="utf-8")
136169
presentation_text = presentation.read_text(encoding="utf-8")
137170
security_text = security.read_text(encoding="utf-8")
171+
branding_text = branding.read_text(encoding="utf-8")
138172
if "field-safety-v5-native-identity-collisions-all-rows" not in runtime_text:
139173
raise ValueError("shared runtime identity safety revision is not current")
140174
if "NUVIO_GLOBAL_STREAM_PRESENTATION_V1" not in presentation_text:
141175
raise ValueError("shared stream presentation wrapper is missing")
142176
if "NUVIO_GLOBAL_PROVIDER_SECURITY_HOOK_V1" not in security_text:
143177
raise ValueError("shared provider security Core hook is missing")
178+
if "NUVIO_GLOBAL_PROVIDER_BRANDING_V1" not in branding_text:
179+
raise ValueError("shared provider branding Core hook is missing")
180+
_assert_branding_inventory()
144181

145182
playback = value.get("playback_integrity_policy") or {}
146183
hooks = [str(path) for path in (playback.get("global_discovery_hooks") or [])]
147-
if hooks.count(GLOBAL_SECURITY_HOOK) != 1 or hooks[-1:] != [GLOBAL_SECURITY_HOOK]:
148-
raise ValueError("global provider security hook must be present exactly once as final Core tail hook")
184+
if hooks.count(GLOBAL_SECURITY_HOOK) != 1 or hooks.count(GLOBAL_BRANDING_HOOK) != 1:
185+
raise ValueError("global provider security/branding hooks must each be present exactly once")
186+
if hooks[-2:] != [GLOBAL_SECURITY_HOOK, GLOBAL_BRANDING_HOOK]:
187+
raise ValueError("global Core tail must end security -> provider branding before presentation")
149188

150189
desktop_text = DESKTOP_COMPAT.read_text(encoding="utf-8")
151190
if _PURSTREAM_DESKTOP_TARGET in desktop_text:
@@ -176,7 +215,8 @@ def main() -> int:
176215
print(
177216
"FIELD_CORE_MEDIA_POLICY "
178217
f"provider_specific_media_repairs=0 changed={len(changed) + len(source_changes)} "
179-
"identity=global_runtime presentation=global_core compatibility=shared security=global_core"
218+
"identity=global_runtime presentation=global_core branding=global_core "
219+
"compatibility=shared security=global_core"
180220
)
181221
return 0
182222

0 commit comments

Comments
 (0)