Skip to content

Admit signals before any worker declares a contract, as PHP does - #572

Merged
rmcdaniel merged 3 commits into
mainfrom
rust-325-commands-before-worker
Oct 21, 2026
Merged

rmcdaniel merged 3 commits into
mainfrom
rust-325-commands-before-worker

Conversation

@rmcdaniel

Copy link
Copy Markdown
Member

Part of #325.

Native now admits signals as published Server 2.5.14 does when no worker of the workflow type has registered yet. Before this change, native rejected such signals with 404 unknown_signal, so they were lost. PHP accepts them.

Found by. The Waterline service-mode qualification (#325 comment 6309509447) ran published Waterline 2.12.0 in front of PHP and in front of native, with identical seeds. Parity fixtures never reach this state, because they always run a worker.

Cause. Native records a run's command contract at start only when a worker for the type has registered. It then treated a missing contract as one that declares nothing. Its recorded contracts also lacked PHP's declared_entry_* fields.

What changes (following PHP's signalAdmissionForRun and ServerWorkflowControlPlane::signal):

  • A signal is unknown only under a complete durable contract. A run without one admits every signal.
  • A declared signal without a parameter contract takes its arguments as given.
  • The runtime message-stream signal name gets 422 runtime_reserved_signal with PHP's body, before admission and whatever the run's state. No command is recorded.
  • A signal without input, or with null input, has no arguments. Native previously refused it with unsupported_payload_codec.
  • Recorded contracts carry PHP's entry defaults: handle, canonical and external:<type>.
  • Signal responses carry workflow_type. The control_plane envelope projects exactly PHP's PROJECTED_FIELDS. Cancel and terminate responses now carry the envelope.

Evidence.

  • New signals-before-worker fixture, compared with a reviewed copy of PHP's responses. Locally, published PHP, embedded and this branch's native build pass it; the native build from Native takeover: refuse PHP schedules native cannot run yet #568's branch (current main plus Native takeover: refuse PHP schedules native cannot run yet #568) fails it.
  • An integration test covers both cases: a run with no recorded contract, and one with a registered contract.
  • Local results: the lib suite (146), the SQLite execution suite (296 passed; the one other failure is the known flaky cooperative_child_independent deadline test, fixed separately), signal tests on PostgreSQL and MySQL 8.4 (36 each), and all 5,043 tests/Unit/ServerParity* contract tests.

Out of scope, tracked on #325. The same comparison found three more differences, each with its own task:

  • durable rejected updates, which PHP records as a command, an update row and an UpdateRejected event;
  • PHP's query_worker_unavailable routing refusals;
  • broader response-shape differences: the start envelope, the describe fields and the history payload fields.

🤖 Generated with Claude Code

A run started before any worker registered for its type has no recorded
command contract. Native treated that as a contract declaring nothing and
rejected every signal with 404 unknown_signal, so the signal was lost;
published PHP admits it (external_contract_unavailable).

Admission now follows PHP's signalAdmissionForRun: only a complete durable
contract makes a signal unknown, a declared signal without a parameter
contract takes its arguments as given, and the runtime message-stream
signal name is refused with 422 runtime_reserved_signal before admission,
without a command. A signal without input, or with null input, has no
arguments. Recorded contracts carry PHP's entry defaults, so they are
complete. Signal responses carry workflow_type, the control_plane envelope
projects exactly PHP's PROJECTED_FIELDS, and cancel and terminate
responses carry it too.

The new signals-before-worker fixture compares every response with a
reviewed copy of published PHP's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rmcdaniel

Copy link
Copy Markdown
Member Author

Merged main after #558 (e4b6de5); the merge was clean. Cancelled the first runs on the behind head: 39557545130 39557544907 39557544983 39557545057 39557538994 .

…ore-worker

# Conflicts:
#	docs/rust-server-port.md
@rmcdaniel

Copy link
Copy Markdown
Member Author

Merged main after #568 (be06d0f); the port-log conflict was resolved by keeping both entries, and the merged tree builds. Superseded runs: 39557610385 39557610291 39557610206 39557545130 .

@sonarqubecloud

Copy link
Copy Markdown

@rmcdaniel
rmcdaniel merged commit 5025bc0 into main Oct 21, 2026
17 of 22 checks passed
@rmcdaniel
rmcdaniel deleted the rust-325-commands-before-worker branch October 21, 2026 07:24
rmcdaniel added a commit that referenced this pull request Oct 21, 2026
…fication-fix

Fix main's database parity jobs after #570 and #572
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants