---
id: w2/m10/t002
title: Cancel verb — kill the in-flight build Job, deploy → canceled; 409 past cancelable states
worker: worker2
status: todo
estimate: 30m
depends_on: [w2/m10/t001]
---

## Objective

`deploys.Service.Cancel(ctx, serviceID, deployID)`: authorize, then cancel a deploy that is still cancelable — per Render's semantics a deploy in `build_in_progress` (and pre-build queued states) cancels; once `update_in_progress`/terminal, return the OpenAPI-specified 4xx (verify the exact code/body against `render-public-api-1.json` before coding). Canceling deletes the in-flight BuildKit Job (foreground propagation) and transitions the deploy record to `canceled`; the running service is untouched (the previous revision keeps serving).

## Context

- Build Jobs: `lego/operator/internal/build/` shapes them; bex-api can delete the Job by its deterministic name/labels — check what m5's trigger path knows about the Job identity and reuse it (don't invent a second naming scheme).
- Race: build completes while cancel is in flight — the transition must be compare-and-swap on status (canceled only from cancelable states), never clobbering `live`.

## Steps

1. Verify Render's cancel status-code/body semantics from the spec; record in the task notes.
2. Implement the verb: CAS status transition + Job deletion; idempotent second cancel (already-canceled → the spec's response).
3. Ensure the operator/build watcher doesn't resurrect or mark the canceled deploy failed afterwards (terminal means terminal).

## Files

- `lego/backend/internal/deploys/service.go`
- `lego/backend/internal/deploys/` (job identity helper if extraction needed)

## Acceptance criteria

- [ ] Cancel during build: Job gone, deploy `canceled`, service still serving the prior revision.
- [ ] Cancel after `update_in_progress`/terminal: spec-matching 4xx; record unchanged; double-cancel idempotent.

## Out of scope

- Surfaces (t004); canceling the k8s rollout itself (post-build cancel is out, matching Render).
