Repository navigation
Fence dedicated update-validation polls against replaced worker registrations #732
Description
Current correction
General's draft #738 is now a7c250b, tree 129be2638cfc5eb4a194594d6a5825c1f0b6037a. It captures the admitted registration fence and hashed incarnation once, then checks both while holding the worker row lock inside every claim/retry transaction. The dedicated poll returns the existing typed stale result; obsolete requests do not consume a task.
The original source cohort at2f92a6a9 passed SQLite29 tests/321 assertions and PostgreSQL17.11/MySQL8.0.46 each29/305. Detailed source receipt records all guards and the final retained unfixed counterfactual: three replacement cases fail while the unchanged control passes.
Direct Sonar identified one maintainability issue in that candidate. The current head consolidates its two stale exits into one fence helper; no other source/test range changed. All five affected wake/lock-retry controls pass again on this head: SQLite5/68, PostgreSQL17.11 and MySQL8.0.46 each5/66, no failures/errors/skips. Pint/diff checks pass. Fresh hosted qualification and Sonar are running; this is still a draft with no final review/merge. Publication and installed PHP acceptance remain required. Native work stays with its owner and is not proved by PHP checks.
Problem
At current main bce669d937594aebb6b2ee45992eeb40ba63e55e, the dedicated POST /api/worker/update-validation-tasks/poll path does not fence a worker registration that changes while the request is waiting.
WorkerController::pollUpdateValidationTasks() resolves one WorkerRegistration, and WorkflowUpdateValidationTaskBroker::poll() captures that model across every repeated LongPoller::until() probe. claim() eventually calls workerMatchesTask(), but that checks WorkerPollFence::isFresh($worker) against the captured model; it does not compare the snapshot with the current registration row. A replacement registration with the same worker ID can therefore supersede the request while the old request still appears fresh in memory.
If an update-validation task wakes the old request during that window, the old request can lease it using the superseded process's routing/capability snapshot. The active replacement does not receive the task until the lease expires, so synchronous update admission can time out or be handled by the wrong worker incarnation. This is a within-namespace availability/correctness defect; no cross-tenant access or data disclosure is currently evidenced.
Workflow, activity, and query polling already carry a WorkerPollFence snapshot and compare it with current durable registration state. The dedicated update-validation poll should preserve the same replacement boundary. Closed #35 covered task-kind/version fencing for multiplexed polls, not worker-registration replacement on this dedicated path.
Acceptance
- Capture the authoritative worker-registration fence/incarnation when the dedicated validation poll is admitted.
- Before every claim, including wake and lock-pressure retry attempts, compare that fence with the current registration inside the claim transaction; do not lease when it is superseded.
- Return the existing typed
stale_worker_registrationpoll result (or an equally explicit compatible refusal) so the obsolete request terminates without consuming the task. - Add a counterfactual that starts a waiting dedicated poll, replaces the same worker ID with the same queue/capabilities before the next probe, makes a validation task ready, and proves the old request does not lease it while the replacement can.
- Cover changed routing/capabilities and the unchanged-current-registration control. Preserve namespace, workflow contract/fingerprint, build-ID, attempt, and lock-pressure fences.
- Qualify SQLite, PostgreSQL, and MySQL behavior and the published PHP consumer path. Do not weaken long-poll, lease, or update-validation timeout thresholds.
Current evidence
WorkflowUpdateValidationTaskBroker::poll()closes over the initially resolved model for the wholeLongPollerloop.workerMatchesTask()calls onlyWorkerPollFence::isFresh()on that captured model.- No current test or open issue found for replacement during the dedicated validation poll.
- Current-main source was re-fetched and inspected at the exact SHA above; no product source was changed during this review.
Activity
rmcdaniel commentedon Oct 24, 2026
Cross-implementation scope correction — 2026-10-24
The Rust Server owner corrected an earlier implementation assumption after inspecting native. Their current finding is:
worker_idduring each validation claim, but does not bind the waiting request to a registration incarnation;stale_worker_registrationresult on replacement, while native query polling does.This is owner-reported scope, not a completed Security source review. Ownership stays explicit: General owns this issue's PHP correction; the Rust Server owner will add a published-2.11.1 replacement counterfactual and native implementation under PR #712, then follow the accepted PHP validation behavior once released. Security will review native only after a formal terminal exact-SHA handoff. No shared/frozen fixture or source overlap is authorized by this note.
Do not close the PHP defect merely because native work exists, and do not claim native parity from the PHP correction. Each implementation needs its own exact evidence and installed/published acceptance.