What to build
A new HTTP endpoint POST /api/internal/stop/phase/{phaseName} that requests graceful shutdown of a running phase. The phase finishes its current chunk / record batch, then stops; no half-written chunks, no abrupt executor interrupts.
The pattern to follow already exists in ItemEquipmentContinuousLoop: an AtomicBoolean shutdown flag, a ReentrantLock + condition for the lock-protected state machine, and an AtomicBoolean started/running/shutdown triplet. Apply the same shape to each phase-specific executor (or to a shared component if the existing scheduler executor is reused).
RunStatusTracker gets a new terminal phase STOPPED. When a phase run ends because of a stop request (not natural completion / failure), the tracker transitions to STOPPED instead of COMPLETED. The existing isTerminal predicate already covers this since STOPPED is a third terminal value.
The stop endpoint accepts X-Airflow-Run-Id for correlation but is best-effort: a stop requested for a phase that isn't running returns 200 with "status": "NOT_RUNNING" and the currently-active runId (if any) so Airflow can correlate.
Acceptance criteria
Blocked by
What to build
A new HTTP endpoint
POST /api/internal/stop/phase/{phaseName}that requests graceful shutdown of a running phase. The phase finishes its current chunk / record batch, then stops; no half-written chunks, no abrupt executor interrupts.The pattern to follow already exists in
ItemEquipmentContinuousLoop: anAtomicBooleanshutdown flag, aReentrantLock+ condition for the lock-protected state machine, and anAtomicBoolean started/running/shutdowntriplet. Apply the same shape to each phase-specific executor (or to a shared component if the existing scheduler executor is reused).RunStatusTrackergets a new terminal phaseSTOPPED. When a phase run ends because of a stop request (not natural completion / failure), the tracker transitions toSTOPPEDinstead ofCOMPLETED. The existingisTerminalpredicate already covers this sinceSTOPPEDis a third terminal value.The stop endpoint accepts
X-Airflow-Run-Idfor correlation but is best-effort: a stop requested for a phase that isn't running returns 200 with"status": "NOT_RUNNING"and the currently-active runId (if any) so Airflow can correlate.Acceptance criteria
POST /api/internal/stop/phase/ITEM_EQUIPMENThalts a running ITEM_EQUIPMENT loop within one chunk boundary (≤ 30s)phase=STOPPEDin/run-statusand counts as terminal"status": "NOT_RUNNING"(not 404 / 409)Blocked by