fix(shiftengine): tell the board when a Shift finishes #31
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/tracker-writeback-and-role-workloads"
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?
Found while triaging a production end-to-end run on 2026-07-30. The factory worked: webhook → routing → Shift → KEDA → worker → OpenHands → PR #30 opened → outcome ingested. Then it told nobody.
The reported bug
Engine.closegated the tracker write-back onnext == StateNeedsHuman. A plan-less team'spr_openedsettlesStateDone, so the successful path — the one that matters — skippednotifyHumanentirely. No error surfaced because no call was made. Ticket 580 sat atdone=falsewith zero comments while its pull request was open.Underneath it,
notifyHumancalledSetStatus(..., StateNeedsHuman)while the Vikunja provider early-returns on anything that is notStateDone. The status write has never done anything on any path.What changed
work.Terminal()gates the write-back.queuedis the only settle result that is not terminal; announcing a stopped Shift mid-retry would be a lie.SettleItemreturns the state it wrote.queuedcoerces tostaleat the attempt cap and the caller could not tell "failed, retrying" from "failed, gave up". The audit row records the effective state now too — it used to claimwork_item.queuedfor a row that went stale.CloseShiftreports whether it won the CAS. Both racers still settle the item (crash repair); only the winner comments, so the outcome fast-path and the sweeper stop double-posting.notifyHuman→notifyTracker, with wording per terminal state extracted to a puretrackerMessage()that is table-testable without Postgres.AssigneeTeams()ranged a Go map, so the same config routed the same person to a different team on different boots — measured 168/200 vs 32/200 in one process.forgebroker.Staticreturns the shared token in the same field;cred.IDwas already the truth predicate, now surfaced asclaimResponse.forgeTokenPerRun. Also fixes contract drift —run-api.v1.schema.jsondeclaresadditionalProperties: falseand never listedforgeTokenat all.resolveTargetlogs success, not only failure. The routing decision used to appear a hop later in the worker, after a pod had been scheduled.default(Kyvernorequire-non-default-serviceaccount); they now get<fullname>-worker, deliberately not ploegd's account. Resources were executor-wide, so every reader in a fan-out Round booked a whole writer-sized pod — three of those do not fit a one-node worker pool.Ploeg still never closes a task
This deployment's Definition of Done is "in production, monitored, first telemetry observed". Ploeg opens a pull request and stops, so it is never in a position to know an item is finished, and the comment it posts literally asks a person to merge. The status write stays
needs_human— now a documented rule rather than an unreachable accident.Verification
TestPublish_DoneOutcomeStillNotifiesTheTrackeris the incident as a test and fails against v0.2.0-rc.12 — confirmed by reverting the gate and watching it go red, then green again. Same both-ways check on the duplicate-assignee rejection.Chart change verified by rendering:
builderkeeps the executor default (1 / 1Gi),reviewertakes its override (500m / 384Mi), both onsa=ploeg-worker. Goldens are additions only and theprivileged-dindlabel count is unchanged.Local CI gates all green:
gofmt,go vet,go build,go test ./...,helm lint,./scripts/helm-golden.sh check.Not in this PR
Per-ticket route selection by tracker label and
PLOEG_TARGET_STRICTare held for rc.14. ADR-0015 requires a hint to select among pre-registered routes rather than construct a target — tracker text is untrusted input, and a hint that constructs a target is an arbitrary-repository write primitive. That needs the OpenSpec/ADR round trip rather than being smuggled into a fix PR.No
VIK-trailer on these commits — there is no board ticket for this work yet.🤖 Generated with Claude Code