autoland: per-repo lander model/effort; fix runs use normal AFK settings #189
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab#189
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What to build
Two independent corrections to how autoland runs pick their model, effort, and provider.
1. Lander model/effort are not selectable
#181shippedlander_providerbut no model or effort knob.LaunchLander(internal/afk/lander.go:231) andLaunchEscalate(lander.go:312) both callResolveModelEffort(..., "", "")with empty requests, and because lander/escalate are not AFK kinds (isAFKKind,internal/instance/credential.go:90) the AFK override layers never apply — so a lander silently lands on the repo base column, then the globalspawn_model_default/spawn_effort_default. There is no way to say "validate with a cheaper model" or "validate at max effort" independently of the base spawn defaults.Add two nullable repo columns,
lander_modelandlander_effort, exactly mirroringlander_provider's semantics:layerSpawnDefault'sreq != ""arm already does; it is only a matter of feeding it.landerChainProvider(lander.go:181); a separate escalate knob is explicitly out of scope.Strictness cuts against the
#181provider precedent deliberately: a wronglander_modelshould fail loudly at launch, not degrade to whatever the base chain says while the operator believes their setting is in effect.Effort must validate against the resolved lander model's own effort list, not the provider union — the
#156rule already implemented insideResolveModelEffort. Resolving model and effort in one call preserves that for free.2. Fix runs bypass normal AFK settings
LaunchFix(internal/afk/fix.go:177-193) passes the persisted authoring run's model and effort as per-spawn requests, andfixProvider(fix.go:112) does the same for the provider viastore.AuthoringRunForBranch. Because a non-empty request short-circuitslayerSpawnDefault(credential.go:211), this deliberately bypasses the AFK override layers — the repo'sAFKModelDefault/AFKEffortDefaultand the globalspawn_model_default_afk/spawn_effort_default_afkare never consulted for a fix run, even thoughRunKindFixis already an AFK kind.That inheritance was an ADR-0048 decision and it is now reversed by operator directive: a fix run is a normal AFK run and must resolve model, effort, and provider through the normal AFK chain. Delete the inheritance rather than rewire it:
LaunchFixcallsResolveProvider(ctx, repo, RunKindFix, "")andResolveModelEffort(ctx, prov, repo, RunKindFix, "", "").fixProviderand its warn-fallback paths are removed. The paired warn-fallback on model/effort goes with them — with empty requests the skip-layer semantics already make a stale value fall through, so nothing needs to be caught.store.AuthoringRunForBranch(internal/store/afk.go:140) loses its only caller. Remove it and its tests unless a caller exists that this issue has missed; do not leave it dangling.Note the practical consequence and do not treat it as a bug: a branch authored by one provider can now be fixed by another, if that is what the repo's AFK chain resolves to. Fix runs adopt the existing branch detached and start fresh, so nothing provider-specific carries over from the authoring session.
Docs
ADR-0048states the inheritance rule as a decision; amend it in place with the reversal and the reason (operator wants one AFK settings surface, not two).CONTEXT.mdgets the two new settings if it names the autoland knobs. The#182acceptance line "fix runs inherit the authoring run's provider" is superseded.Acceptance criteria
lander_modelandlander_effortexist as nullable repo columns with a migration (both dialects) that leaves every existing repo inheriting as beforelander_modelorlander_effortfails the lander launch loudly rather than silently falling back#156per-model catalog rule holds)fixProviderandAuthoringRunForBranchare gone, with no dangling callersOut of scope
MaxEscalateAttempts(internal/afk/decide.go:345) per-repo — noted as a real asymmetry againstmax_fix_attempts, but a different issueResolveSpawnOptions(credential.go:333), which still returns an empty bag for validation-class kindsLander audit — PR #190
Verdict: PASS.
Verification signal relied on: Forgejo Actions
ci / native— success (6m40s, run 213). Not re-run. Its reach covers the Go build/test suite (sqlite backend),golangci-lint, and the full web chain (tsc, vitest, eslint, prettier, vite build).Two gaps in that reach, both stated rather than assumed:
0016is unexercised.internal/store/store_test.goskips the postgres arm offorEachBackendunlessLAB_TEST_POSTGRES_DSNis set, and thestore-postgresjob inci.ymlis still the commented-out M2 stub. Somigrations/postgres/0016_lander_model_effort.sqlhas never been executed. Low risk and consistent with how 0010–0015 all landed: the statement is a bareALTER TABLE ... ADD COLUMN TEXT, byte-identical to the sqlite dialect, as the file itself notes.ci-nixdid not run — correctly. It is paths-gated to**/*.nix,flake.lock,go.mod,go.sum, none of which this diff touches.Checked and clean:
main(no conflicts); one commit,b686586.Closes #189; head branchafk/189matches the AFK contract.0016does not collide —maintops out at0015, both dialects, lockstep-numbered.fixProviderandstore.AuthoringRunForBranchare gone with no dangling callers — a repo-wide grep finds the name only in ADR-0048's prose describing the reversal.LaunchFixand the producer's auth gate both resolve empty-request throughResolveProvider/ResolveModelEffortunderRunKindFix, and the fallback ladder is removed rather than rerouted.#182inheritance pins were rewritten to pin the opposite invariant rather than deleted —TestSpawnOnce_rejectSpawnsFixRunOnAFKChainkeeps a distinctive sonnet/high authoring row precisely to prove it has no influence, and the integration test keeps its seeded haiku/low for the same reason. That is the right shape for a reversal.RepoSettings.tsx) — not a new inconsistency.One non-blocking concern, recorded for a follow-up rather than this PR:
The new strict knobs interact with
#187's burn-on-spawn-intent rule.autolandLaunch(internal/afk/autoland.go:275) burns an attempt forfix/escalateon every outcome exceptErrOverCap— launch-pad failures included, by design. Alander_model/lander_effortthat does not resolve failsLaunchEscalateat the launch pad, so each retry burns an escalate attempt until the bound is spent and the PR goes permanently quiet. The realistic path there is not a typo but a laterlander_providerswap: a value valid for provider A silently becomes invalid when the lander chain moves to provider B, whose catalog lacks it. The autoland provider pre-check gates on provider resolution only, so it does not catch this.Mitigating, which is why this is a concern and not a blocker: the exhaustion is logged at
Errorlevel (spawn: escalation exhausted, autoland silent on pull), the same exposure already exists forlander_providerfrom#181, and the settings UI populates both selects from the live catalog of the lander's effective provider — so it is reachable by a provider swap or the API, not by ordinary use of the form. The deliberate no-write-time-validation decision (dynamic per-provider catalogs,#156/#157) is sound; the gap is that nothing re-validates a stored value when the chain beneath it moves.