104 """Create the round-job row and staging branch; return the job id."""
106 func =
"create_close_job"
109 ctx.registry.round_open(
110 round_no=ctx.round_no,
111 orchestrator_slot=ctx.actor_slot,
112 actor_slot=ctx.actor_slot,
113 actor_type=ctx.actor_type,
115 if create_staging
and not ctx.git.branch_exists(ctx.staging_branch):
116 ctx.git.create_branch(ctx.staging_branch, ctx.base_ref)
118 ctx.git.rev_parse(ctx.staging_branch)
119 if ctx.git.branch_exists(ctx.staging_branch)
122 ctx.registry.backend.create_round_job(
124 round_no=ctx.round_no,
125 created_by=ctx.actor_slot,
126 staging_branch_head_sha=head_sha,
128 _dbg(
"OC_CHAT.MAIN", func, f
"job {ctx.job_id} head={head_sha}", 2)
162 interactive: bool =
False,
163 prompter: Prompter |
None =
None,
164) -> RoundCloseResult:
165 """Execute the step plan from ``start_step`` onward, persisting state."""
168 backend = ctx.registry.backend
170 existing = {int(row[
"step_no"]): row
for row
in backend.list_round_job_steps(ctx.job_id)}
171 completed: list[str] = []
173 prior = existing.get(step.step_no)
174 if step.step_no < start_step
or (prior
and str(prior[
"status"]) ==
"done"):
175 if prior
and str(prior[
"status"]) ==
"done":
176 completed.append(step.step_name)
178 if interactive
and prompter
is not None:
179 decision = prompter(step, ctx)
180 if decision ==
"abort":
181 ex.run_abort(ctx, reason=
"operator abort at phase prompt")
182 backend.set_round_job_status(
183 ctx.job_id, status=
"rolled_back", current_step=step.step_name
185 _dbg(
"OC_CHAT.MAIN", func,
"operator aborted", 2)
188 if result
is not None:
190 completed.append(step.step_name)
191 backend.set_round_job_status(
192 ctx.job_id, status=
"done", current_step=TERMINAL_PHASE
194 _dbg(
"OC_CHAT.MAIN", func, f
"round {ctx.round_no} closed", 2)
201 prior: dict[str, object] |
None,
202) -> RoundCloseResult |
None:
203 func =
"_run_one_step"
204 backend = ctx.registry.backend
205 now = ctx.registry.clock.now_wall()
206 backend.upsert_round_job_step(
208 step_no=step.step_no,
209 step_name=step.step_name,
213 backend.set_round_job_status(
214 ctx.job_id, status=
"running", current_step=step.step_name
216 prior_hash =
None if prior
is None else _optional_text(prior.get(
"input_hash"))
219 except RoundEscalation
as esc:
220 backend.upsert_round_job_step(
222 step_no=step.step_no,
223 step_name=step.step_name,
225 error_summary=esc.escalation.message,
226 finished_at=ctx.registry.clock.now_wall(),
228 backend.set_round_job_status(
229 ctx.job_id, status=
"failed", current_step=step.step_name
231 _dbg(
"OC_CHAT.MAIN", func, f
"escalation {esc.escalation.code.value}", 2)
236 escalation=esc.escalation,
239 json.dumps(list(outcome.artifact_paths))
if outcome.artifact_paths
else None
241 backend.upsert_round_job_step(
243 step_no=step.step_no,
244 step_name=step.step_name,
246 input_hash=outcome.input_hash,
247 post_step_head_sha=outcome.post_step_head_sha,
248 result_summary=outcome.result_summary,
249 artifact_paths_json=artifacts_json,
250 finished_at=ctx.registry.clock.now_wall(),
252 if outcome.post_step_head_sha:
253 backend.set_round_job_head_sha(ctx.job_id, outcome.post_step_head_sha)
254 _dbg(
"OC_CHAT.MAIN", func, f
"step {step.step_name} {outcome.status}", 3)