MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-3485

(motir-core) COLLAPSE the CI-RUNNER supervisor — `system.ci-runner-boot` converges onto `ciRunnerBootService.runIntent`, and the second composition goes

Done
Description

Collapse system.ci-runner-boot's stepped supervision the same way the index collapse collapses the fleet's — but in the CI runner's own files, which it shares with nothing.

What is there today

lib/jobs/definitions/ciRunnerFleet.ts's ciRunnerBoot handler drives ciRunnerBootService as durable steps: boot-runnersupervise-wait:<n> / supervise-poll:<n> up to FLEET_TIME_BUDGETS.maxPollIterations (2_000) → settle-runner. Its header explains the shape from maxDuration = 300 and MOTIR-2007, the incident where an hour-long supervision inside one invocation meant every CI job over ~5 minutes had its supervisor killed with no teardown.

And ciRunnerBootService.runIntent is the same loop without the step idsboot → poll-loop → settle, with a plain sleep. bootIntent / pollOnce / settleSupervision were carved out of it by MOTIR-2007, and the file still says so at each one ("This is the old runIntent up to and including recordBoot", "what used to be runIntent's finally, as an ordinary step"). The service therefore already carries the collapsed composition; only the reason to prefer the stepped one has gone.

The change

Mirror the index card's move in these files: give runIntent the same optional step seam, have the job pass ctx.step into it, and delete the handler's own loop. Keep memoized exactly what the restart decision says to keep — the boot and the teardown are the operations with external effects; the interval and the poll are not.

CORRECT the comments, do not delete them. Four blocks in the handler reason from the invocation ceiling: "⚠️ THE DURABLE POLL LOOP (MOTIR-2007)", "Free waiting: Inngest schedules the resume", "⚠️ AND IT RETIRES MOTIR-2002's MEMO", and "⚠️ TEARDOWN IS REACHED ON EVERY PATH OUT OF THE LOOP". Two of them state properties that must SURVIVE the collapse and now hold for a different reason — the boot executing once per run (memoization, unchanged) and teardown being reached on every exit path (an ordinary finally, which is what a long-lived process makes trustworthy again). Say that. A reader must be able to see that the guarantee stayed and its mechanism changed.

⚠️ Two properties specific to THIS job, which the index card does not have to think about

  • retryPolicy: 'none' — ONE attempt, and it is a correctness decision. The handler's own comment says a retry would re-enter from the top and that the failures worth retrying come back through the next sweep instead. On the engine a LEASE RECLAIM refunds the attempt (lib/jobs/engine/worker.ts: "a reclaim and a drain both refund the attempt"), so a worker restart does not consume this job's single attempt — verify that against the worker rather than assuming it, because if it did, a deploy mid-CI-job would dead-letter a run that was fine, and this is the one job in the fleet where that budget is exactly one.
  • admit's atomic pending → provisioning claim is what stops a second container, NOT the retry policy — the handler says so. The collapse must not move or weaken that claim; it lives in the service, inside bootIntent.

Scope boundary

ENDS at: one composition in ciRunnerBootService, driven by the job through the step seam, with pnpm test green.

Does NOT change any observable behaviourpollWaitMs, every value in FLEET_TIME_BUDGETS, the typed bootIntent outcomes, the reap-after window and the failure details are unchanged.

Does NOT touch the two CRON jobs in the same file. system.ci-runner-provision-sweep and system.ci-runner-reap belong to MOTIR-3416, which counts them among its fourteen. This card edits ciRunnerBoot and nothing else in that file — with ONE exception it does not own either: the sweep's inngest.send(ciRunnerBootEvent(intentId)) at line 152 is MOTIR-3456's single line, already claimed there. Leave it alone.

Does NOT touch lib/ciFleet/limits.ts or the admission gateMOTIR-3417 forbids it.

Does NOT touch the index fleetlib/jobs/indexFleetSteps.ts and lib/services/codeGraphIndexDispatchService.ts are the sibling card's, and the two share no file, which is why neither blocks the other.

HANDS OFF the DOCUMENTS to the docs card, exactly as the index card does.

Acceptance criteria

  • ciRunnerBoot's handler contains no poll loop; git grep -n "for (let iteration" lib/jobs/definitions/ciRunnerFleet.ts lib/services/ciRunnerBootService.ts finds it in exactly one place.
  • The number of job_step rows a completed CI supervision writes is a small CONSTANT, independent of how many times it polled — asserted by a test driving a supervised run at millisecond budgets.
  • Teardown is still reached on EVERY path out of the loop — a done verdict, the iteration ceiling, and a throw from inside the loop — asserted by a test per path, since the mechanism changes from a step reachable on both exits to an ordinary finally and the guarantee must be re-proven, not inherited.
  • A worker restart mid-supervision resumes supervising the SAME container rather than booting a second one, and does not consume the job's single attempt — asserted against the worker's real reclaim path.
  • admit's pending → provisioning claim is unchanged, and a second boot for one intent still loses that claim.
  • FLEET_TIME_BUDGETS is byte-identical, asserted against the shipped values.
  • git diff shows no change to system.ci-runner-provision-sweep, system.ci-runner-reap, CI_RUNNER_PROVISION_SWEEP_CRON, CI_RUNNER_REAP_CRON, or the inngest.send at ciRunnerFleet.ts:152.
  • git diff --stat shows no change under lib/ciFleet/limits.ts.

Context refs

  • lib/jobs/definitions/ciRunnerFleet.tsciRunnerBoot, its four maxDuration comment blocks, and the two cron jobs it must not touch
  • lib/services/ciRunnerBootService.tsrunIntent, bootIntent, pollOnce, settleSupervision, FLEET_TIME_BUDGETS, pollWaitMs, and the "this is the old runIntent" markers
  • lib/ciFleet/bootDispatch.ts — the three senders of the boot event, unchanged here
  • lib/jobs/engine/worker.ts — the lease reclaim, and the attempt refund this card depends on
  • tests/ciFleet/ciRunnerAdmissionWake.test.ts — the existing caller of runIntent, which must keep working through the new seam with no argument
  • docs/decisions/ci-runner-fleet.md — the fleet's own record
  • MOTIR-2007 — the incident that produced the stepped shape, and the premise this reverses