Skip to content

Commit 56aad9f

Browse files
authored
Merge pull request #868 from durable-workflow/rust-325-bridge-exact
Rust #325: compare bridge-adapters exactly against published 2.11.7
2 parents 6af963d + e28a63c commit 56aad9f

5 files changed

Lines changed: 56 additions & 225 deletions

File tree

‎docs/rust-server-port.md‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,25 +51,25 @@ Updated October 27, 2026.
5151
fixtures accept published PHP's typed `429 long_poll_capacity_exhausted`
5252
poll refusal, and retry a locked Composer install after a transient
5353
download failure (the two #813 merge-group failures).
54-
- Native's v1 projection byte meter in Core's canonical unit (#770) is
55-
prepared on `rust-325-wip/canonical-history-bytes`. It lands with the
56-
first reference that carries #812, because `waterline-import` records
57-
`history_size_bytes` from the reference.
54+
- `bridge-adapters` compares exactly: published 2.11.7 accepts and
55+
deduplicates the bridge update (#747) and refuses a foreign instance as
56+
unavailable (#777), so the published-defect answers of 2.11.1 to 2.11.5
57+
are no longer accepted.
5858
- **Still refused at takeover:** workflow-to-workflow messages
5959
(`workflow_messages`, written only by embedded PHP workflows) and external
6060
payload references. PHP keeps references in about 30 table and column
6161
locations in their URI form, so taking them over is its own project; the
6262
recommendation is that the release candidate keeps refusing them.
63-
- **Shared Server fixtures.** Native, embedded and published PHP 2.11.5 are
63+
- **Shared Server fixtures.** Native, embedded and published PHP 2.11.7 are
6464
compared on SQLite, MySQL 8.0, MariaDB 10.11 and PostgreSQL 17. Where the
6565
published reference itself misbehaves, an explicit boundary separates it
6666
from native, and the PHP owner has an issue for each case. Recent ones:
67-
- #777, a bridge start for an instance another namespace holds: 2.11.1
68-
to 2.11.4 name that namespace's type, 2.11.5 the namespace difference;
69-
#811 (after 2.11.5) answers "unavailable";
70-
- #747, a bridge update to an SDK-worker workflow;
7167
- #794, workflow-bound Nexus handlers.
7268

69+
Answered as intended by 2.11.7, so `bridge-adapters` compares exactly:
70+
#747 (a bridge update to an SDK-worker workflow) and #777 (a bridge start
71+
for an instance another namespace holds).
72+
7373
Closed by 2.11.5: Workflow #931 (`service-calls` repeats a key from
7474
another caller again, now refused 403), and the SQLite maintenance pause
7575
for `update-validation` (#723, #726). `service-calls` still records apart

‎scripts/conformance/server-parity/bridge-adapters-contract.mjs‎

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
import assert from 'node:assert/strict';
22

33
// Reviewed bridge adapter webhooks (BridgeAdapterController), recorded
4-
// against published Server 2.11.1. The fixture's `expected` holds the whole
4+
// against published Server 2.11.7. The fixture's `expected` holds the whole
55
// normalized observation, with the probe's workflow prefix as `{id}`, run IDs
66
// as labels and derived workflow IDs as `{derived-<key>}`. Every step
77
// compares exactly.
88
//
9-
// Published defect boundaries (fixture `published_defects`): a bridge update
10-
// skips external update admission, so an update to an SDK-worker workflow is
11-
// rejected workflow_definition_unavailable; and a start for an id another
12-
// namespace holds names that namespace's type (published 2.11.1 to 2.11.4)
13-
// or the namespace difference (2.11.5), not the intended unavailable
14-
// (Server #777). At each
15-
// defect's steps the published answers or the intended ones are accepted, as
16-
// a set per defect; nothing else is relaxed.
9+
// Published 2.11.1 to 2.11.5 answered two sets of steps otherwise: a bridge
10+
// update to an SDK-worker workflow was rejected workflow_definition_unavailable
11+
// (Server #747), and a start for an id another namespace holds named that
12+
// namespace's type or the namespace difference (Server #777). 2.11.7 gives
13+
// the intended answers, so neither is accepted any more.
1714
export function checkBridgeAdapters(fixture, observation, workflowId) {
1815
const state = observation.bridge_adapters;
1916
const projection = {scope: 'bridge_adapters'};
@@ -25,18 +22,6 @@ export function checkBridgeAdapters(fixture, observation, workflowId) {
2522
}
2623
const definition = fixture.bridge_adapters;
2724
const actual = JSON.parse(JSON.stringify(state).replaceAll(workflowId, '{id}'));
28-
for (const {published: defect} of Object.values(definition.published_defects)) {
29-
const steps = Object.keys(defect);
30-
let published = true;
31-
try {
32-
for (const step of steps) assert.deepStrictEqual(actual[step], defect[step]);
33-
} catch {
34-
published = false;
35-
}
36-
if (published) {
37-
for (const step of steps) actual[step] = definition.expected[step];
38-
}
39-
}
4025
for (const [step, expected] of Object.entries(definition.expected)) {
4126
assert.deepStrictEqual(actual[step], expected, `bridge adapters ${step}`);
4227
}

‎tests/Fixtures/ServerParity/bridge-adapters.json‎

Lines changed: 1 addition & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -34,154 +34,6 @@
3434
"bridge_adapters": {
3535
"workflow_type": "parity.v1.bridge",
3636
"empty_blob": "wwHioz3/VYAiNwwA",
37-
"published_defects": {
38-
"update_skips_external_admission": {
39-
"issue": "https://github.com/durable-workflow/server/issues/747",
40-
"summary": "BridgeAdapterController::updateWorkflow calls WorkflowControlPlane::update directly and skips ExternalWorkflowUpdateAdmission, which WorkflowController::update tries first, so a bridge update to a workflow served by an SDK worker is rejected workflow_definition_unavailable and recorded as UpdateRejected, and its replay is rejected again.",
41-
"published": {
42-
"update": {
43-
"status": 422,
44-
"response": {
45-
"schema": "durable-workflow.v2.bridge-adapter-outcome.contract",
46-
"version": 1,
47-
"adapter": "acme.crm:v1",
48-
"action": "update_workflow",
49-
"accepted": false,
50-
"outcome": "rejected",
51-
"reason": "workflow_definition_unavailable",
52-
"idempotency_key": "u-2",
53-
"target": {
54-
"workflow_id": "{id}-bra-order",
55-
"update_name": "bump"
56-
},
57-
"workflow_id": "{id}-bra-order",
58-
"run_id": "run-2",
59-
"control_plane_outcome": "rejected_workflow_definition_unavailable"
60-
}
61-
},
62-
"update_again": {
63-
"status": 422,
64-
"response": {
65-
"schema": "durable-workflow.v2.bridge-adapter-outcome.contract",
66-
"version": 1,
67-
"adapter": "acme.crm:v1",
68-
"action": "update_workflow",
69-
"accepted": false,
70-
"outcome": "rejected",
71-
"reason": "workflow_definition_unavailable",
72-
"idempotency_key": "u-2",
73-
"target": {
74-
"workflow_id": "{id}-bra-order",
75-
"update_name": "bump"
76-
},
77-
"workflow_id": "{id}-bra-order",
78-
"run_id": "run-2",
79-
"control_plane_outcome": "rejected_workflow_definition_unavailable"
80-
}
81-
},
82-
"history_event_types": [
83-
"StartAccepted",
84-
"WorkflowStarted",
85-
"StartAccepted",
86-
"StartRejected",
87-
"SignalWaitOpened",
88-
"SignalReceived",
89-
"SignalReceived",
90-
"UpdateRejected",
91-
"UpdateRejected",
92-
"UpdateRejected"
93-
]
94-
}
95-
},
96-
"foreign_instance_names_type": {
97-
"issue": "https://github.com/durable-workflow/server/issues/777",
98-
"summary": "DefaultWorkflowControlPlane::reserveInstance checks a reserved instance's type before its namespace, so a bridge start for an id another namespace holds is refused with that namespace's durable type named, and with a different-namespace message when the types match. Native checks the namespace first and refuses a foreign instance as unavailable, naming neither.",
99-
"published": {
100-
"start_other_type": {
101-
"status": 422,
102-
"response": {
103-
"schema": "durable-workflow.v2.bridge-adapter-outcome.contract",
104-
"version": 1,
105-
"adapter": "acme.crm:v1",
106-
"action": "start_workflow",
107-
"accepted": false,
108-
"outcome": "rejected",
109-
"reason": "unknown_target",
110-
"idempotency_key": "k-6",
111-
"message": "Workflow instance [{id}] is reserved for durable type [parity.v1.echo] and cannot be reused for [parity.v1.bridge].",
112-
"target": {
113-
"workflow_id": "{id}",
114-
"workflow_type": "parity.v1.bridge",
115-
"task_queue": "{id}-bra-queue"
116-
}
117-
}
118-
},
119-
"start_other_namespace": {
120-
"status": 422,
121-
"response": {
122-
"schema": "durable-workflow.v2.bridge-adapter-outcome.contract",
123-
"version": 1,
124-
"adapter": "acme.crm:v1",
125-
"action": "start_workflow",
126-
"accepted": false,
127-
"outcome": "rejected",
128-
"reason": "unknown_target",
129-
"idempotency_key": "k-7",
130-
"message": "Workflow instance [{id}] cannot be reused with a different namespace.",
131-
"target": {
132-
"workflow_id": "{id}",
133-
"workflow_type": "parity.v1.echo",
134-
"task_queue": "{id}-bra-queue"
135-
}
136-
}
137-
}
138-
}
139-
},
140-
"foreign_instance_names_namespace": {
141-
"issue": "https://github.com/durable-workflow/server/issues/777",
142-
"summary": "Published 2.11.5's Core 2.15.1 checks a reserved instance's namespace before its type, but its BridgeAdapterController still echoes the control plane's message, so a bridge start for an id another namespace holds is refused with a different-namespace message whatever its type. Server #811 (after 2.11.5) refuses it as unavailable, naming neither the type nor the namespace.",
143-
"published": {
144-
"start_other_type": {
145-
"status": 422,
146-
"response": {
147-
"schema": "durable-workflow.v2.bridge-adapter-outcome.contract",
148-
"version": 1,
149-
"adapter": "acme.crm:v1",
150-
"action": "start_workflow",
151-
"accepted": false,
152-
"outcome": "rejected",
153-
"reason": "unknown_target",
154-
"idempotency_key": "k-6",
155-
"message": "Workflow instance [{id}] cannot be reused with a different namespace.",
156-
"target": {
157-
"workflow_id": "{id}",
158-
"workflow_type": "parity.v1.bridge",
159-
"task_queue": "{id}-bra-queue"
160-
}
161-
}
162-
},
163-
"start_other_namespace": {
164-
"status": 422,
165-
"response": {
166-
"schema": "durable-workflow.v2.bridge-adapter-outcome.contract",
167-
"version": 1,
168-
"adapter": "acme.crm:v1",
169-
"action": "start_workflow",
170-
"accepted": false,
171-
"outcome": "rejected",
172-
"reason": "unknown_target",
173-
"idempotency_key": "k-7",
174-
"message": "Workflow instance [{id}] cannot be reused with a different namespace.",
175-
"target": {
176-
"workflow_id": "{id}",
177-
"workflow_type": "parity.v1.echo",
178-
"task_queue": "{id}-bra-queue"
179-
}
180-
}
181-
}
182-
}
183-
}
184-
},
18537
"expected": {
18638
"applicable": true,
18739
"namespace": "{id}-bra",
@@ -628,5 +480,5 @@
628480
]
629481
}
630482
},
631-
"contract": "Bridge adapter webhooks (BridgeAdapterController) as published Server 2.11.1 serves them, in their own namespace: the adapter name, payload and action refusals (422, the target redacted to its routing fields); start_workflow accepted (202), returned as a duplicate (200) under the default policy, rejected under reject_duplicate (422), with a workflow ID derived from the adapter and the idempotency key when none is given, and on the default queue for an unconfigured type; signal_workflow and update_workflow refused for an unknown workflow or a malformed target, accepted (202), and deduplicated (200) by adapter, action, idempotency key and target name. Run IDs are labels in order of first appearance and derived workflow IDs are checked against PHP's derivation. Two published defects are bounded: a bridge update to an SDK-worker workflow skips external update admission and is rejected workflow_definition_unavailable, and a start for an id another namespace holds names that namespace's type (published 2.11.1 to 2.11.4) or the namespace difference (2.11.5); for each, the published answers or the intended ones (acceptance and deduplication; a foreign instance refused as unavailable) are accepted. Embedded applications have no HTTP bridge adapters."
483+
"contract": "Bridge adapter webhooks (BridgeAdapterController) as published Server 2.11.7 serves them, in their own namespace: the adapter name, payload and action refusals (422, the target redacted to its routing fields); start_workflow accepted (202), returned as a duplicate (200) under the default policy, rejected under reject_duplicate (422), with a workflow ID derived from the adapter and the idempotency key when none is given, and on the default queue for an unconfigured type; signal_workflow and update_workflow refused for an unknown workflow or a malformed target, accepted (202), and deduplicated (200) by adapter, action, idempotency key and target name. Run IDs are labels in order of first appearance and derived workflow IDs are checked against PHP's derivation. A bridge update to an SDK-worker workflow passes external update admission like the regular update route, so it is accepted and deduplicated, and a start for an id another namespace holds is refused as unavailable, naming neither its type nor its namespace (#747 and #777; published 2.11.1 to 2.11.5 answered both otherwise). Every step compares exactly. Embedded applications have no HTTP bridge adapters."
632484
}

‎tests/Fixtures/ServerParityExpected/bridge-adapters-observation.json‎

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@
113113
"outcome": "rejected",
114114
"reason": "unknown_target",
115115
"idempotency_key": "k-6",
116-
"message": "Workflow instance [pr1-bridge-adapters] is reserved for durable type [parity.v1.echo] and cannot be reused for [parity.v1.bridge].",
116+
"message": "Workflow instance [pr1-bridge-adapters] is unavailable.",
117117
"target": {
118118
"workflow_id": "pr1-bridge-adapters",
119119
"workflow_type": "parity.v1.bridge",
@@ -132,7 +132,7 @@
132132
"outcome": "rejected",
133133
"reason": "unknown_target",
134134
"idempotency_key": "k-7",
135-
"message": "Workflow instance [pr1-bridge-adapters] cannot be reused with a different namespace.",
135+
"message": "Workflow instance [pr1-bridge-adapters] is unavailable.",
136136
"target": {
137137
"workflow_id": "pr1-bridge-adapters",
138138
"workflow_type": "parity.v1.echo",
@@ -375,43 +375,40 @@
375375
}
376376
},
377377
"update": {
378-
"status": 422,
378+
"status": 202,
379379
"response": {
380380
"schema": "durable-workflow.v2.bridge-adapter-outcome.contract",
381381
"version": 1,
382382
"adapter": "acme.crm:v1",
383383
"action": "update_workflow",
384-
"accepted": false,
385-
"outcome": "rejected",
386-
"reason": "workflow_definition_unavailable",
384+
"accepted": true,
385+
"outcome": "accepted",
387386
"idempotency_key": "u-2",
388387
"target": {
389388
"workflow_id": "pr1-bridge-adapters-bra-order",
390389
"update_name": "bump"
391390
},
392391
"workflow_id": "pr1-bridge-adapters-bra-order",
393-
"run_id": "run-2",
394-
"control_plane_outcome": "rejected_workflow_definition_unavailable"
392+
"run_id": "run-2"
395393
}
396394
},
397395
"update_again": {
398-
"status": 422,
396+
"status": 200,
399397
"response": {
400398
"schema": "durable-workflow.v2.bridge-adapter-outcome.contract",
401399
"version": 1,
402400
"adapter": "acme.crm:v1",
403401
"action": "update_workflow",
404402
"accepted": false,
405-
"outcome": "rejected",
406-
"reason": "workflow_definition_unavailable",
403+
"outcome": "duplicate",
407404
"idempotency_key": "u-2",
408405
"target": {
409406
"workflow_id": "pr1-bridge-adapters-bra-order",
410407
"update_name": "bump"
411408
},
412409
"workflow_id": "pr1-bridge-adapters-bra-order",
413410
"run_id": "run-2",
414-
"control_plane_outcome": "rejected_workflow_definition_unavailable"
411+
"control_plane_outcome": "deduped_existing_command"
415412
}
416413
},
417414
"update_undeclared": {
@@ -442,8 +439,7 @@
442439
"SignalWaitOpened",
443440
"SignalReceived",
444441
"SignalReceived",
445-
"UpdateRejected",
446-
"UpdateRejected",
442+
"UpdateAccepted",
447443
"UpdateRejected"
448444
]
449445
}

0 commit comments

Comments
 (0)