Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
82fc9f6
Refuse malformed input envelopes like published PHP
rmcdaniel Oct 21, 2026
5a9136c
Report repair candidates like published PHP
rmcdaniel Oct 21, 2026
3692e9a
Merge branch 'main' into rust-325-repair-status
rmcdaniel Oct 21, 2026
2bc38eb
Repair workflow runs like published PHP
rmcdaniel Oct 21, 2026
6b50b8d
Merge branch 'main' into rust-325-workflow-repair
rmcdaniel Oct 21, 2026
ea0f3fc
Merge branch 'main' into rust-325-input-envelopes
rmcdaniel Oct 21, 2026
0497b64
Read PHP's legacy authentication settings
rmcdaniel Oct 21, 2026
834c274
Merge remote-tracking branch 'origin/main' into rust-325-input-envelopes
rmcdaniel Oct 21, 2026
656b30a
Merge remote-tracking branch 'origin/rust-325-input-envelopes' into r…
rmcdaniel Oct 21, 2026
fcfb139
Merge remote-tracking branch 'origin/main' into rust-325-workflow-repair
rmcdaniel Oct 21, 2026
4fbe618
Merge remote-tracking branch 'origin/rust-325-workflow-repair' into r…
rmcdaniel Oct 21, 2026
c5929ca
Merge branch 'main' into rust-325-repair-status
rmcdaniel Oct 21, 2026
3e0b62d
Refuse PHP settings native cannot honor
rmcdaniel Oct 21, 2026
d0f7dae
Report activity deadlines on claims and status reads like PHP
rmcdaniel Oct 21, 2026
f6ff81a
Record the structural limits native does not enforce yet
rmcdaniel Oct 21, 2026
c2c0eeb
Record the settings checks' verification
rmcdaniel Oct 21, 2026
837405a
Merge remote-tracking branch 'origin/main' into rust-325-legacy-auth-env
rmcdaniel Oct 21, 2026
94edbf4
Judge a late activity result by its attempt first, like PHP
rmcdaniel Oct 21, 2026
9f8321f
Answer any other late result for a closed activity task as stale
rmcdaniel Oct 21, 2026
6abc630
Answer late results after an activity timeout with PHP's stale receipt
rmcdaniel Oct 21, 2026
9f7e063
Record the activity recovery checkpoint and two activity result gaps
rmcdaniel Oct 21, 2026
37a7702
Admit queries while a workflow task is open, like PHP
rmcdaniel Oct 21, 2026
7faaf93
Record the query open-task checkpoint
rmcdaniel Oct 21, 2026
28741de
Narrow the structural limits gap to what PHP enforces for external wo…
rmcdaniel Oct 21, 2026
6eae18e
Retry a failed workflow task that carries an update or a signal
rmcdaniel Oct 21, 2026
6e3f07d
Apply a signal without arguments as true, like PHP
rmcdaniel Oct 21, 2026
ffe23b0
Record the update scenario message checkpoint
rmcdaniel Oct 21, 2026
48aadb8
Record the missing search attribute upsert command
rmcdaniel Oct 21, 2026
22e70d5
Wait for an unreachable database at startup, like PHP
rmcdaniel Oct 21, 2026
bf3b2a6
Apply upsert_search_attributes like PHP
rmcdaniel Oct 21, 2026
1d356d8
Give signal task claims the signal's arguments, like PHP
rmcdaniel Oct 21, 2026
c0da48c
Filter workflow lists with PHP's typed visibility queries
rmcdaniel Oct 21, 2026
936c2db
Merge branch 'main' into rust-325-input-envelopes
rmcdaniel Oct 21, 2026
5b31949
Deliver cancellation to a parallel timer group, like PHP
rmcdaniel Oct 21, 2026
0dc4c6b
Authenticate database runtime credentials and the none driver like PHP
rmcdaniel Oct 22, 2026
a3faf63
Compare stored credential claims and expiry through typed locals
rmcdaniel Oct 22, 2026
d41fce9
Merge remote-tracking branch 'origin/rust-325-input-envelopes' into r…
rmcdaniel Oct 22, 2026
7a8f6cb
Merge remote-tracking branch 'origin/rust-325-query-open-task' into r…
rmcdaniel Oct 22, 2026
0db6466
Merge remote-tracking branch 'origin/rust-325-signal-claim-arguments'…
rmcdaniel Oct 22, 2026
656ece2
Merge remote-tracking branch 'origin/rust-325-repair-status' into rus…
rmcdaniel Oct 22, 2026
394f94c
Merge remote-tracking branch 'origin/rust-325-wait-for-database' into…
rmcdaniel Oct 22, 2026
9d067e8
Merge remote-tracking branch 'origin/rust-325-retry-message-tasks' in…
rmcdaniel Oct 22, 2026
2081e56
Merge remote-tracking branch 'origin/rust-325-search-attribute-upsert…
rmcdaniel Oct 22, 2026
c0becff
Merge remote-tracking branch 'origin/rust-325-parallel-cancellation' …
rmcdaniel Oct 22, 2026
8fe0ed4
Merge remote-tracking branch 'origin/rust-325-workflow-repair' into r…
rmcdaniel Oct 22, 2026
128c9fd
Merge remote-tracking branch 'origin/rust-325-legacy-auth-env' into r…
rmcdaniel Oct 22, 2026
e827eb5
Merge remote-tracking branch 'origin/rust-325-activity-deadline-view'…
rmcdaniel Oct 22, 2026
a1be32b
Integrate the October 21-22 port train: restore the startup test modu…
rmcdaniel Oct 22, 2026
308d2ba
Merge remote-tracking branch 'origin/rust-325-runtime-credentials' in…
rmcdaniel Oct 22, 2026
09c0a12
Expect native to accept the none driver and runtime credentials in th…
rmcdaniel Oct 22, 2026
6c4ef39
Dispatch a replay-blocked run's repaired task, like PHP
rmcdaniel Oct 22, 2026
faac0d4
Prove a failed credential lookup refuses without falling back
rmcdaniel Oct 22, 2026
db421a4
Merge remote-tracking branch 'origin/rust-325-runtime-credentials' in…
rmcdaniel Oct 22, 2026
19af791
Merge remote-tracking branch 'origin/main' into rust-325-train-20261022
rmcdaniel Oct 22, 2026
7c05431
Merge branch 'main' into rust-325-train-20261022
rmcdaniel Oct 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# October 21, activity deadlines and late results

- **Found by.** Running the Sample App's activity recovery scenario
(`scripts/sdk-activity-recovery.sh`) against native, unchanged except that
its server, and its separate PHP `activity:timeout-enforce` loop, were
native processes. The scenario failed four times in turn, once per gap below.
- **Deadlines on claims and status.** Published PHP returns the activity
execution's deadlines on an activity claim and on the attempt status read:
`schedule_to_start`, `start_to_close`, `schedule_to_close` and `heartbeat`,
each in UTC with microseconds, leaving out the ones that are not set. PHP
leaves the claim field out when there are none and reports `null` on
status. Native returned `null` on status and had no claim field, so the
scenario could not find the original total deadline.
- **Late results are judged by their attempt first.** PHP checks the
reporting attempt before the task's lease or recorded outcome. A result
from an attempt that is no longer current gets `409 stale_attempt`, even
after another attempt finished the task, or after the attempt's own
failure closed its task. The completion receipt carries the attempt
owner, `activity_status`, `attempt_status` and `task_status`; the failure
receipt does not. Native compared the task's recorded receipt first and
answered `conflicting_completion`, or `task_not_leased` for a failure. An
identical repeat of the result that finished the task keeps native's
idempotent receipt.
- **Results after a timeout.** A timeout closes the attempt and cancels its
task. Native answered a later completion or failure for that task with a
bare `409 stale_attempt`, without the task, attempt and status fields. The
PHP SDK could not recognize that refusal, tried to fail the task, received
the same refusal and stopped its worker. Every later case of the scenario
then had no PHP activity worker. Native now answers with PHP's full stale
attempt receipt, which the PHP SDK discards. Heartbeats keep their answer.
- **Verification.**
- Tests cover the deadlines map on a claim and a status read with and
without deadlines, a late result from a replaced attempt and from a
failed and retried attempt, and late results after each authored
timeout. They pass on SQLite, MySQL and PostgreSQL.
- On a disposable host, the scenario's five PHP-to-PHP cases and two
PHP-to-Python cases then passed. The next case stopped on a separate
gap: native refuses the Python SDK's activity failure reports, which
carry `class` and `stack_trace`.
28 changes: 28 additions & 0 deletions docs/rust-server-port-checkpoints/2026-10-21-database-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# October 21, waiting for the database at startup

- **Found by.** The Sample App's update scenario against native, on a fresh
VM. MySQL's health check passed while its entrypoint ran a temporary
socket-only server. Native connected just after that server stopped and
before the real one listened, got `Connection refused` and exited, and
Compose reported `dependency failed to start`.
- **PHP's behavior.** The published image's entrypoint runs
`server:assert-php-database --allow-unavailable` and starts Apache while
the database is unavailable. Requests fail until the database answers,
but the server keeps running.
- **Native's behavior now.** When the database cannot be reached yet,
native logs one line and retries with a backoff from 250 ms up to 5 s,
then logs that the database is reachable. Cases that count as not reachable
yet: the connection is refused or drops, the host doesn't resolve yet, the
pool times out, or PostgreSQL answers SQLSTATE 57P03 (starting up). A
refused login or a configuration error still stops the process.
`SIGTERM` or `SIGINT` while waiting exits cleanly, even during a
connection attempt.
- **Stop signal.** The published image declares `STOPSIGNAL SIGWINCH`.
Native does not stop on it, so this is recorded as a known gap for a
native image.
- **Verification.**
- A unit test pins which errors are awaited.
- Locally, native started four seconds before a fresh MySQL 8.0 container.
It waited, connected, and `/api/health` answered 200.
- Native waiting on an unresolvable host exited 0 within 0.4 s of
`SIGTERM`.
65 changes: 65 additions & 0 deletions docs/rust-server-port-checkpoints/2026-10-21-input-envelopes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# October 21, input envelope refusals

- **Found by.** While porting plain query input, the Waterline harness sent the
same 15 malformed `input` values to start, signal, update and query on
published PHP 2.5.14 and on native. In 4 of the 60 cases the responses
matched.
- **PHP's rules.** These come from `RuntimeExternalPayloadTransport`,
`AvroPayloadEnvelopeResolver`, `PayloadEnvelopeResolver` and the
workflow package's Avro serializer:
- **Transport.** Before validation, the transport refuses any input object
with an `external_storage` key: `422 external_payload_unsupported`.
- **Codec.** Any input object with a `codec` key must name `avro`, or have
an empty codec. Otherwise PHP refuses it on `input.codec` with
`unsupported_payload_codec: workflow payload codec "X" is not supported …`.
For a codec that is not a string, X is the PHP type name, such as `int`.
- **Envelope shape.** An exact envelope also needs a non-empty codec
string ("The payload envelope codec must be a non-empty string.") and a
string blob ("The payload envelope blob must be a string."). Laravel's
middleware turns an empty string into null first.
- **Signal and update decode their blob.** If decoding fails, PHP answers
"The input envelope blob could not be decoded with codec "avro": …" with
the serializer's diagnosis and remediation. A blob that decodes to a
scalar gets "The input envelope blob must decode to an array." A
non-empty map supplies its values as the arguments.
- **Base64.** PHP's strict base64 skips ASCII whitespace. Padding must be
absent or exactly what the final quantum needs, and unused trailing bits
may be set.
- **Envelope.** Every refusal is `422 validation_failed` with the
operation's control-plane envelope and `workflow_id`.
- **Source.**
- **Input checks.** `input_envelope` applies the transport, codec and
shape rules on all four routes.
- Signal and update refuse undecodable and scalar blobs with PHP's
messages, and take a map's values.
- Start parses non-envelope objects in request order, but keeps an object
whose codec would be refused, so the codec check still sees it.
- Start also refuses an undecodable blob with the signal message.
Published PHP 2.5.14 answers that case with a 500 (server#601).
- **Codec.** `codec::ingress_diagnosis` gives PHP's diagnosis for each
decode failure: JSON-looking text, bad base64, missing magic, unknown
fingerprint, trailing bytes, truncation, or a malformed datum. Native's
Avro decoding now uses PHP's base64 rules everywhere.
- **Envelopes.** Update refusals now carry the control-plane envelope, as
signal and query refusals do. A command validation failure always
includes `workflow_id`.
- **Stored signals.** A signal already stored and applied during a worker's
task completion keeps its `invalid_signal_arguments` refusal.
- **Remaining differences.**
- **Start with an undecodable blob.** Native refuses it with 422; PHP 2.5.14
answers 500 (server#601).
- **Query with an undecodable blob.** Native refuses it at admission with
`invalid_query_arguments`. PHP first checks for a query worker, so
without one it answers `409 query_worker_unavailable`.
- **External payload references.** `{codec, external_payload}` input on
signal, update and query is still unsupported in native. PHP resolves it.
- **Verification.**
- **Harness.** The 60 malformed-input cases now match PHP's status, reason
and errors in 56 cases; the 4 left are the start and query differences
above. For seven refusal cases covering signal, update, query and an
unknown workflow, the bodies are identical, envelope included.
- **Tests.** `input_envelopes` covers the refusals on start and signal, plus
a map blob and an unpadded blob, on SQLite, PostgreSQL and MySQL. Unit
tests cover the diagnoses and the base64 rules. The query and update
tests now expect PHP's `validation_failed`. The full native suite passes
on SQLite.
54 changes: 54 additions & 0 deletions docs/rust-server-port-checkpoints/2026-10-21-legacy-auth-env.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# October 21, legacy authentication settings

- **Found by.** Preparing to run the Sample App's polyglot stack against
native. Its unchanged server environment sets `WORKFLOW_SERVER_AUTH_TOKEN`.
Native required `DW_AUTH_TOKEN`, and refused to start when one of the
legacy role-token names `WORKFLOW_SERVER_WORKER_TOKEN`,
`WORKFLOW_SERVER_OPERATOR_TOKEN` or `WORKFLOW_SERVER_ADMIN_TOKEN` was set.
Published PHP reads each of these through `EnvAuditor::env`: the `DW_*`
name when it is set, otherwise the legacy name.
- **Source.** Native now reads the auth token and the three role tokens the
same way. A set primary wins even when it is empty, as PHP's `env()`
returns it. The provider, principal-token and runtime-credential settings
keep their existing refusals.
- **Environment comparison.** Published PHP 2.5.14's `config/*.php` read 49
settings through `EnvAuditor::env`. Native reads 16 of them, together with
their legacy names. The rest fall into four groups:
- **Refused when set.** Authentication drivers other than `token` are
refused at start, so their signature keys and headers cannot take effect
silently.
- **Refused unless they match native.** `DW_DEFAULT_NAMESPACE` names the
namespace PHP serves to requests that name none, after lowercasing it.
`DW_WORKER_PROTOCOL_VERSION` is the protocol version PHP advertises and
gates features on. Native serves `default` and implements protocol
`1.20`, so it now refuses to start when either setting, or its legacy
name, holds another value. The error names the setting, not its value.
- **Read by PHP but never applied.** Published PHP puts
`DW_MAX_HISTORY_EVENTS`, `DW_MAX_PENDING_ACTIVITIES`,
`DW_MAX_PENDING_CHILDREN` and `DW_ACTIVITY_TASK_TIMEOUT` into its
configuration, but no code reads those entries. The structural limits
PHP enforces come from the Workflow package's own `DW_V2_LIMIT_*`
settings. Ignoring these four settings matches PHP. Of the package
limits PHP applies to external workers, native enforces only the memo
size; #612 tracks the rest. `DW_MAX_TASKS_PER_POLL` only widens
PHP's candidate scan (ten candidates for each task); a poll still claims
one task, so native's own scan width is not a contract difference.
- **PHP process roles and topology.** `DW_MODE`, `DW_SERVER_ID`,
`DW_SERVER_PROCESS_CLASS`, `DW_SERVER_TOPOLOGY_SHAPE`,
`DW_TASK_DISPATCH_MODE`, the poll interval settings and the
update-validation settings describe PHP's separate processes. Native
runs one process type.
- **Features native does not have.** Response compression, package
provenance, the external executor configuration and storage admission
files.
- **Verification.** A unit test covers the precedence: the primary wins, the
legacy name fills in, a set but empty primary still wins, and neither
gives nothing. A second unit test covers the refusals: the namespace and
protocol settings refuse other values under either name, accept
`default` in any case and `1.20`, and settings PHP never applies stay
accepted. Starting the binary with `DW_DEFAULT_NAMESPACE=billing` or
`WORKFLOW_SERVER_WORKER_PROTOCOL_VERSION=1.19` exits with the setting's
name; `DW_DEFAULT_NAMESPACE=Default` starts. On a disposable host, the
Sample App's PolyglotWorkflow and its timers scenario passed against
native with the sample's unchanged server environment, which sets only
`WORKFLOW_SERVER_AUTH_TOKEN`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# October 21, cancellation delivered to a parallel timer group

- **Found by.** The Sample App's timers scenario (`scripts/sdk-timers.sh`)
against native, in its `concurrent-cancellation` case. Each SDK's workflow
waits on three concurrent timers and then receives a cooperative
cancellation request. All three SDKs called `deliver-cancellation` with
`call_kind: parallel` and `sequence_span: 3`. Native refused with
`422 cancellation_delivery_boundary_not_available`, the Rust worker
exited, and the run never recorded its delivery.
- **PHP's behavior.** `CooperativeCancellationDelivery` accepts a
`parallel` boundary whose span is at most a command batch. At the next
sequence it records the delivery directly. Otherwise `parallelEligibility`
requires every member to belong to the group at its index. A group whose
members all resolved before the request is not eligible.
`deliverPortableCancellation` then cancels each member's open wait and
records `CooperativeCancellationDelivered` with `sequence_span`.
- **Native now.** Native applies the same checks to a group whose members
are all timers:
- It cancels each pending member timer (`TimerCancelled`).
- It records the delivery with `sequence_span` and returns it in the
receipt.
- A repeated delivery returns the same receipt.
- A wrong span answers `409 cancellation_delivery_shape_mismatch`.

A group with an activity, child or wait member keeps its earlier refusal.
The remaining boundaries are recorded as a known gap.
- **Verification.**
- A new test in `tests/execution/parallel_cancellation.rs` schedules three
parallel timers, requests cancellation and refuses a wrong span. It then
delivers to the group, repeats the delivery, and checks the history:
three `TimerCancelled`, one delivery with `sequence_span: 3`, then
`WorkflowCancelled`.
- The full suite passes on SQLite. The cooperative and cancellation tests
(53) pass on PostgreSQL and MySQL.
47 changes: 47 additions & 0 deletions docs/rust-server-port-checkpoints/2026-10-21-query-open-tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# October 21, queries during open workflow tasks

- **Found by.** The Sample App's update scenario (`scripts/sdk-updates.sh`),
run unchanged against native. After nine cross-SDK update round trips, its
snapshot query reached a Rust workflow that had an open workflow task.
Native refused the query with `409 query_snapshot_busy`, a known gap.
- **Published PHP (`WorkflowQueryTaskBroker`).**
- **Admission.** PHP admits the query and records the run's history
sequence. If a workflow task holds a live lease at that moment, the
same sequence becomes the query's history cutoff.
- **Claim.** A worker cannot claim the query while any workflow task of
the run holds a live lease. It also cannot while a due resume task
(signal, update, activity, child or timer) is ready and the query
cannot see its effect. Two resume tasks do not hold the claim: one
created after a query with a cutoff was admitted, and a signal whose
`SignalReceived` event the query already observed. A run's first
workflow task is not a resume task.
- **Poll status.** A poll that finds only held queries returns
`workflow_task_pending` at once when a ready resume task holds them. It
returns `workflow_task_leased` when its wait ends while a lease holds
them.
- **Delivery.** The claim delivers the history read at claim time, up to
the cutoff (and never past the run's current history) or all of it
without one. `history_cutoff_sequence` is `null` without a cutoff.
- **Correction.** The earlier known-gap entry said a query sent right after
a signal waits on PHP. PHP records `SignalReceived` when the signal is
accepted, and the query observes it, so that resume task does not hold
the claim.
- **Native.** It follows these rules. Native's activity and ordinary timer
resume tasks name their source without a `resume_source_kind`, so native
recognizes them by `activity_execution_id` and `timer_id`. A run whose
history is already larger than native's one bounded query page is still
refused at admission with `413 query_snapshot_exceeds_budget`. A run that
grows past it before the claim fails the query with that reason. A poll
held by a lease re-checks with a read-only snapshot instead of the
transition lock, and wakes when the task ends.
- **Verification.**
- New tests cover:
- a query admitted during a leased task: it waits, the poll reports
`workflow_task_leased`, and after completion it reads history only to
its cutoff;
- a ready timer resume: `workflow_task_pending` at once, still held
while that task is leased, then the full history at the claim;
- a run's first task and an observed signal, neither of which holds the
claim.
- The existing query lease and replay tests pass unchanged. The two
`query_snapshot_busy` assertions they made were removed.
43 changes: 43 additions & 0 deletions docs/rust-server-port-checkpoints/2026-10-21-repair-status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# October 21, repair status

- **Found by.** The published dw 2.3.0 CLI sweep. `system:repair-status`
(`GET /api/system/repair`) got native's 501.
- **Source.** Native serves the admin-only `GET /api/system/repair` in the
default or a named namespace. Like published PHP 2.5.14, the response has
two parts: `policy` and `candidates`.
- **Candidates.** This is `TaskRepairCandidates::snapshot`, scanned across
every namespace, as in PHP:
- **Ready tasks** that are due and whose dispatch failed, whose claim
failed, or whose dispatch is overdue past the 3-second redispatch
cutoff, each with PHP's backoff rules.
- **Leased tasks** whose lease expired.
- **Open runs without a next task** that need repair, or that wait on
children that have all closed.
- **Grouping and selection.** Candidates are grouped by connection,
queue and compatibility. Null, empty and public-default values share a
scope, and each scope's selection uses its representative's exact
values, as PHP's `applyScope` does. Selection takes up to 25 per scope
and goes round-robin, oldest scope first, up to 25 in all.
- **Order.** Scopes are sorted busiest first, then oldest, then by key.
- **Time comparisons** bind "now" in whole seconds, as Laravel does.
- **Policy.** `policy` is native's own `repair_policy`, the same one
operator metrics reports: claim-time lease recovery, with no repair loop,
loop throttle or failure backoff. PHP reports its maintenance loop's
policy instead.
- **Operator metrics.** These keep their empty `repair` section, which is
what PHP returns when the request names a namespace.
- **Still open.**
- `POST /api/system/repair/pass`.
- Repairing a run whose task is ready (`workflow:repair` gets
`development_workflow_task_repair_unqualified`).
- **Verification.**
- **Integration test.** `repair_status` passes on SQLite, PostgreSQL and
MySQL. It covers two overdue ready tasks in one queue, an expired lease
in another, and a run in a third queue that lost its task. Unit tests
cover the round-robin selection and the merging of default scopes.
- **Harness, empty.** On the seeded data, both servers reported no
candidates.
- **Harness, candidates.** With three runs started on two queues and no
worker, the candidates matched published PHP 2.5.14 field for field,
apart from times. dw 2.3.0's `system:repair-status` succeeds against
both servers.
Loading
Loading