@@ -254,13 +254,77 @@ def cache_rebuild_lock(workspace: Path, payload: dict[str, Any]) -> str:
254254 return str (target )
255255
256256
257- def compare_lock_payloads (left : dict [str , Any ], right : dict [str , Any ]) -> bool :
258- def comparable (payload : dict [str , Any ]) -> dict [str , Any ]:
259- data = dict (payload )
260- data .pop ("build_id" , None )
261- return data
257+ def get_functional_snapshot (payload : dict [str , Any ]) -> dict [str , Any ]:
258+ """
259+ Extract critical metadata for functional equivalence check.
260+ Ignores non-functional fields like build_id, git_branch, or resolved_command.
261+ """
262+ if not isinstance (payload , dict ):
263+ return {}
264+
265+ project = payload .get ("project" ) or {}
266+ build = payload .get ("build" ) or {}
267+ engine = payload .get ("engine" ) or {}
268+ platform_info = payload .get ("platform" ) or {}
269+ dependencies = payload .get ("dependencies" ) or {}
270+
271+ return {
272+ "project" : {
273+ "name" : str (project .get ("name" ) or "" ),
274+ "version" : str (project .get ("version" ) or "" ),
275+ "entry" : str (project .get ("entry" ) or "" ),
276+ "git_commit" : project .get ("git_commit" ),
277+ },
278+ "build" : {
279+ "output" : str (build .get ("output" ) or "" ),
280+ "data" : list (build .get ("data" ) or []),
281+ "exclude" : list (build .get ("exclude" ) or []),
282+ "icon" : build .get ("icon" ),
283+ },
284+ "engine" : {
285+ "name" : str (engine .get ("name" ) or "" ),
286+ "version" : str (engine .get ("version" ) or "" ),
287+ "config" : engine .get ("config" ) or {},
288+ },
289+ "platform" : {
290+ "os" : platform_info .get ("os" ),
291+ "arch" : platform_info .get ("arch" ),
292+ "python_version" : platform_info .get ("python_version" ),
293+ },
294+ "dependencies" : dependencies ,
295+ }
296+
297+
298+ def compare_lock_payloads (
299+ left : dict [str , Any ], right : dict [str , Any ], return_diff : bool = False
300+ ) -> bool | tuple [bool , list [str ]]:
301+ """
302+ Compare two build lock payloads for functional equivalence.
303+ If return_diff is True, returns (is_equal, diff_list).
304+ """
305+ snap_left = get_functional_snapshot (left )
306+ snap_right = get_functional_snapshot (right )
307+
308+ if not return_diff :
309+ return snap_left == snap_right
310+
311+ diffs = []
312+
313+ def _diff_dict (a : dict , b : dict , path : str ):
314+ keys = set (a .keys ()) | set (b .keys ())
315+ for k in sorted (keys ):
316+ val_a = a .get (k )
317+ val_b = b .get (k )
318+ cur_path = f"{ path } .{ k } " if path else k
319+
320+ if val_a != val_b :
321+ if isinstance (val_a , dict ) and isinstance (val_b , dict ):
322+ _diff_dict (val_a , val_b , cur_path )
323+ else :
324+ diffs .append (f"{ cur_path } : { val_a } -> { val_b } " )
262325
263- return comparable (left ) == comparable (right )
326+ _diff_dict (snap_left , snap_right , "" )
327+ return (len (diffs ) == 0 , diffs )
264328
265329
266330def default_lock_path (workspace : Path ) -> Path :
0 commit comments