Repository navigation
fix(scheduler): apply the heartbeat receipt fence to Python follow-ups - #8606
Draft
GZY-SUPER-HACKER wants to merge 1 commit into
Draft
GZY-SUPER-HACKER wants to merge 1 commit into
GZY-SUPER-HACKER wants to merge 1 commit into
Conversation
A Turn id already bound the follow-up to one heartbeat receipt, but the compatibility transport sent turn_instance_id=null and require_heartbeat_receipt=false to the native transaction. The Python route therefore rejected a missing receipt and accepted a superseded one, while the generated route rejects both. Forward the Turn id when the caller has one, so the native transaction owns the missing/superseded decision for both routes. Callers without a Turn (the in-process API) keep the receipt-less compatibility request. Refs loopx-project#8605 Signed-off-by: GZY-SUPER-HACKER <162807803+GZY-SUPER-HACKER@users.noreply.github.com>
9 of 18 tasks
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Goal And Delivered Outcome
Outcome basis / optional anchor: Related to [Bug]: the Python scheduler follow-up route accepts a heartbeat receipt a newer Turn superseded #8605 (the reproduced defect opened in parallel with this draft; the issue carries the same reproduction and the alternative fix shape). It does not close the broader compatibility-contract question in [Task]: Decide the support contract for receipt-less scheduler ACK/failure before retiring the Python facade #7876.
Goal/source and gap: a scheduler follow-up that carries a Turn id is receipt-bound by contract. The native transaction rejects both a missing and a superseded heartbeat receipt (
loopx/control_plane/scheduler/heartbeat_followup.ts:338-356; the stale case is re-checked under the state lock inloopx/control_plane/scheduler/heartbeat_commit.ts:827-849). The Python compatibility route implemented only the first half:build_scheduler_followup_payloadrefused when the receipt was absent, then calledrecord_quota_scheduler_ack, whose commit transport hard-codedturn_instance_id: nullandrequire_heartbeat_receipt: false(loopx/control_plane/quota/scheduler_ack.py:193-194). The fence therefore never ran on that route.Observable before → after, with the validation row that proves it: with a Turn whose receipt a later
quota should-runsuperseded,quota scheduler-ack-current --turn-instance-id <old> --executeused to returnrc 0,ok: true,scheduler_state_mutated: trueand rewritelast_applied_rrule. It now returnsrc 1,status: heartbeat_receipt_stale,state: blocked_receipt,scheduler_state_mutated: false, and leaves the scheduler state file absent (validation rows 1–2).Issue/task and intended base: Related to [Bug]: the Python scheduler follow-up route accepts a heartbeat receipt a newer Turn superseded #8605; intended base
main.Author Declaration
Implemented against
Specification and revision: the receipt contract already shipped in the current tree —
loopx/control_plane/scheduler/heartbeat_followup.ts(heartbeatReceiptStatusgate and the structuredreceiptFailurepayload) and its pinned teststests/control_plane_ts/scheduler_heartbeat_followup.test.ts:184-255(missing or superseded heartbeat receipt rejects before scheduler mutation, and the lock-recheck case). No new invariant is introduced; the change makes the Python route delegate to that existing owner.Criteria:
loopx/cli_commands/quota_scheduler_followup.pytests/control_plane/test_scheduler_stale_receipt_gate.pycontrol in the issue reproduction_run_native_followupnow forwardsturn_instance_idand setsrequire_heartbeat_receipttests/control_plane/test_scheduler_stale_receipt_gate.py::test_superseded_receipt_cannot_commit_a_scheduler_ackreceiptFailurereturns before the commitscheduler_state_mutated: false, state file absent)tests/control_plane/test_scheduler_stale_receipt_gate.py::test_fresh_receipt_still_commits_the_ack_run_native_followup(turn_instance_id=None)defaultrecord_quota_scheduler_ack(...)path is unchangedmainbefore changing anything. The fix does not add a second copy of the staleness rule: it hands the Turn to the transaction that already owns it, and keeps the Python absence check because it prevents rebuilding authority from the live frontier. I verified the counter-experiment (product files reverted, tests kept): the superseded test fails with the oldok: trueresult while the fresh-receipt twin still passes.Scope And Continuation
Completed scope and remaining work: complete within this scope for
scheduler-ack,scheduler-ack-currentandscheduler-fail-current, which share the transport. The broader question — whether receipt-less manual commands should remain supported at all, and whether the compatibility facade should be retired — stays with [Task]: Decide the support contract for receipt-less scheduler ACK/failure before retiring the Python facade #7876 and is not decided here.Unrelated but visible: the Python side still performs its own existence check before rebuilding the decision. That duplicates the existence half of the fence, but it fails earlier with the wording the issue's control case relies on; removing it would change the missing-receipt payload, so it is left alone.
Slice boundary / successor: independently testable and reversible — one optional argument threaded through four call sites plus one regression module. Reverting the four product lines restores the old behaviour. No successor is required.
Validation
5a84086a6unitpassedtests/control_plane/test_scheduler_stale_receipt_gate.py— 2 passed: a superseded receipt is refused withheartbeat_receipt_staleand no scheduler state file; a fresh receipt still commitsregression_paritypassedrc 0,ok: true) and the fresh-receipt twin passes; with the change restored, both passreal_entrypointpassedloopxCLI throughloopx.control_plane.testing.canary_harness.run_json_cli_result; the suite uses the repository's ownwrite_cli_fixturestaticpassedruff checkclean on the three product files and the new test;git diff --checkcleanreal_backendnot_applicableprovider="file", because the local Node 24.14.1 / SQLite 3.51.2 pair is not on the product's qualified list; the change is provider-independent and CI exercises the default provider.scheduler-fail-currentshares_run_native_followupand the same pre-check but has no separate row here beyond the shared code path. The reproduction and its assumptions are recorded in [Bug]: the Python scheduler follow-up route accepts a heartbeat receipt a newer Turn superseded #8605.Frontend / Visual Evidence
Type of Change
LoopX Area
Technical Direction
Shared-authority RFC fixture impact
Boundary Checklist
.loopx/,.codex/goals/, and liveACTIVE_GOAL_STATE.md).python3 scripts/check_rfc_bilingual.py --base origin/main --head HEADpasses, or no paired RFC changed.none.Signed-off-bytrailer (git commit -s).