app_dependency_graph.py (4524B)
1 """Validate the real locked application graph after the proxy crate is retired.""" 2 3 from __future__ import annotations 4 5 from typing import Any 6 7 OWNED = {"tera_core", "tera_ffi", "tera_bindgen", "tera_wasm"} 8 MOBILE = OWNED - {"tera_wasm"} 9 RETIRED = { 10 "radroots_ios_source_lock", 11 "radroots_mobile_core", 12 "radroots_mobile_ffi", 13 "radroots_mobile_bindgen", 14 "radroots_mobile_wasm", 15 } 16 17 18 class GraphError(ValueError): 19 """A stable rejection without dependency source paths or command output.""" 20 21 22 def require(condition: bool, reason: str) -> None: 23 if not condition: 24 raise GraphError(reason) 25 26 27 def indexed(rows: Any, label: str) -> dict[str, dict[str, Any]]: 28 require(isinstance(rows, list) and bool(rows), f"{label} inventory is absent") 29 result = {} 30 for row in rows: 31 require(isinstance(row, dict), f"{label} entry is malformed") 32 identifier = row.get("id") 33 require( 34 isinstance(identifier, str) and bool(identifier), 35 f"{label} identity is absent", 36 ) 37 require(identifier not in result, f"{label} identity is duplicated") 38 result[identifier] = row 39 return result 40 41 42 def owned_packages( 43 document: dict[str, Any], packages: dict[str, Any] 44 ) -> dict[str, Any]: 45 members = document.get("workspace_members") 46 require(isinstance(members, list), "owned workspace members are absent") 47 require(len(members) == len(OWNED), "owned workspace inventory differs") 48 require( 49 all(identifier in packages for identifier in members), "owned package is absent" 50 ) 51 owned = { 52 packages[identifier]["name"]: packages[identifier] for identifier in members 53 } 54 require(set(owned) == OWNED, "owned workspace inventory differs") 55 defaults = document.get("workspace_default_members") 56 require(isinstance(defaults, list), "mobile defaults are absent") 57 require( 58 sorted(defaults) == sorted(owned[name]["id"] for name in MOBILE), 59 "mobile defaults must select the real native packages only", 60 ) 61 return owned 62 63 64 def foundation_sources(packages: dict[str, Any], foundation: dict[str, Any]) -> None: 65 revision = foundation["revision"] 66 expected = f"git+{foundation['repository']}?rev={revision}#{revision}" 67 shared = [] 68 for package in packages.values(): 69 name = package.get("name") 70 require(isinstance(name, str), "resolved package name is absent") 71 require( 72 name not in RETIRED, 73 "retired mobile producer or source-lock shim is present", 74 ) 75 if name.startswith("radroots_"): 76 shared.append(package) 77 require( 78 package.get("source") == expected, "resolved foundation source differs" 79 ) 80 require( 81 package.get("version") == foundation["version"], 82 "resolved foundation version differs", 83 ) 84 elif package.get("source") is None: 85 require( 86 name in OWNED, "unowned local package entered the application graph" 87 ) 88 require(bool(shared), "resolved foundation inventory is empty") 89 90 91 def mobile_profile(owned: dict[str, Any], nodes: dict[str, Any]) -> None: 92 ffi = owned["tera_ffi"] 93 edges = ffi.get("dependencies") 94 require(isinstance(edges, list), "owned FFI dependency inventory is absent") 95 core_edges = [edge for edge in edges if edge.get("name") == "tera_core"] 96 require(len(core_edges) == 1, "owned FFI must select one application core") 97 edge = core_edges[0] 98 require( 99 edge.get("source") is None and edge.get("kind") is None, 100 "FFI core must be an owned runtime dependency", 101 ) 102 require( 103 "mobile-social" in edge.get("features", []), 104 "owned FFI mobile-social profile is absent", 105 ) 106 core_node = nodes.get(owned["tera_core"]["id"], {}) 107 require( 108 "mobile-social" in core_node.get("features", []), 109 "resolved mobile-social feature is absent", 110 ) 111 112 113 def validate(document: dict[str, Any], foundation: dict[str, Any]) -> None: 114 packages = indexed(document.get("packages"), "resolved package") 115 owned = owned_packages(document, packages) 116 foundation_sources(packages, foundation) 117 resolution = document.get("resolve") 118 require(isinstance(resolution, dict), "resolved dependency graph is absent") 119 nodes = indexed(resolution.get("nodes"), "resolved node") 120 require(set(nodes) == set(packages), "resolved node inventory differs") 121 mobile_profile(owned, nodes)