Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/native-image-parity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
run: |
set -euo pipefail
bash scripts/ci/pull-images.sh \
"$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.5.json)" \
"$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json)" \
composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee

- name: Set up Docker Buildx
Expand Down
58 changes: 29 additions & 29 deletions .github/workflows/server-parity.yml

Large diffs are not rendered by default.

56 changes: 56 additions & 0 deletions docs/rust-server-port-checkpoints/2026-10-29-reference-2117.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# October 29, the parity reference moves to published 2.11.7

- **Before.** The Shared Server fixtures compared native and embedded
against published Server 2.11.5 (Core 2.15.1).
- **Reference.** Published Server 2.11.7, Core 2.15.7, as General published
it and Security accepted it (#861):
- source `f55f90e54a213872f1c164a1b22c082d859c18aa`;
- index `sha256:96e3e7de8c58f31bfb1ef4d5bddddc2678f4684f5cb3505e6e9ddc6fa3f7f625`;
- linux/amd64 `sha256:be7c2288ff935396262d743590530b204fac11b76582e56a525cc81a8da840d3`;
- linux/arm64 `sha256:7dbc3db050fec05ea5cd10e07ac8399ecf3e285d593bca32979a2320f02a104f`.

`tests/Fixtures/ServerParityBaselines/php-2.11.7.json` records it. The
SDK, CLI, Waterline and sample-app pins are unchanged.
- **Why not 2.11.6.** A local survey of published 2.11.6 found that a
repeated bridge-adapter update answered `500` (#861). 2.11.7 carries the
fix (#862).
- **Survey.** Every fixture in `tests/Fixtures/ServerParity` was recorded
locally against the 2.11.7 amd64 image, SQLite, maintenance loop running,
except `service-calls` (local SQLite lock pressure) and
`visibility-current-runs` (it needs the published CLI artifact CI
provides). Results:
- 72 of 74 pass unchanged, `bridge-adapters` among them: 2.11.7 gives the
intended answers at all three of its published-defect steps;
- `schedule-catalog` fails locally the same way on 2.11.5;
- `waterline-import` changes as expected (below).
- **Expectation change.** Server #812 (#770) counts a v1 projection's
history bytes as Core's `HistoryBudget::sizeOfEvent` does: the event
type's bytes plus its payload as `json_encode` with
`JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE`. That is the unit
`DW_V2_LIMIT_HISTORY_TRANSACTION_BYTES` limits everywhere else.
`waterline-import` records five `history_size_bytes` values:

| Export | 2.11.5 | 2.11.7 |
| --- | --- | --- |
| project | 5431 | 4713 |
| running | 2683 | 2445 |
| failed | 5425 | 4707 |
| offsets | 6242 | 5336 |
| bare | 1856 | 1668 |

No other value in that fixture changed.
- **Native.** The prepared meter change (`e0ed32a6`, held since October 27
for this move) counts each event the same way, for the byte limit and
for the stored summary.
- **Local checks before the pull request.**
- `waterline-import` was recorded against a fresh 2.11.7 harness and
against a native image built from this branch. Both pass, and
`compare` accepts the pair. The 2.11.7 recording equals the stored
contract observation (with the five new sizes) exactly.
- The Rust library and integration suites pass (634 and 359).
- The node unit suite passes, except the PHP image-context test,
which fails the same way on a clean `main` checkout here because of
local ignored state.
- **Not changed here.** The `bridge-adapters` contract still accepts the
published-defect answers of 2.11.1 to 2.11.5. 2.11.7 no longer gives
them, so a later change can make that contract exact.
3 changes: 3 additions & 0 deletions docs/rust-server-port.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ Updated October 27, 2026.
and moved to published 2.11.5 (#815); the SQLite maintenance pause stays
for `service-calls` only
([checkpoint](rust-server-port-checkpoints/2026-10-27-reference-2115.md)).
- The parity reference moves to published 2.11.7 (#863), with native's
v1 projection history bytes in Core's canonical unit (#770)
([checkpoint](rust-server-port-checkpoints/2026-10-29-reference-2117.md)).
- [#814](https://github.com/durable-workflow/server/pull/814): the shared
fixtures accept published PHP's typed `429 long_poll_capacity_exhausted`
poll refusal, and retry a locked Composer install after a transient
Expand Down
35 changes: 18 additions & 17 deletions rust/src/runtime/waterline_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -609,14 +609,7 @@ fn events(source: &PhpArray, logs: &[PhpArray], signals: &[PhpArray], exceptions
recorded_at,
payload,
};
let size = object(vec![
("event_type", text(event.event_type)),
("recorded_at", optional(json_time(event.recorded_at))),
("payload", event.payload.clone()),
("sequence", Php::Int(event.sequence)),
("id", text(&event.id)),
]).encode(Flags { unescaped_slashes: true, ..Flags::default() }).len();
bytes = bytes.checked_add(size).unwrap_or(usize::MAX);
bytes = bytes.checked_add(event_bytes(&event)).unwrap_or(usize::MAX);
if let Some(error) = exceeded(limits, 0, bytes) {
return Err((error, index + 1));
}
Expand All @@ -625,6 +618,15 @@ fn events(source: &PhpArray, logs: &[PhpArray], signals: &[PhpArray], exceptions
Ok((built, bytes))
}

/// Core's history-transaction byte unit, `HistoryBudget::sizeOfEvent`: the
/// event type's bytes plus its payload as `json_encode` with
/// `JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE` writes it (Server #770).
/// The event's id, sequence and time are not counted.
fn event_bytes(event: &Event) -> usize {
event.event_type.len()
+ event.payload.encode(Flags { unescaped_slashes: true, unescaped_unicode: true, ..Flags::default() }).len()
}

/// `report`: the projection report, with the first error's reason, message
/// and remediation at the top of a rejection, then `extra`.
fn report(outcome: &Outcome, status: &str, extra: Vec<(&str, Value)>) -> Value {
Expand Down Expand Up @@ -1352,13 +1354,7 @@ mod tests {
let (outcome, start) = events_built(|| normalize_bounded("src-1", source, None, &limited(None, None)));
let Outcome::Valid(all) = outcome else { panic!("valid") };
assert_eq!(start, all.events.len());
let first = |count: usize| all.events[..count].iter().map(|event| object(vec![
("event_type", text(event.event_type)),
("recorded_at", optional(json_time(event.recorded_at))),
("payload", event.payload.clone()),
("sequence", Php::Int(event.sequence)),
("id", text(&event.id)),
]).encode(Flags { unescaped_slashes: true, ..Flags::default() }).len()).sum::<usize>();
let first = |count: usize| all.events[..count].iter().map(event_bytes).sum::<usize>();
let (outcome, built) = events_built(|| normalize_bounded("src-1", source, None, &limited(None, Some(first(4)))));
assert!(matches!(outcome, Outcome::TooLarge { built: 5, .. }));
assert_eq!(built, 5);
Expand All @@ -1367,7 +1363,8 @@ mod tests {
#[test]
fn events_and_history_size_follow_php() {
// PHP 2.11.1: LegacyV1Projection::normalize('src-1', bare source, 'ns-1')
// printed these event encodings (json_encode, JSON_UNESCAPED_SLASHES).
// printed these event encodings (json_encode, JSON_UNESCAPED_SLASHES),
// which fix the events' ids and payloads.
let source = super::super::php_value::decode(br#"{"operator_id":"v1:4501","engine_source":"v1","class":"App\\Workflows\\Legacy\\Bare","status":"completed"}"#)
.unwrap();
let Outcome::Valid(normalized) = normalize("src-1", source.array().unwrap(), Some("ns-1")) else { panic!("valid") };
Expand All @@ -1380,7 +1377,11 @@ mod tests {
]).encode(Flags { unescaped_slashes: true, ..Flags::default() })).collect();
assert_eq!(encoded[0].len(), 1374, "{}", encoded[0]);
assert_eq!(encoded[1].len(), 496, "{}", encoded[1]);
assert_eq!(normalized.history_size_bytes, 1870);
// Server #812's LegacyV1Projection::normalize on Core 2.15's
// HistoryBudget::sizeOfEvent counted 1280 and 402 bytes for these
// events: their types and payloads, not their ids, sequences or times.
assert_eq!(normalized.events.iter().map(event_bytes).collect::<Vec<_>>(), [1280, 402]);
assert_eq!(normalized.history_size_bytes, 1682);
assert_eq!(normalized.workflow_id, "v1:src-1:c8c25815a3332910ba072598");
assert_eq!(normalized.run_id, "b0ee56b2b89080ea9181490e2f");
assert_eq!(normalized.events[0].id, "9730c0bb4149e0a120cc8a8d7e");
Expand Down
4 changes: 2 additions & 2 deletions scripts/conformance/server-parity.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Requires Node 20+ and PHP 8.3+ with the locked adapter installed. Each target
must have its own isolated, already bootstrapped database. Set DW_PARITY_TOKEN
for the HTTP target; it is never recorded. Use the same --prefix on all targets
and a fresh database or prefix for a new run. --artifacts selects the exact
consumer tuple; the default is tests/Fixtures/ServerParityBaselines/php-2.11.5.json.
consumer tuple; the default is tests/Fixtures/ServerParityBaselines/php-2.11.7.json.
Repeat --fixture PATH to record or compare an explicitly selected candidate
set. --database-resource-limits accepts inspected memory_bytes and
memory_swap_bytes as a JSON object; comparisons require equal effective
Expand Down Expand Up @@ -81,7 +81,7 @@ try {
}
const record = {
schema: 'durable-workflow.server-parity-record/v1', target: values.target, mode: values.mode,
runner_revision: values['runner-revision'], artifacts: json(values.artifacts ?? resolve(root, 'tests/Fixtures/ServerParityBaselines/php-2.11.5.json')),
runner_revision: values['runner-revision'], artifacts: json(values.artifacts ?? resolve(root, 'tests/Fixtures/ServerParityBaselines/php-2.11.7.json')),
started_at: new Date().toISOString(), outcome: 'runner-blocked', fixture_hashes: hashes, cases: [],
database_resource_limits: parseDatabaseResourceLimits(values['database-resource-limits']),
};
Expand Down
2 changes: 1 addition & 1 deletion scripts/conformance/server-parity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ Docker kill and verifies exit code 137 before restarting the native process.

For an adapter installed elsewhere, set `DW_PARITY_SDK_AUTOLOAD` to its locked
`vendor/autoload.php`. `--artifacts` selects a frozen tuple manifest. The default
is `tests/Fixtures/ServerParityBaselines/php-2.11.5.json`; its SDK version must match
is `tests/Fixtures/ServerParityBaselines/php-2.11.7.json`; its SDK version must match
the installed lock. A frozen tuple does not prove every listed consumer ran.
The original `tests/Fixtures/ServerParity/php-baseline.json` remains an immutable
2.5.13 / Workflow 2.5.4 reference, together with its known NUL reproducer outside
Expand Down
2 changes: 1 addition & 1 deletion scripts/conformance/server-parity/native-image-corpus.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ scope="dw-parity-${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}-${GITHUB_JOB:-
export RESOURCE_SCOPE="$scope"
export DW_PARITY_RUNNER_REVISION
DW_PARITY_RUNNER_REVISION=$(git rev-parse HEAD)
image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.5.json)
image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json)

# CI's corpus selection: the reviewed corpus without service-calls, which
# records separately below with PHP's maintenance loop stopped (Server #796),
Expand Down
2 changes: 1 addition & 1 deletion scripts/conformance/server-parity/role-profile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ test -n "$RESOURCE_SCOPE"
scope_identity="$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT-$GITHUB_JOB"
case "$RESOURCE_SCOPE" in *"$scope_identity"*) ;; *) exit 2 ;; esac
backend="$1"
image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.5.json)
image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json)
rust_image=mirror.gcr.io/library/rust@sha256:ba81bc3eaa4422af576c0262515d96b0111a628a6ccc2c86557cf55c9a4bbee0
fixture=tests/Fixtures/ServerParityProfiles/role-tokens/admission-role-tokens.json
credentials=(-e DW_AUTH_TOKEN=parity-role-legacy -e DW_WORKER_TOKEN=parity-role-worker
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ name="${3:-update-validation}"
[[ -n "$DW_PARITY_RUNNER_REVISION" ]]
fixture="tests/Fixtures/ServerParity/$name.json"
test -f "$fixture"
image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.5.json)
image=$(jq -r '.server.images["linux/amd64"] | sub("^durableworkflow/server@"; "ghcr.io/durable-workflow/server@")' tests/Fixtures/ServerParityBaselines/php-2.11.7.json)
. scripts/conformance/server-parity/php-maintenance-window.sh

prefix=parity-v1
Expand Down
10 changes: 5 additions & 5 deletions tests/Fixtures/ServerParity/waterline-import.json
Original file line number Diff line number Diff line change
Expand Up @@ -4535,7 +4535,7 @@
"task_problem_badge": null,
"exception_count": 1,
"history_event_count": 6,
"history_size_bytes": 5431,
"history_size_bytes": 4713,
"history_fan_out": 0,
"continue_as_new_recommended": false,
"history_budget_pressure": "ok",
Expand Down Expand Up @@ -5604,7 +5604,7 @@
"task_problem_badge": null,
"exception_count": 0,
"history_event_count": 2,
"history_size_bytes": 2683,
"history_size_bytes": 2445,
"history_fan_out": 0,
"continue_as_new_recommended": false,
"history_budget_pressure": "ok",
Expand Down Expand Up @@ -6696,7 +6696,7 @@
"task_problem_badge": null,
"exception_count": 1,
"history_event_count": 6,
"history_size_bytes": 5425,
"history_size_bytes": 4707,
"history_fan_out": 0,
"continue_as_new_recommended": false,
"history_budget_pressure": "ok",
Expand Down Expand Up @@ -7909,7 +7909,7 @@
"task_problem_badge": null,
"exception_count": 1,
"history_event_count": 8,
"history_size_bytes": 6242,
"history_size_bytes": 5336,
"history_fan_out": 0,
"continue_as_new_recommended": false,
"history_budget_pressure": "ok",
Expand Down Expand Up @@ -8955,7 +8955,7 @@
"task_problem_badge": null,
"exception_count": 0,
"history_event_count": 2,
"history_size_bytes": 1856,
"history_size_bytes": 1668,
"history_fan_out": 0,
"continue_as_new_recommended": false,
"history_budget_pressure": "ok",
Expand Down
20 changes: 20 additions & 0 deletions tests/Fixtures/ServerParityBaselines/php-2.11.7.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"schema": "durable-workflow.server-port-artifacts/v1",
"server": {
"version": "2.11.7",
"source_commit": "f55f90e54a213872f1c164a1b22c082d859c18aa",
"images": {
"linux/amd64": "durableworkflow/server@sha256:be7c2288ff935396262d743590530b204fac11b76582e56a525cc81a8da840d3",
"linux/arm64": "durableworkflow/server@sha256:7dbc3db050fec05ea5cd10e07ac8399ecf3e285d593bca32979a2320f02a104f"
}
},
"workflow": "2.15.7",
"sdk_php": "2.2.6",
"sdk_python": "2.5.0",
"sdk_rust": "3.4.1",
"cli": "2.2.0",
"waterline": "2.3.1",
"sample_app_commit": "4427a537e092f9d92c96d2a4867b9186e0e75ccd",
"capacity_suite": "1.9.0",
"performance_status": "not_measured"
}
Original file line number Diff line number Diff line change
Expand Up @@ -4419,7 +4419,7 @@
"task_problem_badge": null,
"exception_count": 1,
"history_event_count": 6,
"history_size_bytes": 5431,
"history_size_bytes": 4713,
"history_fan_out": 0,
"continue_as_new_recommended": false,
"history_budget_pressure": "ok",
Expand Down Expand Up @@ -5488,7 +5488,7 @@
"task_problem_badge": null,
"exception_count": 0,
"history_event_count": 2,
"history_size_bytes": 2683,
"history_size_bytes": 2445,
"history_fan_out": 0,
"continue_as_new_recommended": false,
"history_budget_pressure": "ok",
Expand Down Expand Up @@ -6580,7 +6580,7 @@
"task_problem_badge": null,
"exception_count": 1,
"history_event_count": 6,
"history_size_bytes": 5425,
"history_size_bytes": 4707,
"history_fan_out": 0,
"continue_as_new_recommended": false,
"history_budget_pressure": "ok",
Expand Down Expand Up @@ -7793,7 +7793,7 @@
"task_problem_badge": null,
"exception_count": 1,
"history_event_count": 8,
"history_size_bytes": 6242,
"history_size_bytes": 5336,
"history_fan_out": 0,
"continue_as_new_recommended": false,
"history_budget_pressure": "ok",
Expand Down Expand Up @@ -8839,7 +8839,7 @@
"task_problem_badge": null,
"exception_count": 0,
"history_event_count": 2,
"history_size_bytes": 1856,
"history_size_bytes": 1668,
"history_fan_out": 0,
"continue_as_new_recommended": false,
"history_budget_pressure": "ok",
Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/ServerParityImagePullTest.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ test('each container-heavy fixture job pulls its pinned images first, with retri
const pull = steps[1];
assert.match(pull, /- name: Pull the pinned images, retrying registry errors\n/, name);
assert.match(pull, /bash scripts\/ci\/pull-images\.sh \\\n/, name);
assert.match(pull, /"\$\(baseline php-2\.11\.5\)"/, name);
assert.match(pull, /"\$\(baseline php-2\.11\.7\)"/, name);
for (const image of extra) assert.ok(pull.includes(image), `${name} pulls ${image}`);
// Every Rust image the job runs is in its pull list, so a digest bump
// cannot leave the pull behind.
Expand Down
5 changes: 3 additions & 2 deletions tests/Unit/ServerParityWaterlineImportContractTest.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import test from 'node:test';
import {checkWaterlineImport} from '../../scripts/conformance/server-parity/waterline-import-contract.mjs';

const fixture = JSON.parse(readFileSync(new URL('../Fixtures/ServerParity/waterline-import.json', import.meta.url)));
// The observation published Server 2.11.1 returned for this fixture.
// The observation published Server 2.11.7 returned for this fixture (2.11.1
// returned the same answers with the pre-#770 history sizes).
const recorded = JSON.parse(readFileSync(
new URL('../Fixtures/ServerParityExpected/waterline-import-observation.json', import.meta.url)));
const id = 'pr1-waterline-import';
Expand All @@ -15,7 +16,7 @@ const observation = (change = () => {}) => {
};
const check = state => checkWaterlineImport(fixture, state, id);

test('the published 2.11.1 recording satisfies the contract', () => {
test('the published 2.11.7 recording satisfies the contract', () => {
assert.deepStrictEqual(check(observation()), {scope: 'waterline_import'});
});

Expand Down
Loading