77import subprocess
88import sys
99import tempfile
10+ from concurrent .futures import ThreadPoolExecutor
1011from datetime import datetime , timezone
1112from pathlib import Path
1213from typing import Any
@@ -205,7 +206,17 @@ def current_head(repository: str, branch: str) -> str:
205206 return sha
206207
207208
208- def compare (repository : str , base : str , head : str ) -> dict [str , Any ]:
209+ def compare (
210+ repository : str ,
211+ base : str ,
212+ head : str ,
213+ patch_rules : list [str ] | None = None ,
214+ ) -> dict [str , Any ]:
215+ """Compare refs while materializing patches only for semantic-review paths.
216+
217+ Hard contract changes need only their filenames. Diffing every changed file was
218+ the dominant cost of the native Lab resolver and provided no extra evidence.
219+ """
209220 with tempfile .TemporaryDirectory (prefix = "niakvio-client-drift-" ) as tmp :
210221 work = Path (tmp )
211222 run_git (["init" , "--quiet" ], cwd = work )
@@ -244,8 +255,12 @@ def compare(repository: str, base: str, head: str) -> dict[str, Any]:
244255 timeout = 30 ,
245256 )
246257 files = [name .strip () for name in names .splitlines () if name .strip ()]
258+ if patch_rules :
259+ patch_files = [name for name in files if path_matches (name , patch_rules )][:250 ]
260+ else :
261+ patch_files = files [:250 ]
247262 patches : dict [str , str ] = {}
248- for filename in files [: 250 ] :
263+ for filename in patch_files :
249264 patch = run_git (
250265 [
251266 "diff" ,
@@ -315,7 +330,7 @@ def inspect_client(key: str, row: dict[str, Any], sources: dict[str, Any] | None
315330 if head == accepted_ref :
316331 return result
317332
318- comparison = compare (repository , accepted_ref , head )
333+ comparison = compare (repository , accepted_ref , head , semantic_rules )
319334 status = str (comparison .get ("status" ) or "unknown" )
320335 patches = comparison .get ("patches" ) or {}
321336 files = [
@@ -389,6 +404,8 @@ def apply_safe_state(
389404 advanced : list [str ] = []
390405
391406 for key , row in (config .get ("clients" ) or {}).items ():
407+ if key not in results :
408+ continue
392409 result = results [key ]
393410 contract_ref = str (row .get ("verified_ref" ) or "" )
394411 state = clients .get (key )
@@ -431,6 +448,11 @@ def main() -> int:
431448 "--output" ,
432449 default = str (ROOT / "health-output" / "nuvio-client-upstream-status.json" ),
433450 )
451+ parser .add_argument (
452+ "--clients" ,
453+ nargs = "+" ,
454+ help = "Inspect only these configured client ids. Omit for the full registry." ,
455+ )
434456 parser .add_argument (
435457 "--no-fail" ,
436458 action = "store_true" ,
@@ -460,12 +482,20 @@ def main() -> int:
460482 "Nuvio client upstream configuration invalid:\n - " + "\n - " .join (config_errors )
461483 )
462484
485+ all_clients = config .get ("clients" ) or {}
486+ requested = args .clients or list (all_clients )
487+ unknown = [key for key in requested if key not in all_clients ]
488+ if unknown :
489+ raise SystemExit ("unknown Nuvio client ids: " + ", " .join (unknown ))
490+ selected_items = [(key , all_clients [key ]) for key in requested ]
491+
463492 now = datetime .now (timezone .utc ).isoformat ()
464493 report : dict [str , Any ] = {
465- "schema_version" : 3 ,
494+ "schema_version" : 4 ,
466495 "generated_at" : now ,
467- "transport" : "git-ls-remote-plus-partial-tree-diff" ,
496+ "transport" : "parallel- git-ls-remote-plus-targeted -partial-tree-diff" ,
468497 "policy" : config .get ("policy" ) or {},
498+ "selected_clients" : requested ,
469499 "clients" : {},
470500 "review_required" : [],
471501 "safe_advance_available" : [],
@@ -474,12 +504,11 @@ def main() -> int:
474504 "inconclusive" : [],
475505 }
476506
477- failures : list [str ] = []
478- for key , row in (config .get ("clients" ) or {}).items ():
507+ def inspect_one (key : str , row : dict [str , Any ]) -> dict [str , Any ]:
479508 try :
480- result = resilient_inspect_client (str (key ), row , sources )
509+ return resilient_inspect_client (str (key ), row , sources )
481510 except Exception as error :
482- result = {
511+ return {
483512 "id" : key ,
484513 "repository" : row .get ("repository" ),
485514 "branch" : row .get ("branch" ),
@@ -488,6 +517,15 @@ def main() -> int:
488517 "review_required" : True ,
489518 "error" : f"{ type (error ).__name__ } : { error } " ,
490519 }
520+
521+ worker_count = max (1 , min (3 , len (selected_items )))
522+ with ThreadPoolExecutor (max_workers = worker_count , thread_name_prefix = "nuvio-drift" ) as pool :
523+ futures = {key : pool .submit (inspect_one , key , row ) for key , row in selected_items }
524+ inspected = {key : futures [key ].result () for key , _row in selected_items }
525+
526+ failures : list [str ] = []
527+ for key , _row in selected_items :
528+ result = inspected [key ]
491529 report ["clients" ][key ] = result
492530 status = result .get ("status" )
493531 if status == "verified" :
@@ -529,7 +567,8 @@ def main() -> int:
529567
530568 if args .apply_safe_advance and not failures and not report ["inconclusive" ]:
531569 advanced = apply_safe_state (sources , config , report ["clients" ], now )
532- if advanced or "nuvio_client_compatibility" not in load (sources_path ):
570+ previous_sources = load (sources_path ) if sources_path .is_file () else {}
571+ if advanced or "nuvio_client_compatibility" not in previous_sources :
533572 dump (sources_path , sources )
534573 report ["auto_advanced" ] = advanced
535574 for key in advanced :
@@ -555,6 +594,7 @@ def main() -> int:
555594
556595 print (
557596 "Nuvio client upstream drift check: "
597+ f"selected={ ',' .join (requested ) or '-' } ; "
558598 f"verified={ ',' .join (report ['verified' ]) or '-' } ; "
559599 f"safe={ ',' .join (report ['safe_advance_available' ]) or '-' } ; "
560600 f"auto_advanced={ ',' .join (report ['auto_advanced' ]) or '-' } ; "
0 commit comments