Skip to content

Fence dedicated update-validation polls against replaced worker registrations #732

Open

Description

@rmcdaniel

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_registration poll 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 whole LongPoller loop.
  • workerMatchesTask() calls only WorkerPollFence::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

  1. rmcdaniel commented on Oct 24, 2026

    @rmcdaniel
    MemberAuthor

    Cross-implementation scope correction — 2026-10-24

    The Rust Server owner corrected an earlier implementation assumption after inspecting native. Their current finding is:

    • native re-reads the registration row by worker_id during each validation claim, but does not bind the waiting request to a registration incarnation;
    • a same-ID replacement with the same queue/capabilities can therefore still look current to the superseded validation poll;
    • native held workflow/activity polls also do not yet return PHP's in-poll stale_worker_registration result 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions