You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Typed datetime search attributes with a UTC offset are stored as the wrong instant #898
The writer, UTC6 predicates and history-backed repair command are published in Workflow 2.12.0, source 789f3b6428db91590edfb45fc6da717113a61ee4. Normal package and registered-command acceptance passes 31 cases / 246 assertions and matches all 646 installed source/manifest files. Preview/apply, repeat, namespace/history/pruning refusals and rollback are covered. The all-eight-locale recovery guide is merged as Website #400; Pages acceptance remains pending.
Still open: adopt the package into the next safely published Server image and verify the original typed datetime HTTP/read/filter behavior on that exact image and database. Existing Server 2.11.0 carries Core 2.10.1. A package tag does not update that image, and old rows need the explicit guarded repair rather than automatic rewriting. No frozen/native catalog adoption is implied.
Problem
A typed datetime search attribute written with a UTC offset is stored as a different instant. The run reads it back with the offset dropped:
Upserted value
Read back by GET /api/workflows/{id}
Correct instant
2026-10-08T12:34:56+02:00
2026-10-08T12:34:56.000000Z
2026-10-08T10:34:56.000000Z
Visibility filters and Waterline see the shifted value. Values written in UTC (...Z) are unaffected.
Reproduce
Against the published Server image ghcr.io/durable-workflow/server@sha256:28fa0f3968ea253c727aad7fe583bb61c788553f1e4b96ece6ea4570d20faf42 (PostgreSQL), complete a workflow task with:
SearchAttributesUpserted.payload.attributes.SaOffset keeps +02:00, and GET /api/workflows/{id} returns "SaOffset":"2026-10-08T12:34:56.000000Z".
Expected
The stored attribute is the instant that was written. Describe, list results and visibility comparisons use 2026-10-08T10:34:56.000000Z.
Context
WorkflowSearchAttribute::setDatetimeValue keeps the parsed Carbon value in its own offset. The model's Y-m-d H:i:s.u date format writes that wall-clock time without converting it to UTC.
Found while porting search attribute upserts to the native server (server#325), which stores the correct instant.
The intended contract is to preserve the instant. An offset timestamp must be converted to UTC, not relabelled with Z; the native behavior described here should stay unchanged. This is a PHP product defect investigation, priority P2, not a documentation-only limitation.
The supplied reproduction is explicitly the frozen published Server 2.5.14 cohort. First reproduce or reject the problem on the current normally installed Workflow and published PHP Server before attributing it to a current release. Cover positive/negative offsets, day boundaries, fractional seconds and existing UTC values across embedded/service storage, describe/list and visibility comparisons. Preserve original history and define any explicit projection repair from retained history without guessing missing source values.
General owns the PHP follow-through after the active release/consumer gates. No native correction, frozen retuple or current-release finding is inferred from the older evidence.
The UTC correction now has actual SQL counterfactual coverage in addition to the unit cases.
The unmodified published Server 2.11.1 / Core 2.10.2 model and filter files are byte-identical to those in Core 2.11.4 (SHA-256 8cd5649bbb2899ff4b3617350c496037a6969748cbf075274b0ecfefaedfc607 and 7c8d36704536bf1ba75d68614644934e6f9fc18a4172d127b8ee86843b5e7fb8). With constructed rows on real SQLite and PostgreSQL 17.11, positive and negative offsets store the wrong UTC instant; even the UTC control misses an equal-instant predicate because its microseconds are lost when binding. A cold read under America/New_York also adopts the wrong timezone. All three case groups fail on each unmodified backend.
Explicitly mounting only the two corrected class files passes all sixteen field/predicate checks per backend: exact UTC stored value, cold read, equivalent-instant match, one-microsecond-neighbor refusal and unchanged input; the non-UTC application read also preserves the instant. This qualifies the source storage component, not a normally installed new release, actual workflow, HTTP route or legacy repair. The PostgreSQL fixture table was removed and the isolated database exited 0 without OOM; all task containers are removed.
Draft #914 now includes real migrated-table upsert/filter regressions at 9e569d89183073ecb88751fe1922e65edbab336a / tree 42f47caf8dbf298818ff4e6aa8b652b0a3aeb974. Fresh normal CI must qualify those tests, including MySQL. The previous formatting-only source check passed ECS/Stan after its one requested chain-layout correction.
Remaining: exact final source/SQL qualification, the separate PHP Server datetime predicate path, and an explicit retained-evidence repair for already shifted rows. Existing rows must not be silently reinterpreted or repaired by guessing a timezone. No schema change, new tag or completed product claim.
Source delivery checkpoint: the datetime writer/predicate and explicit retained-history repair merged through PR #914 as b3bbfdb, whole tree f29f12a500382c21d773f673922940e9760e6900, identical to reviewed candidate 6e53b06. Every applicable candidate check passes, including the actual regression corpus, source quality, MySQL feature selection and Sonar. The remote head is deleted and independently absent.
The final functional component passes 92 tests / 3,711 assertions, plus the actual migrated-table repair selection independently on PostgreSQL 17.11 and MySQL 8.0.46 (21 tests / 130 assertions each). The official complete three-way corpus counterfactual passes: the new retained-history repair fixture fails on unchanged base and passes on this implementation. Earlier failed candidates remain failed.
The separate PHP Server predicate fix #697 is merged at 3f448d3099848d6d8ec5971432bd8b60f26dfec3 / tree 6885be942be631f0dac2aa85d70d8a6f0c4647fb and its independent main source checks now pass. Neither merge is a released package/image.
Workflow 2.12.0 metadata is prepared. Fresh Core main build 39980005341, normal published-package verification, actual Server HTTP consumers and historical repair acceptance still follow before this issue closes. All-eight-locale recovery guidance is being prepared against the exact command contract, with publication tied to the actual package. No schema, business history, task/lease, native/shared or frozen-reference adoption is included.
The retained original MySQL report is now read and its assertion is established. A temporary read-only Actions job 39987095094 downloaded artifact 12513234212 through GitHub's network, verified SHA-256 ec1defd32c23feda5a5dfdda7ffc4f54a5e0539244e6f9e50fb96110836e52ad, and exposed its existing failures as bounded annotations. It ran no product tests, modified no default-branch file, and its temporary branch/worktree are retired.
All eight original errors are MultipleRecordsFoundException: 2 records were found in V2ActivityEntryArgumentsTest::runWorkflowTask, line 208. That helper selects ready workflow tasks globally. These errors precede the entry-safety assertions; they are not evidence of a datetime or entry-safety runtime failure.
A new explicit unrelated-run control at test-first source c1117d9 independently reproduces the same two-record collision on MySQL: one new case errors while all eight original cases pass in isolation. The fixture correction scopes task/execution/history/repair assertions to the original run and checks the unrelated task/history remain unchanged. Safety assertions and runtime source are retained. Prefix-plus-target requalification and fresh normal checks follow; the original hosted run remains failed.
The separate derived timeline fix is source-merged through #916 at e943469 after all applicable candidate checks and zero OPEN/CONFIRMED Sonar findings. Its fresh main is independent. No stable package/tag or Server image is claimed yet. This issue remains open for normal publication and actual repair/HTTP consumer acceptance.
Published Workflow 2.12.0 at immutable source 789f3b6428db91590edfb45fc6da717113a61ee4, whole tree b93cfe8be0124b562b9938af1152cf6f327fd680. The complete exact-main database/upgrade/quality/coverage run, CodeQL, boundary and Sonar checks pass.
Normal Packagist source and dist identify that exact commit. A fresh Composer consumer, without source overrides, matches all 646 installed source/manifest files byte-for-byte. On PHP 8.3.36 / Laravel 12 / SQLite, the normally installed package passes 31 focused cases / 246 assertions: four UTC-offset writer inputs and microsecond SQL boundaries, eight registered Artisan repair cases (preview, apply, repeat, latest-history/namespace/pruning refusal and transactional rollback), and 22 UTF-8 timeline summary/unchanged-evidence boundaries. The initial consumer harness pointed at the wrong migration directory and had eight pre-assertion errors; that failed preparation is retained separately, with no product or regression-test edit.
The published-package workflow has passed its package contract and is running its supported Laravel upgrade matrix. Source checks and this installed package receipt do not claim that every pending cell has passed.
This qualifies the package and the documented history-backed repair command. A Server image containing this package, and its actual HTTP/database acceptance, remain required before closing the original Server-facing datetime/timeline delivery. Existing installed Server 2.11.0 still carries Core 2.10.1; no old image/tag is changed or called repaired. Native/shared/frozen catalog adoption remains separate. The wider canonical failure-storage limit remains with shared #225.
All consumer/compiler/cache/runtime resources from this acceptance are removed; original failures, source identities, lockfiles and compact evidence remain. Website PR #400 now has the actual package/normal-command receipt needed for its staged 2.12.0 minimum; Pages and all-eight-route verification remain before Website completion.
Activity
rmcdaniel commentedon Oct 22, 2026
The intended contract is to preserve the instant. An offset timestamp must be converted to UTC, not relabelled with
Z; the native behavior described here should stay unchanged. This is a PHP product defect investigation, priority P2, not a documentation-only limitation.The supplied reproduction is explicitly the frozen published Server 2.5.14 cohort. First reproduce or reject the problem on the current normally installed Workflow and published PHP Server before attributing it to a current release. Cover positive/negative offsets, day boundaries, fractional seconds and existing UTC values across embedded/service storage, describe/list and visibility comparisons. Preserve original history and define any explicit projection repair from retained history without guessing missing source values.
General owns the PHP follow-through after the active release/consumer gates. No native correction, frozen retuple or current-release finding is inferred from the older evidence.
rmcdaniel commentedon Oct 23, 2026
The UTC correction now has actual SQL counterfactual coverage in addition to the unit cases.
The unmodified published Server 2.11.1 / Core 2.10.2 model and filter files are byte-identical to those in Core 2.11.4 (SHA-256
8cd5649bbb2899ff4b3617350c496037a6969748cbf075274b0ecfefaedfc607and7c8d36704536bf1ba75d68614644934e6f9fc18a4172d127b8ee86843b5e7fb8). With constructed rows on real SQLite and PostgreSQL 17.11, positive and negative offsets store the wrong UTC instant; even the UTC control misses an equal-instant predicate because its microseconds are lost when binding. A cold read under America/New_York also adopts the wrong timezone. All three case groups fail on each unmodified backend.Explicitly mounting only the two corrected class files passes all sixteen field/predicate checks per backend: exact UTC stored value, cold read, equivalent-instant match, one-microsecond-neighbor refusal and unchanged input; the non-UTC application read also preserves the instant. This qualifies the source storage component, not a normally installed new release, actual workflow, HTTP route or legacy repair. The PostgreSQL fixture table was removed and the isolated database exited 0 without OOM; all task containers are removed.
Draft #914 now includes real migrated-table upsert/filter regressions at
9e569d89183073ecb88751fe1922e65edbab336a/ tree42f47caf8dbf298818ff4e6aa8b652b0a3aeb974. Fresh normal CI must qualify those tests, including MySQL. The previous formatting-only source check passed ECS/Stan after its one requested chain-layout correction.Remaining: exact final source/SQL qualification, the separate PHP Server datetime predicate path, and an explicit retained-evidence repair for already shifted rows. Existing rows must not be silently reinterpreted or repaired by guessing a timezone. No schema change, new tag or completed product claim.
rmcdaniel commentedon Oct 23, 2026
Source delivery checkpoint: the datetime writer/predicate and explicit retained-history repair merged through PR #914 as b3bbfdb, whole tree f29f12a500382c21d773f673922940e9760e6900, identical to reviewed candidate 6e53b06. Every applicable candidate check passes, including the actual regression corpus, source quality, MySQL feature selection and Sonar. The remote head is deleted and independently absent.
The final functional component passes 92 tests / 3,711 assertions, plus the actual migrated-table repair selection independently on PostgreSQL 17.11 and MySQL 8.0.46 (21 tests / 130 assertions each). The official complete three-way corpus counterfactual passes: the new retained-history repair fixture fails on unchanged base and passes on this implementation. Earlier failed candidates remain failed.
The separate PHP Server predicate fix #697 is merged at 3f448d3099848d6d8ec5971432bd8b60f26dfec3 / tree 6885be942be631f0dac2aa85d70d8a6f0c4647fb and its independent main source checks now pass. Neither merge is a released package/image.
Workflow 2.12.0 metadata is prepared. Fresh Core main build 39980005341, normal published-package verification, actual Server HTTP consumers and historical repair acceptance still follow before this issue closes. All-eight-locale recovery guidance is being prepared against the exact command contract, with publication tied to the actual package. No schema, business history, task/lease, native/shared or frozen-reference adoption is included.
rmcdaniel commentedon Oct 23, 2026
The retained original MySQL report is now read and its assertion is established. A temporary read-only Actions job 39987095094 downloaded artifact 12513234212 through GitHub's network, verified SHA-256
ec1defd32c23feda5a5dfdda7ffc4f54a5e0539244e6f9e50fb96110836e52ad, and exposed its existing failures as bounded annotations. It ran no product tests, modified no default-branch file, and its temporary branch/worktree are retired.All eight original errors are
MultipleRecordsFoundException: 2 records were foundinV2ActivityEntryArgumentsTest::runWorkflowTask, line 208. That helper selects ready workflow tasks globally. These errors precede the entry-safety assertions; they are not evidence of a datetime or entry-safety runtime failure.A new explicit unrelated-run control at test-first source c1117d9 independently reproduces the same two-record collision on MySQL: one new case errors while all eight original cases pass in isolation. The fixture correction scopes task/execution/history/repair assertions to the original run and checks the unrelated task/history remain unchanged. Safety assertions and runtime source are retained. Prefix-plus-target requalification and fresh normal checks follow; the original hosted run remains failed.
The separate derived timeline fix is source-merged through #916 at e943469 after all applicable candidate checks and zero OPEN/CONFIRMED Sonar findings. Its fresh main is independent. No stable package/tag or Server image is claimed yet. This issue remains open for normal publication and actual repair/HTTP consumer acceptance.
rmcdaniel commentedon Oct 23, 2026
Published Workflow 2.12.0 at immutable source
789f3b6428db91590edfb45fc6da717113a61ee4, whole treeb93cfe8be0124b562b9938af1152cf6f327fd680. The complete exact-main database/upgrade/quality/coverage run, CodeQL, boundary and Sonar checks pass.Normal Packagist source and dist identify that exact commit. A fresh Composer consumer, without source overrides, matches all 646 installed source/manifest files byte-for-byte. On PHP 8.3.36 / Laravel 12 / SQLite, the normally installed package passes 31 focused cases / 246 assertions: four UTC-offset writer inputs and microsecond SQL boundaries, eight registered Artisan repair cases (preview, apply, repeat, latest-history/namespace/pruning refusal and transactional rollback), and 22 UTF-8 timeline summary/unchanged-evidence boundaries. The initial consumer harness pointed at the wrong migration directory and had eight pre-assertion errors; that failed preparation is retained separately, with no product or regression-test edit.
The published-package workflow has passed its package contract and is running its supported Laravel upgrade matrix. Source checks and this installed package receipt do not claim that every pending cell has passed.
This qualifies the package and the documented history-backed repair command. A Server image containing this package, and its actual HTTP/database acceptance, remain required before closing the original Server-facing datetime/timeline delivery. Existing installed Server 2.11.0 still carries Core 2.10.1; no old image/tag is changed or called repaired. Native/shared/frozen catalog adoption remains separate. The wider canonical failure-storage limit remains with shared #225.
All consumer/compiler/cache/runtime resources from this acceptance are removed; original failures, source identities, lockfiles and compact evidence remain. Website PR #400 now has the actual package/normal-command receipt needed for its staged 2.12.0 minimum; Pages and all-eight-route verification remain before Website completion.