@@ -45,6 +45,46 @@ def status(self) -> str:
4545 return "completed"
4646
4747
48+ @dataclass (frozen = True )
49+ class RuntimePreflightResult :
50+ repo_path : Path
51+ out_dir : Path
52+ profile : AppRuntimeProfile
53+ preflight : dict [str , Any ]
54+ elapsed_s : float
55+
56+
57+ def run_runtime_preflight (
58+ * ,
59+ repo_path : Path ,
60+ profile_path : Path | None = None ,
61+ results_dir : Path | None = None ,
62+ execute_app : bool = False ,
63+ allow_inferred_runtime : bool = False ,
64+ provider : str | None = None ,
65+ model : str | None = None ,
66+ agent_env : dict [str , str ] | None = None ,
67+ pi_config_dir : Path | None = None ,
68+ sandbox_mode : str | None = None ,
69+ event_sink : EventSinkLike = NULL_EVENT_SINK ,
70+ ) -> RuntimePreflightResult :
71+ """Run runtime setup checks without requiring static triage artifacts."""
72+ return _run_runtime_preflight (
73+ repo_path = repo_path ,
74+ profile_path = profile_path ,
75+ results_dir = results_dir ,
76+ execute_app = execute_app ,
77+ allow_inferred_runtime = allow_inferred_runtime ,
78+ provider = provider ,
79+ model = model ,
80+ agent_env = agent_env ,
81+ pi_config_dir = pi_config_dir ,
82+ sandbox_mode = sandbox_mode ,
83+ event_sink = event_sink ,
84+ emit_events = True ,
85+ )
86+
87+
4888def run_verify_repo (
4989 * ,
5090 triage_path : Path ,
@@ -59,14 +99,17 @@ def run_verify_repo(
5999 agent_env : dict [str , str ] | None = None ,
60100 pi_config_dir : Path | None = None ,
61101 sandbox_mode : str | None = None ,
102+ preflight_result : RuntimePreflightResult | None = None ,
62103 event_sink : EventSinkLike = NULL_EVENT_SINK ,
63104) -> RuntimeVerifyResult :
64105 start = time .time ()
65106 triage_file = _resolve_triage_file (triage_path )
66107 repo = (repo_path or _repo_from_summary (triage_file ) or Path .cwd ()).expanduser ().resolve ()
67- profile = load_runtime_profile ( repo , profile_path )
68- out_dir = results_dir or triage_file .parent / "runtime"
108+ out_dir = results_dir or ( preflight_result . out_dir if preflight_result else None )
109+ out_dir = out_dir or triage_file .parent / "runtime"
69110 out_dir .mkdir (parents = True , exist_ok = True )
111+ if preflight_result is not None :
112+ execute_app = bool (preflight_result .preflight .get ("execute_app" , execute_app ))
70113 event_sink .emit (
71114 "runtime_verification_started" ,
72115 {
@@ -77,52 +120,24 @@ def run_verify_repo(
77120 },
78121 stage = "runtime" ,
79122 )
80- preflight = _environment_preflight (
81- profile ,
82- execute_app = execute_app ,
83- allow_inferred_runtime = allow_inferred_runtime ,
84- provider = provider ,
85- model = model ,
86- sandbox_mode = sandbox_mode ,
87- )
88- if execute_app and not preflight [ "ready" ]:
89- _write_setup_guide ( out_dir / "RUNTIME-SETUP.md" , profile , preflight )
90- event_sink . emit (
91- "artifact_written" ,
92- artifact_data ( out_dir / "RUNTIME-SETUP.md" , artifact_type = "runtime_setup" ) ,
93- stage = "runtime" ,
123+ if preflight_result is None :
124+ preflight_result = _run_runtime_preflight (
125+ repo_path = repo ,
126+ profile_path = profile_path ,
127+ results_dir = out_dir ,
128+ execute_app = execute_app ,
129+ allow_inferred_runtime = allow_inferred_runtime ,
130+ provider = provider ,
131+ model = model ,
132+ agent_env = agent_env ,
133+ pi_config_dir = pi_config_dir ,
134+ sandbox_mode = sandbox_mode ,
135+ event_sink = event_sink ,
136+ emit_events = False ,
94137 )
95- _write_runtime_artifact (
96- out_dir / "environment-summary.json" ,
97- preflight ,
98- event_sink = event_sink ,
99- stage = "runtime" ,
100- artifact_type = "runtime_environment_summary" ,
101- )
102-
103- agent_config = {}
104- if execute_app and provider and model :
105- agent_config = {
106- "runner" : "pi" ,
107- "provider" : provider ,
108- "model" : model ,
109- "pi_config_dir" : str (pi_config_dir ) if pi_config_dir else None ,
110- "auth_env_keys" : sorted ((agent_env or {}).keys ()),
111- }
112- environment = dict (profile .environment )
113- if preflight ["sandbox_backend" ] in {"docker" , "local" }:
114- environment ["sandbox" ] = preflight ["sandbox_backend" ]
115- profile = replace (
116- profile ,
117- environment = environment ,
118- verification = {
119- ** profile .verification ,
120- "execute_app" : execute_app ,
121- "allow_inferred_runtime" : allow_inferred_runtime ,
122- "preflight" : preflight ,
123- "agent" : {** dict (profile .verification .get ("agent" ) or {}), ** agent_config },
124- },
125- )
138+ profile = preflight_result .profile
139+ preflight = preflight_result .preflight
140+ repo = preflight_result .repo_path
126141 findings = _load_merged_findings (triage_file )
127142 if finding_ids :
128143 wanted = set (finding_ids )
@@ -211,6 +226,141 @@ def run_verify_repo(
211226 )
212227
213228
229+ def _run_runtime_preflight (
230+ * ,
231+ repo_path : Path ,
232+ profile_path : Path | None ,
233+ results_dir : Path | None ,
234+ execute_app : bool ,
235+ allow_inferred_runtime : bool ,
236+ provider : str | None ,
237+ model : str | None ,
238+ agent_env : dict [str , str ] | None ,
239+ pi_config_dir : Path | None ,
240+ sandbox_mode : str | None ,
241+ event_sink : EventSinkLike ,
242+ emit_events : bool ,
243+ ) -> RuntimePreflightResult :
244+ start = time .time ()
245+ repo = Path (repo_path ).expanduser ().resolve ()
246+ profile = load_runtime_profile (repo , profile_path )
247+ out_dir = results_dir or default_results_dir (repo )
248+ out_dir .mkdir (parents = True , exist_ok = True )
249+ if emit_events :
250+ event_sink .emit (
251+ "runtime_preflight_started" ,
252+ {
253+ "repo" : str (repo ),
254+ "results_dir" : str (out_dir ),
255+ "execute_app" : execute_app ,
256+ },
257+ stage = "runtime" ,
258+ )
259+ preflight = _environment_preflight (
260+ profile ,
261+ execute_app = execute_app ,
262+ allow_inferred_runtime = allow_inferred_runtime ,
263+ provider = provider ,
264+ model = model ,
265+ sandbox_mode = sandbox_mode ,
266+ )
267+ profile = _profile_with_runtime_context (
268+ profile ,
269+ preflight ,
270+ execute_app = execute_app ,
271+ allow_inferred_runtime = allow_inferred_runtime ,
272+ provider = provider ,
273+ model = model ,
274+ agent_env = agent_env ,
275+ pi_config_dir = pi_config_dir ,
276+ )
277+ _write_runtime_preflight_artifacts (
278+ out_dir ,
279+ profile ,
280+ preflight ,
281+ execute_app = execute_app ,
282+ event_sink = event_sink ,
283+ )
284+ elapsed = time .time () - start
285+ if emit_events :
286+ event_sink .emit (
287+ "runtime_preflight_finished" ,
288+ {
289+ "ready" : preflight ["ready" ],
290+ "issues" : len (preflight ["issues" ]),
291+ "elapsed_s" : elapsed ,
292+ },
293+ stage = "runtime" ,
294+ )
295+ return RuntimePreflightResult (
296+ repo_path = repo ,
297+ out_dir = out_dir ,
298+ profile = profile ,
299+ preflight = preflight ,
300+ elapsed_s = elapsed ,
301+ )
302+
303+
304+ def _profile_with_runtime_context (
305+ profile : AppRuntimeProfile ,
306+ preflight : dict [str , Any ],
307+ * ,
308+ execute_app : bool ,
309+ allow_inferred_runtime : bool ,
310+ provider : str | None ,
311+ model : str | None ,
312+ agent_env : dict [str , str ] | None ,
313+ pi_config_dir : Path | None ,
314+ ) -> AppRuntimeProfile :
315+ agent_config = {}
316+ if execute_app and provider and model :
317+ agent_config = {
318+ "runner" : "pi" ,
319+ "provider" : provider ,
320+ "model" : model ,
321+ "pi_config_dir" : str (pi_config_dir ) if pi_config_dir else None ,
322+ "auth_env_keys" : sorted ((agent_env or {}).keys ()),
323+ }
324+ environment = dict (profile .environment )
325+ if preflight ["sandbox_backend" ] in {"docker" , "local" }:
326+ environment ["sandbox" ] = preflight ["sandbox_backend" ]
327+ return replace (
328+ profile ,
329+ environment = environment ,
330+ verification = {
331+ ** profile .verification ,
332+ "execute_app" : execute_app ,
333+ "allow_inferred_runtime" : allow_inferred_runtime ,
334+ "preflight" : preflight ,
335+ "agent" : {** dict (profile .verification .get ("agent" ) or {}), ** agent_config },
336+ },
337+ )
338+
339+
340+ def _write_runtime_preflight_artifacts (
341+ out_dir : Path ,
342+ profile : AppRuntimeProfile ,
343+ preflight : dict [str , Any ],
344+ * ,
345+ execute_app : bool ,
346+ event_sink : EventSinkLike ,
347+ ) -> None :
348+ if execute_app and not preflight ["ready" ]:
349+ _write_setup_guide (out_dir / "RUNTIME-SETUP.md" , profile , preflight )
350+ event_sink .emit (
351+ "artifact_written" ,
352+ artifact_data (out_dir / "RUNTIME-SETUP.md" , artifact_type = "runtime_setup" ),
353+ stage = "runtime" ,
354+ )
355+ _write_runtime_artifact (
356+ out_dir / "environment-summary.json" ,
357+ preflight ,
358+ event_sink = event_sink ,
359+ stage = "runtime" ,
360+ artifact_type = "runtime_environment_summary" ,
361+ )
362+
363+
214364def _write_runtime_artifact (
215365 path : Path ,
216366 data : Any ,
0 commit comments