Repository navigation
refactor(autoland): execute each step through a StepContext with injected services - #71
Merged
Merged
Conversation
…cted services ## Summary Phase 3 of the autoland step-abstraction stack. Execution moves out of `engine.execute_plan`'s `isinstance` chain and into the steps. Each step now runs against an explicit context and injected services. - **`AutolandStep.execute(self, sc: StepContext) -> StepResult`** is a new abstract method. `LandStep`, `WorkflowStep`, and `ConfirmStep` implement it with the logic of their old engine branches, unchanged. - **`Services`** (new `autoland/services.py`) is a frozen dataclass of everything a step may use: `github`, `ui` (`print`, `input` that raises `EOFError`, and markup `escape`), `waits` (approval, checks, merge queue, workflow), `repo` (`landed_sha`, `rebase_and_resubmit`), and `sleep`. `ui`, `waits`, and `repo` are `Protocol`s, so a fake is just a small class. - **`StepContext`** (new `steps/execution.py`) is a frozen dataclass with `index`, `total`, `run` (the `LandingContext`), `opts`, `common`, and `services`, plus these: - `checkpoint()`: saves the run. - `upcoming()`: the `StepView` descriptions of the steps that follow, for the confirm prompt's "Next steps". - `landed_sha()`: the code landed so far. - `ends_landed_prefix`: whether this step is the last one in the already-landed prefix. - `banner(title, style)`: the "Step i/n" heading. - **`StepResult`** is a frozen dataclass with `abort_reason` (empty means success) and `landed_sha`. It has `done()` and `abort()` constructors. - **The landed SHA now travels explicitly.** A land step reports the merge commit of the PR it landed in `StepResult.landed_sha`, and the engine records it on the run. A workflow step reads it from `sc.landed_sha()`. If nothing has landed yet, the engine looks up the target branch head and records it before the step waits. That is the same point at which the old code wrote it, so a Ctrl+C checkpoint taken during the wait still contains it. The already-landed prefix works through `ends_landed_prefix`: only the prefix's last step looks up its SHA, as before. - **The engine** keeps the plan walk, the checkpoint before each step, recording what steps report, and aborting. It has no step-type checks left. - **Native stack merging stays in the engine for now.** It lives in a small `_NativeStackMerge.before_step` hook, marked temporary. It runs before each step and does what the old land branch did: merge the run of land steps starting there, then fall back to landing one at a time for the rest of that run. It does not belong in `LandStep`, because steps must stay independent of each other. Phase 4's plan-optimization pass replaces it with a policy that groups land steps into one execution unit. - **Production services delegate at call time.** `Services.production()` resolves `runtime.console`, `gh.github`, `waits.wait_for_*`, `waits._refresh_last_landed_sha`, `waits.rebase_and_resubmit`, and `runtime.resilient_sleep` through their module attributes each time they are called. So every existing test patch, including all of the golden harness's seams, still intercepts what steps do. Phase 6 moves tests over to injected fakes, and then this delegation can go. - `execute_plan` gains a keyword-only `services` argument, which defaults to production. `commands.py` passes `Services.production()`. - `waits.wait_for_workflow` gains an optional `target_sha`, which defaults to the run's landed SHA as before. - `display._next_steps_lines` is gone. The confirm step now formats its own "Next steps" list from `sc.upcoming()`. No user-visible change. Console output, checkpoints, and status output are byte-for-byte identical. ## Test plan - [x] The golden autoland scenarios (`tests/test_autoland_golden.py`) pass with no golden files regenerated. - [x] New `tests/test_autoland_step_execution.py` runs each step type in isolation, with a hand-built `StepContext` and fake UI, waits, and repo, and no module patching: - land: the happy path with a rebase; the top of the stack, with no rebase; a PR that merges while waiting; a PR that was already merged; an already-landed step reporting its SHA only at the end of the prefix; aborts on approval, checks, and queue failures; and a failed rebase that still reports the landed SHA. - workflow: waits for the given SHA; failure; already done. - confirm: retries until `y`, with an escaped condition and the numbered "Next steps" (moved from the old `_next_steps_lines` tests); the final step; EOF aborts with today's message; already confirmed. - [x] Engine tests with injected services: a workflow before any land waits for the target branch head, and one after a land waits for the SHA that land reported; an abort reason is recorded and checkpointed. - [x] The full suite passes with an empty `GH_CONFIG_DIR` and `GH_TOKEN` unset. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> stack-info: PR: #71, branch: micahjsmith/stack/5
micahjsmith
force-pushed
the
micahjsmith/stack/5
branch
from
October 12, 2026 23:05
089bffd to
2497309
Compare
This was referenced Oct 12, 2026
micahjsmith
added this pull request to stack #77
October 12, 2026 23:22
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked PRs:
Summary
Phase 3 of the autoland step-abstraction stack. Execution moves out of
engine.execute_plan'sisinstancechain and into the steps. Each step now runs against an explicit context and injected services.AutolandStep.execute(self, sc: StepContext) -> StepResultis a new abstract method.LandStep,WorkflowStep, andConfirmStepimplement it with the logic of their old engine branches, unchanged.Services(newautoland/services.py) is a frozen dataclass of everything a step may use:github,ui(print,inputthat raisesEOFError, and markupescape),waits(approval, checks, merge queue, workflow),repo(landed_sha,rebase_and_resubmit), andsleep.ui,waits, andrepoareProtocols, so a fake is just a small class.StepContext(newsteps/execution.py) is a frozen dataclass withindex,total,run(theLandingContext),opts,common, andservices, plus these:checkpoint(): saves the run.upcoming(): theStepViewdescriptions of the steps that follow, for the confirm prompt's "Next steps".landed_sha(): the code landed so far.ends_landed_prefix: whether this step is the last one in the already-landed prefix.banner(title, style): the "Step i/n" heading.StepResultis a frozen dataclass withabort_reason(empty means success) andlanded_sha. It hasdone()andabort()constructors.StepResult.landed_sha, and the engine records it on the run. A workflow step reads it fromsc.landed_sha(). If nothing has landed yet, the engine looks up the target branch head and records it before the step waits. That is the same point at which the old code wrote it, so a Ctrl+C checkpoint taken during the wait still contains it. The already-landed prefix works throughends_landed_prefix: only the prefix's last step looks up its SHA, as before._NativeStackMerge.before_stephook, marked temporary. It runs before each step and does what the old land branch did: merge the run of land steps starting there, then fall back to landing one at a time for the rest of that run. It does not belong inLandStep, because steps must stay independent of each other. Phase 4's plan-optimization pass replaces it with a policy that groups land steps into one execution unit.Services.production()resolvesruntime.console,gh.github,waits.wait_for_*,waits._refresh_last_landed_sha,waits.rebase_and_resubmit, andruntime.resilient_sleepthrough their module attributes each time they are called. So every existing test patch, including all of the golden harness's seams, still intercepts what steps do. Phase 6 moves tests over to injected fakes, and then this delegation can go.execute_plangains a keyword-onlyservicesargument, which defaults to production.commands.pypassesServices.production().waits.wait_for_workflowgains an optionaltarget_sha, which defaults to the run's landed SHA as before.display._next_steps_linesis gone. The confirm step now formats its own "Next steps" list fromsc.upcoming().No user-visible change. Console output, checkpoints, and status output are byte-for-byte identical.
Test plan
tests/test_autoland_golden.py) pass with no golden files regenerated.tests/test_autoland_step_execution.pyruns each step type in isolation, with a hand-builtStepContextand fake UI, waits, and repo, and no module patching:y, with an escaped condition and the numbered "Next steps" (moved from the old_next_steps_linestests); the final step; EOF aborts with today's message; already confirmed.GH_CONFIG_DIRandGH_TOKENunset.🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 5.5 (1M context) noreply@anthropic.com