diff --git a/.github/workflows/server-parity.yml b/.github/workflows/server-parity.yml index 79ce72269..281b88f36 100644 --- a/.github/workflows/server-parity.yml +++ b/.github/workflows/server-parity.yml @@ -867,6 +867,22 @@ jobs: DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.1 DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:bf8bed13f5f809ee3425233180a581353eac4683adbee5c4ecbf63bb18bbd102 run: bash scripts/conformance/server-parity/upgrade-source-cleanup.sh mysql + - name: Back up, take over and resume a published PHP 2.11.3 upgrade source + if: ${{ matrix.part == 'cohorts' }} + timeout-minutes: 8 + env: + DW_PARITY_UPGRADE_LABEL: php2113 + DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.3 + DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:923a8924ecad13485562d7af30dc87d7fd6d9d7d4758ec8113bac0766fa8603e + run: bash scripts/conformance/server-parity/upgrade-source-profile.sh mysql + - name: Retire PHP 2.11.3 upgrade source cohort + if: ${{ always() && matrix.part == 'cohorts' }} + timeout-minutes: 3 + env: + DW_PARITY_UPGRADE_LABEL: php2113 + DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.3 + DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:923a8924ecad13485562d7af30dc87d7fd6d9d7d4758ec8113bac0766fa8603e + run: bash scripts/conformance/server-parity/upgrade-source-cleanup.sh mysql - name: Start the native node the corrected marker cohort uses id: prepare_corrected if: ${{ matrix.part == 'corrected-markers' }} @@ -1517,6 +1533,22 @@ jobs: DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.1 DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:bf8bed13f5f809ee3425233180a581353eac4683adbee5c4ecbf63bb18bbd102 run: bash scripts/conformance/server-parity/upgrade-source-cleanup.sh pgsql + - name: Back up, take over and resume a published PHP 2.11.3 upgrade source + if: ${{ matrix.part == 'cohorts' }} + timeout-minutes: 8 + env: + DW_PARITY_UPGRADE_LABEL: php2113 + DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.3 + DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:923a8924ecad13485562d7af30dc87d7fd6d9d7d4758ec8113bac0766fa8603e + run: bash scripts/conformance/server-parity/upgrade-source-profile.sh pgsql + - name: Retire PHP 2.11.3 upgrade source cohort + if: ${{ always() && matrix.part == 'cohorts' }} + timeout-minutes: 3 + env: + DW_PARITY_UPGRADE_LABEL: php2113 + DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.3 + DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:923a8924ecad13485562d7af30dc87d7fd6d9d7d4758ec8113bac0766fa8603e + run: bash scripts/conformance/server-parity/upgrade-source-cleanup.sh pgsql - name: Start the native node the corrected marker cohort uses id: prepare_corrected if: ${{ matrix.part == 'corrected-markers' }} @@ -2182,6 +2214,24 @@ jobs: DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.1 DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:bf8bed13f5f809ee3425233180a581353eac4683adbee5c4ecbf63bb18bbd102 run: bash scripts/conformance/server-parity/upgrade-source-cleanup.sh sqlite + - name: Back up, take over and resume a published PHP 2.11.3 upgrade source + if: ${{ matrix.part == 'cohorts' }} + timeout-minutes: 8 + env: + RESOURCE_SCOPE: dw-parity-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} + DW_PARITY_UPGRADE_LABEL: php2113 + DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.3 + DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:923a8924ecad13485562d7af30dc87d7fd6d9d7d4758ec8113bac0766fa8603e + run: bash scripts/conformance/server-parity/upgrade-source-profile.sh sqlite + - name: Retire PHP 2.11.3 upgrade source cohort + if: ${{ always() && matrix.part == 'cohorts' }} + timeout-minutes: 3 + env: + RESOURCE_SCOPE: dw-parity-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} + DW_PARITY_UPGRADE_LABEL: php2113 + DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.11.3 + DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:923a8924ecad13485562d7af30dc87d7fd6d9d7d4758ec8113bac0766fa8603e + run: bash scripts/conformance/server-parity/upgrade-source-cleanup.sh sqlite - name: Start the native node the corrected marker cohort uses id: prepare_corrected if: ${{ matrix.part == 'corrected-markers' }} diff --git a/docs/rust-server-port-checkpoints/2026-10-26-maintenance-repair.md b/docs/rust-server-port-checkpoints/2026-10-26-maintenance-repair.md new file mode 100644 index 000000000..3115dd493 --- /dev/null +++ b/docs/rust-server-port-checkpoints/2026-10-26-maintenance-repair.md @@ -0,0 +1,43 @@ +# October 26, the repair pass runs on its own + +- **Found by.** A review of what native does without a request. The published + image's `scheduler` service (`docker-compose.yml`) loops every 10 seconds + through: + - `schedule:evaluate`; + - `workflow:v2:repair-pass`, unless `DW_V2_MATCHING_ROLE_QUEUE_WAKE` is false + or 0; + - activity timeout enforcement; + - external payload cleanup; + - history pruning. + + The parity reference runs the same loop every second. Native's ticker + already fired due timers and schedules every 250 ms, and enforced activity + and cooperative deadlines. The repair pass ran only when someone called + `POST /api/system/repair/pass`. So an idle run past its execution or run + deadline was never given the task that closes it, and overdue ready tasks + and due service calls waited for a request. +- **Change.** The ticker can now also run the repair pass with PHP's loop + throttle: at most once per 5 seconds, shared with requested passes that + respect it. + - **Opt-in.** It runs only with `DW_RUST_MAINTENANCE_LOOP=1`, which the + native image will set. A PHP server container never repairs on its own: + PHP's separate `scheduler` container does. So the bare binary keeps the + server role, and the Shared Server fixtures, which pair each native node + with a PHP server container, compare like with like. + - On PR #797's first run, a default-on loop repaired a task the corrected + marker cohort expects untouched, and raced `repair_status`'s staged + candidates. + - `DW_V2_MATCHING_ROLE_QUEUE_WAKE=false` (or `0`) still turns it off, as it + does PHP's. + - `Runtime::set_maintenance_repair` sets it for one runtime. +- **Tests.** + - `the_maintenance_loop_gives_an_overdue_idle_run_its_deadline_task` waits + for the loop alone to give an idle run past its deadline its workflow task. + - The execution suite passes with the loop off by default, and passed with + it on (558). + - Native passes, with the loop on, the shared fixtures most exposed to + background repair: repair-pass, the lease, cancellation and deadline + fixtures, worker sessions and service calls. +- **Still to do.** History pruning and external payload cleanup also run in + PHP's loop; native runs them only on request (`POST + /api/system/retention/pass` and its payload cleanup). diff --git a/docs/rust-server-port-checkpoints/2026-10-26-php-2111-takeover.md b/docs/rust-server-port-checkpoints/2026-10-26-php-2111-takeover.md index a44d8478d..631a54615 100644 --- a/docs/rust-server-port-checkpoints/2026-10-26-php-2111-takeover.md +++ b/docs/rust-server-port-checkpoints/2026-10-26-php-2111-takeover.md @@ -82,5 +82,6 @@ Before, it reported the three service-call counters as zeros. `the_repair_pass_times_out_calls_past_their_deadline` covers it. - **Still refused.** Takeover still refuses a source holding rows native - cannot carry over yet: runtime credentials, message streams, update - validation tasks and external payloads. + cannot carry over yet: message streams, update validation tasks and + external payloads. Runtime credentials carried over later the same day + ([2026-10-26-takeover-runtime-credentials.md](2026-10-26-takeover-runtime-credentials.md)). diff --git a/docs/rust-server-port-checkpoints/2026-10-26-php-2113-takeover.md b/docs/rust-server-port-checkpoints/2026-10-26-php-2113-takeover.md new file mode 100644 index 000000000..f842744c2 --- /dev/null +++ b/docs/rust-server-port-checkpoints/2026-10-26-php-2113-takeover.md @@ -0,0 +1,60 @@ +# October 26, takeover of published Server 2.11.3 + +- **Before.** Native refused every published 2.11.3 database as + `upgrade_source_catalog_unqualified`. Server 2.11.3 (Core 2.14.2) was + published today, image index + `sha256:923a8924ecad13485562d7af30dc87d7fd6d9d7d4758ec8113bac0766fa8603e`. +- **Layouts name their parent.** `SOURCE_LAYOUTS` was a chain: each delta + applied to the layout before it. 2.11.3 has to apply to both 2.11.1 + variants, so each layout now names the layout its delta applies to. + `layout_lineage` gives the path from the frozen layout, and + `every_recorded_layout_descends_from_an_earlier_one` checks the order. + - `php-2.11.3` descends from fresh `php-2.11.1`. + - `php-2.11.3-from-2.9.7` descends from `php-2.11.1-from-2.9.7`. +- **Captured from real databases.** The published images bootstrapped and + migrated SQLite, PostgreSQL 17, MySQL 8.0 and MariaDB 10.11 databases along + five paths: + - fresh 2.11.3; + - 2.11.1 then 2.11.3; + - 2.8.0 then 2.11.3; + - 2.9.7 then 2.11.3; + - 2.9.7, 2.11.1, then 2.11.3. + + Each path gave the same delta from its parent on every backend. The first + three end at `php-2.11.3` and the last two at `php-2.11.3-from-2.9.7`. The + native binary then inspected, fingerprinted and took over all five real + SQLite databases. +- **Recipe** (details in [the layout README](../../rust/upgrade/php-2.11.3/README.md)). + - Each active session's recorded holder incarnation is copied to + `dw_worker_session_holders`. Earlier releases record none, so takeover + derives the holder from the current registration. A 2.11.3 session that + PHP already orphaned stays orphaned. + - `message_full` is counted (`full_failure_messages_removed`) and dropped. + - The idempotency digest and its index are dropped. + - Completed dispatches are counted (`service_call_dispatches_removed`) and + the outbox is dropped. A scheduled or claimed dispatch refuses takeover + before any change (`upgrade_source_service_call_dispatches_pending`), and + inspection reports `service_call_dispatch_inspection`. + - `reclaim_at` and its index are dropped. + - MySQL and MariaDB run this as journal step 10. Each part checks what is + left to do, so a resumed step repeats safely. +- **Tests.** + - SQLite: `takeover_converts_php_2113_databases` covers both layouts, with + a held session, a replaced holder, a full failure message, a completed + dispatch and an idempotency digest. + `takeover_refuses_a_php_2113_dispatch_still_pending` checks that a + pending dispatch refuses and leaves the file byte-identical. + - PostgreSQL, MySQL and MariaDB: + `qualification_takeover_converts_php_2113_and_native_opens_it`. + - MySQL and MariaDB: the step-kill test also kills a 2.11.3 takeover + after step 9 and resumes it. + - CI: a `php2113` upgrade cohort backs up, takes over and resumes a + published 2.11.3 source on SQLite, PostgreSQL and MySQL. +- **Not yet native.** The parity reference is still 2.11.1. Core 2.14 also + changed behavior that native does not follow yet: + - full failure-message storage on write; + - service-call scheduling through the outbox; + - the idempotency scope of target namespace, names and key, with + `service_call_idempotency_scope_conflict`. + + These belong to the parity move to 2.11.3. diff --git a/docs/rust-server-port-checkpoints/2026-10-26-takeover-runtime-credentials.md b/docs/rust-server-port-checkpoints/2026-10-26-takeover-runtime-credentials.md new file mode 100644 index 000000000..24fb6ae0a --- /dev/null +++ b/docs/rust-server-port-checkpoints/2026-10-26-takeover-runtime-credentials.md @@ -0,0 +1,28 @@ +# October 26, takeover keeps runtime credentials + +- **Before.** Takeover refused any PHP database holding a + `runtime_credentials` row (`upgrade_source_runtime_credentials_unsupported`). + An operator who had issued worker tokens through + `/api/runtime-credentials` could not cut over without + revoking them all and reissuing tokens to every worker. +- **Why it is safe to keep them.** Native serves runtime credentials from the + same `runtime_credentials` table, with the same columns. PHP stores + `hash('sha256', $token)` as lowercase hex; native looks a token up by the same + digest. Expiry, revocation, roles, tenant and claims are read from the same + columns. So the rows need no conversion, and a token PHP issued keeps + working after takeover. +- **Change.** The refusal is removed. Takeover leaves the table as it is. +- **Tests.** `PHP_CREDENTIAL_ROWS` stores one live and one revoked credential + the way PHP does, with digests computed outside native. After takeover the + live PHP token authenticates with its subject, roles, tenant and claims, + and the revoked one is refused: + - SQLite: `takeover_keeps_php_runtime_credentials_that_native_then_accepts`; + - PostgreSQL, MySQL and MariaDB: the 2.11.1 takeover qualification, for + both the fresh and the upgraded-from-2.9.7 layouts. +- **Update validation tasks** carry over too (October 27). Native validates + updates in the same `workflow_update_validation_tasks` rows and keeps no + state of its own, so takeover leaves them as they are. A pending task whose + caller was waiting on PHP is then what a PHP restart leaves: a validator may + still claim it, and a caller's retry with the same request reads its + recorded outcome. `takeover_keeps_php_update_validation_tasks` covers it. +- **Still refused.** Message streams and external payloads. diff --git a/docs/rust-server-port.md b/docs/rust-server-port.md index e51ed7834..5c83a58a6 100644 --- a/docs/rust-server-port.md +++ b/docs/rust-server-port.md @@ -489,6 +489,16 @@ contract fixture before its correction. drops the three columns; the verified backup keeps them ([#325 decision](https://github.com/durable-workflow/server/issues/325#issuecomment-6306545520), [checkpoint](rust-server-port-checkpoints/2026-10-26-php-2111-takeover.md)). +- **Server 2.11.3 (Core 2.14.2) takeover.** Takeover recognizes both + published 2.11.3 layouts, fresh and created by 2.9.x. It copies each active + session's recorded holder, drops full failure messages (counted; native + reads the labelled preview, as PHP's older readers do), the service-call + idempotency digest, completed dispatches and the stream reclaim deadline, + and refuses while a dispatch is pending + ([checkpoint](rust-server-port-checkpoints/2026-10-26-php-2113-takeover.md)). + The parity reference is still 2.11.1. Core 2.14's new behavior (full + failure-message storage, service-call scheduling and idempotency scope) + is native work for the move to 2.11.3. - **Stop signal in the published image.** The published Server image declares `STOPSIGNAL SIGWINCH`, Apache's graceful stop. Native stops gracefully on `SIGTERM` or `SIGINT` and ignores `SIGWINCH`, so `docker stop` on the diff --git a/docs/rust-server-upgrade-takeover.md b/docs/rust-server-upgrade-takeover.md index 15dbfed31..7439f085a 100644 --- a/docs/rust-server-upgrade-takeover.md +++ b/docs/rust-server-upgrade-takeover.md @@ -77,6 +77,16 @@ inspection is `unqualified` and takeover refuses with `upgrade_source_external_payload_storage_unsupported` before any change. A disabled policy does not block takeover. +PHP 2.11.3 (Core 2.14) schedules a service call's handler in the +`workflow_service_call_dispatches` outbox and dispatches it in a later pass. +Native runs the handler when it accepts the call, so it cannot resume a +dispatch that is still scheduled or claimed. Every inspection reports +`service_call_dispatch_inspection`: the number of dispatches and of pending +ones, without naming calls. A pending dispatch makes the inspection +`unqualified`, and takeover refuses with +`upgrade_source_service_call_dispatches_pending` before any change. Let PHP +finish them before the stop. Completed dispatches do not block takeover. + ## Exclusive transaction per backend The marker, the fingerprint check and the schema changes must not interleave diff --git a/rust/src/runtime/execution.rs b/rust/src/runtime/execution.rs index a5678ad41..56e20d1e4 100644 --- a/rust/src/runtime/execution.rs +++ b/rust/src/runtime/execution.rs @@ -52,6 +52,19 @@ struct TimerScheduler { stop: watch::Sender, task: Mutex>>, healthy: Arc, + /// Whether the ticker also runs the repair pass (`maintenance_repair_enabled`). + repair: Arc, +} + +/// Whether the ticker runs the repair pass, as PHP's separate `scheduler` +/// container does. A PHP server container never repairs on its own, so the +/// native binary takes that role only when asked: `DW_RUST_MAINTENANCE_LOOP=1` +/// (the native image sets it, having no separate scheduler). +/// `DW_V2_MATCHING_ROLE_QUEUE_WAKE=false` or `0` still turns it off, as it +/// turns off PHP's. +fn maintenance_repair_enabled() -> bool { + matches!(std::env::var("DW_RUST_MAINTENANCE_LOOP").as_deref(), Ok("1" | "true")) + && !matches!(std::env::var("DW_V2_MATCHING_ROLE_QUEUE_WAKE").as_deref(), Ok("false" | "0")) } impl TimerScheduler { @@ -61,11 +74,13 @@ impl TimerScheduler { stop, task: Mutex::new(None), healthy: Arc::new(AtomicBool::new(true)), + repair: Arc::new(AtomicBool::new(maintenance_repair_enabled())), } } fn start(&self, storage: Arc) { let mut stop = self.stop.subscribe(); let healthy = self.healthy.clone(); + let repair = self.repair.clone(); let task = tokio::spawn(async move { loop { tokio::select! { @@ -75,10 +90,15 @@ impl TimerScheduler { // retried from durable state, without logging payloads. // Timer and schedule transactions share the cross-node lock with // completion. CPU/deadline performance is unqualified. - let result = async { match &*storage { - Storage::Sqlite(store) => { store.fire_due_timers().await?; store.fire_due_schedules().await }, - Storage::Postgres(store) => { store.fire_due_timers().await?; store.fire_due_schedules().await }, - Storage::MySql(store) => { store.fire_due_timers().await?; store.fire_due_schedules().await }, + // Then PHP's maintenance loop (`workflow:v2:repair-pass`), + // at most once per repair throttle: run deadlines, task + // repair, activity and service-call timeouts. + let pass = repair.load(Ordering::Relaxed) + .then(|| super::repair_pass::PassRequest { respect_throttle: true, ..Default::default() }); + let result: Result<()> = async { match &*storage { + Storage::Sqlite(store) => { store.fire_due_timers().await?; store.fire_due_schedules().await?; if let Some(pass) = &pass { store.repair_pass(pass).await?; } Ok(()) }, + Storage::Postgres(store) => { store.fire_due_timers().await?; store.fire_due_schedules().await?; if let Some(pass) = &pass { store.repair_pass(pass).await?; } Ok(()) }, + Storage::MySql(store) => { store.fire_due_timers().await?; store.fire_due_schedules().await?; if let Some(pass) = &pass { store.repair_pass(pass).await?; } Ok(()) }, }}.await; let success = result.is_ok(); if healthy.swap(success,Ordering::Relaxed) && !success { @@ -188,6 +208,13 @@ impl Runtime { Ok(()) } + /// Turns the maintenance loop's repair pass on or off for this runtime, + /// as `DW_V2_MATCHING_ROLE_QUEUE_WAKE` does at startup. Timers, schedules + /// and deadlines still fire. + pub fn set_maintenance_repair(&self, enabled: bool) { + self.scheduler.repair.store(enabled, Ordering::Relaxed); + } + pub async fn open(database: &str, token: String) -> Result { Self::validate_token(&token)?; let external = super::ExternalPayloadConfig::from_env()?; diff --git a/rust/src/runtime/mysql.rs b/rust/src/runtime/mysql.rs index e7eaa662a..80c0669a4 100644 --- a/rust/src/runtime/mysql.rs +++ b/rust/src/runtime/mysql.rs @@ -328,20 +328,20 @@ async fn source_layout( canonical(include_str!("../../upgrade/php-2.9.7/mysql.json")), canonical(include_str!("../../upgrade/php-2.11.1/mysql.json")), canonical(include_str!("../../upgrade/php-2.11.1-from-2.9.7/mysql.json")), + canonical(include_str!("../../upgrade/php-2.11.3/mysql.json")), + canonical(include_str!("../../upgrade/php-2.11.3-from-2.9.7/mysql.json")), ], Flavor::MariaDb => [ canonical(include_str!("../../upgrade/php-2.8.0/mariadb.json")), canonical(include_str!("../../upgrade/php-2.9.7/mariadb.json")), canonical(include_str!("../../upgrade/php-2.11.1/mariadb.json")), canonical(include_str!("../../upgrade/php-2.11.1-from-2.9.7/mariadb.json")), + canonical(include_str!("../../upgrade/php-2.11.3/mariadb.json")), + canonical(include_str!("../../upgrade/php-2.11.3-from-2.9.7/mariadb.json")), ], }; - let layout = super::upgrade::source_layout( - &actual, - expected.clone(), - [&deltas[0], &deltas[1], &deltas[2], &deltas[3]], - |row| row.0.clone(), - ); + let deltas = deltas.each_ref().map(String::as_str); + let layout = super::upgrade::source_layout(&actual, expected.clone(), deltas, |row| row.0.clone()); // Laravel's separate mariadb connection stores the backup hold owner as // uuid rather than char(36); every release layout is otherwise the same. let layout = match (layout, flavor) { @@ -350,17 +350,14 @@ async fn source_layout( &expected, &canonical(include_str!("../../upgrade/mariadb-driver.json")), )?; - match super::upgrade::source_layout( - &actual, - driver, - [&deltas[0], &deltas[1], &deltas[2], &deltas[3]], - |row| row.0.clone(), - )? { + match super::upgrade::source_layout(&actual, driver, deltas, |row| row.0.clone())? { "frozen" => "frozen+mariadb-driver", "php-2.8.0" => "php-2.8.0+mariadb-driver", "php-2.9.7" => "php-2.9.7+mariadb-driver", "php-2.11.1" => "php-2.11.1+mariadb-driver", "php-2.11.1-from-2.9.7" => "php-2.11.1-from-2.9.7+mariadb-driver", + "php-2.11.3" => "php-2.11.3+mariadb-driver", + "php-2.11.3-from-2.9.7" => "php-2.11.3-from-2.9.7+mariadb-driver", _ => { return Err(refuse( StatusCode::CONFLICT, @@ -1246,6 +1243,8 @@ mod tests { include_str!("../../upgrade/php-2.9.7/mysql.json"), include_str!("../../upgrade/php-2.11.1/mysql.json"), include_str!("../../upgrade/php-2.11.1-from-2.9.7/mysql.json"), + include_str!("../../upgrade/php-2.11.3/mysql.json"), + include_str!("../../upgrade/php-2.11.3-from-2.9.7/mysql.json"), ], ), Flavor::MariaDb => ( @@ -1255,6 +1254,8 @@ mod tests { include_str!("../../upgrade/php-2.9.7/mariadb.json"), include_str!("../../upgrade/php-2.11.1/mariadb.json"), include_str!("../../upgrade/php-2.11.1-from-2.9.7/mariadb.json"), + include_str!("../../upgrade/php-2.11.3/mariadb.json"), + include_str!("../../upgrade/php-2.11.3-from-2.9.7/mariadb.json"), ], ), }; @@ -1264,17 +1265,12 @@ mod tests { .execute("SET FOREIGN_KEY_CHECKS=1; ALTER TABLE workflow_tasks DROP KEY dw_workflow_tasks_poll") .await .unwrap(); - let releases = match release { - None => 0, - Some("php-2.8.0") => 1, - Some("php-2.9.7") => 2, - Some("php-2.11.1") => 3, - Some(_) => 4, - }; + let lineage = super::super::upgrade::layout_lineage(release.unwrap_or("frozen")); + assert_eq!(lineage.is_empty(), release.is_none(), "{release:?} is a recorded layout"); // A replaced table can have inbound foreign keys (2.11.1's runs); they // name the table, so they hold again once it is recreated. connection.execute("SET FOREIGN_KEY_CHECKS=0").await.unwrap(); - for delta in &deltas[..releases] { + for delta in lineage.into_iter().map(|index| deltas[index]) { let delta: Value = serde_json::from_str(delta).unwrap(); for added in delta["add"].as_array().unwrap() { let (table, ddl) = (added[0].as_str().unwrap(), added[1].as_str().unwrap()); @@ -1395,6 +1391,9 @@ mod tests { "INSERT INTO workflow_worker_sessions(namespace,session_id,queue,status,lease_owner,lease_expires_at,ttl_expires_at,created_at,updated_at) VALUES ('default','held','q','active','current','2030-01-01 00:00:00','2030-01-01 00:00:00','2026-10-20 09:00:00','2026-10-20 09:00:00');", "INSERT INTO workflow_service_calls(id,namespace,endpoint_name,service_name,operation_name,caller_namespace,target_namespace,status,outcome,operation_mode,accepted_at,call_deadline_at,created_at,updated_at) VALUES ('01m5wvy8d1j3dtr13es05kcall','default','pay','billing','charge','default','default','accepted','accepted','async','2026-10-26 10:00:00.000000','2027-02-03 04:05:06.500000','2026-10-26 10:00:00','2026-10-26 10:00:00');", )).await.unwrap(); + for statement in crate::runtime::runtime_credentials::PHP_CREDENTIAL_ROWS { + sqlx::raw_sql(statement).execute(&mut connection).await.unwrap(); + } connection.close().await.unwrap(); let fingerprint = backup_fingerprint(&db).await; let report = takeover::take_over_mysql(db.options.clone(), &fingerprint, &TAKEOVER_QUEUES) @@ -1422,6 +1421,51 @@ mod tests { takeover::take_over_mysql(db.options.clone(), &fingerprint, &TAKEOVER_QUEUES).await, "upgrade_source_already_owned", ); + let runtime = crate::runtime::Runtime::open_mysql(db.options.clone(), "test-token".into()).await.unwrap(); + crate::runtime::runtime_credentials::assert_php_credentials_served(&runtime).await; + runtime.close().await; + db.remove().await; + } + } + + #[tokio::test] + #[ignore = "requires an isolated MySQL/MariaDB create-database role"] + async fn qualification_takeover_converts_php_2113_and_native_opens_it() { + for release in ["php-2.11.3", "php-2.11.3-from-2.9.7"] { + let db = Database::new().await; + php_layout(&db, Some(release)).await; + registrations(&db).await; + let recorded = super::super::worker_sessions::incarnation_digest("0123456789abcdef0123456789abcdef"); + let mut connection = db.connection().await; + for statement in [ + format!("INSERT INTO workflow_worker_sessions(namespace,session_id,queue,status,lease_owner,lease_owner_incarnation,lease_expires_at,ttl_expires_at,created_at,updated_at) VALUES ('default','held','q','active','current','{recorded}','2030-01-01 00:00:00','2030-01-01 00:00:00','2026-10-20 09:00:00','2026-10-20 09:00:00')"), + "INSERT INTO workflow_failures(id,workflow_run_id,source_kind,source_id,propagation_kind,exception_class,message,file,message_full,created_at,updated_at) VALUES ('01m5wvy8d1j3dtr13es05kfail','01jrun0000000000000000000a','activity_execution','a','activity','RuntimeException','preview','','base64:ZnVsbA==','2026-10-26 10:00:00','2026-10-26 10:00:00')".to_owned(), + "INSERT INTO workflow_service_calls(id,namespace,endpoint_name,service_name,operation_name,caller_namespace,target_namespace,status,outcome,operation_mode,idempotency_key,idempotency_scope_hash,accepted_at,completed_at,created_at,updated_at) VALUES ('01m5wvy8d1j3dtr13es05kcall','default','pay','billing','charge','default','default','completed','succeeded','async','order-1','0000000000000000000000000000000000000000000000000000000000000001','2026-10-26 10:00:00','2026-10-26 10:00:01','2026-10-26 10:00:00','2026-10-26 10:00:01')".to_owned(), + "INSERT INTO workflow_service_call_dispatches(service_call_id,state,dispatch_options,available_at,completed_at,created_at,updated_at) VALUES ('01m5wvy8d1j3dtr13es05kcall','completed','{}','2026-10-26 10:00:00','2026-10-26 10:00:01','2026-10-26 10:00:00','2026-10-26 10:00:01')".to_owned(), + ] { + connection.execute(sqlx::AssertSqlSafe(statement)).await.unwrap(); + } + connection.close().await.unwrap(); + let fingerprint = backup_fingerprint(&db).await; + let report = takeover::take_over_mysql(db.options.clone(), &fingerprint, &TAKEOVER_QUEUES) + .await + .unwrap(); + assert_eq!(report["status"], "taken_over", "{report}"); + assert_eq!(report["source_layout"], release); + assert_eq!(report["converted"]["session_holders_recorded"], 1, "{report}"); + assert_eq!(report["converted"]["full_failure_messages_removed"], 1, "{report}"); + assert_eq!(report["converted"]["service_call_dispatches_removed"], 1, "{report}"); + converted(&db).await; + let mut connection = db.connection().await; + let holder: String = sqlx::query_scalar("SELECT incarnation FROM dw_worker_session_holders WHERE namespace='default' AND session_id='held'") + .fetch_one(&mut connection).await.unwrap(); + assert_eq!(holder, recorded); + let left: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND (TABLE_NAME,COLUMN_NAME) IN (('workflow_failures','message_full'),('workflow_service_calls','idempotency_scope_hash'),('workflow_worker_sessions','lease_owner_incarnation'),('workflow_durable_streams','reclaim_at'))") + .fetch_one(&mut connection).await.unwrap(); + let dispatches: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='workflow_service_call_dispatches'") + .fetch_one(&mut connection).await.unwrap(); + assert_eq!((left, dispatches), (0, 0), "{release}: PHP 2.11.3's additions are gone"); + connection.close().await.unwrap(); db.remove().await; } } @@ -1590,14 +1634,16 @@ mod tests { #[ignore = "requires an isolated MySQL/MariaDB create-database role"] async fn qualification_takeover_killed_at_each_step_resumes_with_the_same_fingerprint() { use std::os::unix::process::ExitStatusExt; - for boundary in [ - "before-marker-row", - "after-step-1", - "after-step-3", - "after-step-8", + for (release, boundary) in [ + ("php-2.9.7", "before-marker-row"), + ("php-2.9.7", "after-step-1"), + ("php-2.9.7", "after-step-3"), + ("php-2.9.7", "after-step-8"), + // Killed before PHP 2.11.3's own step. + ("php-2.11.3", "after-step-9"), ] { let db = Database::new().await; - php_layout(&db, Some("php-2.9.7")).await; + php_layout(&db, Some(release)).await; registrations(&db).await; let fingerprint = backup_fingerprint(&db).await; let directory = tempfile::tempdir().unwrap(); @@ -1650,7 +1696,7 @@ mod tests { .await .unwrap(); assert_eq!(report["status"], "taken_over", "{boundary}: {report}"); - assert_eq!(report["source_layout"], "php-2.9.7", "{boundary}"); + assert_eq!(report["source_layout"], release, "{boundary}"); converted(&db).await; db.remove().await; } diff --git a/rust/src/runtime/postgres.rs b/rust/src/runtime/postgres.rs index 9eb4b02e1..e51964c4e 100644 --- a/rust/src/runtime/postgres.rs +++ b/rust/src/runtime/postgres.rs @@ -119,6 +119,8 @@ pub(super) async fn php_source_catalog(connection: &mut PgConnection) -> Result< include_str!("../../upgrade/php-2.9.7/postgres.json"), include_str!("../../upgrade/php-2.11.1/postgres.json"), include_str!("../../upgrade/php-2.11.1-from-2.9.7/postgres.json"), + include_str!("../../upgrade/php-2.11.3/postgres.json"), + include_str!("../../upgrade/php-2.11.3-from-2.9.7/postgres.json"), ], |row| (row.0.clone(), row.1.clone()), )?; @@ -846,6 +848,17 @@ mod tests { const TRACE_CARRIERS: &str = "ALTER TABLE workflow_runs ADD COLUMN observability_context json; ALTER TABLE workflow_commands ADD COLUMN observability_context json; ALTER TABLE workflow_tasks ADD COLUMN observability_context json;"; + /// PHP 2.11.3's changes (Core 2.14.2 and Server), in its migrations' order. + const RELEASE_2113: &str = "ALTER TABLE workflow_failures ADD COLUMN message_full text; + ALTER TABLE workflow_service_calls ADD COLUMN idempotency_scope_hash character(64); + CREATE UNIQUE INDEX wf_service_calls_idem_scope_unique ON workflow_service_calls (idempotency_scope_hash); + CREATE TABLE workflow_service_call_dispatches (service_call_id varchar(26) NOT NULL, state varchar(32) NOT NULL, dispatch_options json NOT NULL, attempt integer NOT NULL DEFAULT '1', max_attempts integer NOT NULL DEFAULT '1', available_at timestamp(6) without time zone NOT NULL, claim_generation bigint NOT NULL DEFAULT '0', claim_token character(64), claimed_at timestamp(6) without time zone, claim_expires_at timestamp(6) without time zone, authorized_catalog_revision character(64), authorized_policy_revision character(64), dispatch_attempted_at timestamp(6) without time zone, repair_available_at timestamp(6) without time zone, completed_at timestamp(6) without time zone, last_error text, created_at timestamp(6) without time zone, updated_at timestamp(6) without time zone, PRIMARY KEY (service_call_id)); + CREATE INDEX wf_service_dispatch_due_idx ON workflow_service_call_dispatches (state, available_at); + CREATE INDEX wf_service_dispatch_claim_idx ON workflow_service_call_dispatches (state, claim_expires_at); + CREATE INDEX wf_service_dispatch_repair_idx ON workflow_service_call_dispatches (state, repair_available_at); + ALTER TABLE workflow_worker_sessions ADD COLUMN lease_owner_incarnation character(64); + ALTER TABLE workflow_durable_streams ADD COLUMN reclaim_at timestamp(6) without time zone; + CREATE INDEX wf_durable_streams_ns_reclaim_idx ON workflow_durable_streams (namespace, reclaim_at, id);"; /// The frozen PHP layout with a release's changes applied in order. async fn php_release(db: &Database, changes: &[&'static str]) -> PgConnection { @@ -882,6 +895,9 @@ mod tests { .execute(&mut connection) .await .unwrap(); + for statement in crate::runtime::runtime_credentials::PHP_CREDENTIAL_ROWS { + sqlx::raw_sql(statement).execute(&mut connection).await.unwrap(); + } connection.close().await.unwrap(); let fingerprint = backup_fingerprint(&db).await; let report = takeover::take_over_postgres(db.options.clone(), &fingerprint, &TAKEOVER_QUEUES) @@ -906,6 +922,56 @@ mod tests { let storage = PostgresStorage::open(db.options.clone()).await.unwrap(); storage.close().await; PostgresStorage::check(db.options.clone()).await.unwrap(); + let runtime = crate::runtime::Runtime::open_postgres(db.options.clone(), "test-token".into()).await.unwrap(); + crate::runtime::runtime_credentials::assert_php_credentials_served(&runtime).await; + runtime.close().await; + db.remove().await; + } + } + + #[tokio::test] + #[ignore = "requires a disposable PostgreSQL; run the qualification_ tests explicitly"] + async fn qualification_takeover_converts_php_2113_and_native_opens_it() { + for (release, changes) in [ + ("php-2.11.3", [PHP_280_CHANGES, TRACE_CARRIERS, QUEUE_DELIVERY, RELEASE_2113]), + ("php-2.11.3-from-2.9.7", [PHP_280_CHANGES, QUEUE_DELIVERY, TRACE_CARRIERS, RELEASE_2113]), + ] { + let db = Database::new().await; + let mut connection = php_release(&db, &changes).await; + let recorded = super::super::worker_sessions::incarnation_digest("0123456789abcdef0123456789abcdef"); + for statement in [ + "INSERT INTO workflow_worker_registrations(worker_id,namespace,task_queue,runtime,registration_token,created_at,updated_at) VALUES ('current','default','q','php','0123456789abcdef0123456789abcdef','2026-10-20 09:00:00','2026-10-20 09:00:00')".to_owned(), + "INSERT INTO workflow_worker_registration_incarnations VALUES ('0123456789abcdef0123456789abcdef','default','current','active',0,NULL,'2026-10-20 09:00:00','2026-10-20 09:00:00')".to_owned(), + format!("INSERT INTO workflow_worker_sessions(namespace,session_id,queue,status,lease_owner,lease_owner_incarnation,lease_expires_at,ttl_expires_at,created_at,updated_at) VALUES ('default','held','q','active','current','{recorded}','2030-01-01 00:00:00','2030-01-01 00:00:00','2026-10-20 09:00:00','2026-10-20 09:00:00')"), + "INSERT INTO workflow_failures(id,workflow_run_id,source_kind,source_id,propagation_kind,exception_class,message,file,message_full,created_at,updated_at) VALUES ('01m5wvy8d1j3dtr13es05kfail','01jrun0000000000000000000a','activity_execution','a','activity','RuntimeException','preview','','base64:ZnVsbA==','2026-10-26 10:00:00','2026-10-26 10:00:00')".to_owned(), + "INSERT INTO workflow_service_calls(id,namespace,endpoint_name,service_name,operation_name,caller_namespace,target_namespace,status,outcome,operation_mode,idempotency_key,idempotency_scope_hash,accepted_at,completed_at,created_at,updated_at) VALUES ('01m5wvy8d1j3dtr13es05kcall','default','pay','billing','charge','default','default','completed','succeeded','async','order-1','0000000000000000000000000000000000000000000000000000000000000001','2026-10-26 10:00:00','2026-10-26 10:00:01','2026-10-26 10:00:00','2026-10-26 10:00:01')".to_owned(), + "INSERT INTO workflow_service_call_dispatches(service_call_id,state,dispatch_options,available_at,completed_at,created_at,updated_at) VALUES ('01m5wvy8d1j3dtr13es05kcall','completed','{}','2026-10-26 10:00:00','2026-10-26 10:00:01','2026-10-26 10:00:00','2026-10-26 10:00:01')".to_owned(), + ] { + sqlx::raw_sql(sqlx::AssertSqlSafe(statement)).execute(&mut connection).await.unwrap(); + } + connection.close().await.unwrap(); + let fingerprint = backup_fingerprint(&db).await; + let report = takeover::take_over_postgres(db.options.clone(), &fingerprint, &TAKEOVER_QUEUES) + .await + .unwrap(); + assert_eq!(report["status"], "taken_over", "{report}"); + assert_eq!(report["source_layout"], release); + assert_eq!(report["converted"]["session_holders_recorded"], 1, "{report}"); + assert_eq!(report["converted"]["full_failure_messages_removed"], 1, "{report}"); + assert_eq!(report["converted"]["service_call_dispatches_removed"], 1, "{report}"); + let mut connection = db.connection().await; + let holder: String = sqlx::query_scalar("SELECT incarnation FROM dw_worker_session_holders WHERE namespace='default' AND session_id='held'") + .fetch_one(&mut connection).await.unwrap(); + assert_eq!(holder, recorded); + let left: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='public' AND (table_name,column_name) IN (('workflow_failures','message_full'),('workflow_service_calls','idempotency_scope_hash'),('workflow_worker_sessions','lease_owner_incarnation'),('workflow_durable_streams','reclaim_at'))") + .fetch_one(&mut connection).await.unwrap(); + let dispatches: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public' AND table_name='workflow_service_call_dispatches'") + .fetch_one(&mut connection).await.unwrap(); + assert_eq!((left, dispatches), (0, 0), "{release}: PHP 2.11.3's additions are gone"); + connection.close().await.unwrap(); + let storage = PostgresStorage::open(db.options.clone()).await.unwrap(); + storage.close().await; + PostgresStorage::check(db.options.clone()).await.unwrap(); db.remove().await; } } diff --git a/rust/src/runtime/runtime_credentials.rs b/rust/src/runtime/runtime_credentials.rs index 8034ee416..2643c773e 100644 --- a/rust/src/runtime/runtime_credentials.rs +++ b/rust/src/runtime/runtime_credentials.rs @@ -393,6 +393,33 @@ where } } +/// Credentials as published PHP stores them: `hash('sha256', $token)` of +/// each token. The first is live until 2030, the second is revoked. +#[cfg(test)] +pub(crate) const PHP_CREDENTIAL_ROWS: [&str; 2] = [ + r#"INSERT INTO runtime_credentials(id,name,subject,roles,tenant,claims,token_prefix,token_hash,expires_at,created_at,updated_at) VALUES ('01m5wvy8d1j3dtr13es05kcred','worker key','worker-a','["worker"]','default','{"region":"eu"}','dw_live_abc','5ac9039987e0c53110302d98235eafd5b48403cc74f32a9dc365418eab15bfa2','2030-01-01 00:00:00','2026-10-26 10:00:00','2026-10-26 10:00:00')"#, + r#"INSERT INTO runtime_credentials(id,name,subject,roles,tenant,claims,token_prefix,token_hash,revoked_at,created_at,updated_at) VALUES ('01m5wvy8d1j3dtr13es05kgone','old key','worker-b','["worker"]','default','[]','dw_live_old','2b968f18b9fe6bbd5de84bd3dfa0cf8e2eaf41be6a2e9a8ca860cae3a28321e9','2026-10-26 11:00:00','2026-10-26 10:00:00','2026-10-26 11:00:00')"#, +]; + +/// What native accepts over `PHP_CREDENTIAL_ROWS` after a takeover: the live +/// PHP-issued token authenticates as its subject, and the revoked one does not. +#[cfg(test)] +pub(crate) async fn assert_php_credentials_served(runtime: &super::Runtime) { + let principal = runtime + .runtime_credential_principal("dw_live_abcdefghijklmnopqrstuvwxyz") + .await + .unwrap() + .expect("the PHP-issued token authenticates"); + assert_eq!( + (principal.subject.as_str(), principal.roles.as_slice(), principal.method, principal.tenant.as_deref(), &principal.claims), + ("worker-a", ["worker".to_owned()].as_slice(), "runtime-token", Some("default"), &json!({"region": "eu"})) + ); + assert!( + runtime.runtime_credential_principal("dw_live_oldoldoldoldoldoldoldold").await.unwrap().is_none(), + "a revoked key stays refused" + ); +} + #[cfg(test)] mod tests { use super::*; diff --git a/rust/src/runtime/schema.rs b/rust/src/runtime/schema.rs index 761260808..c94bd0358 100644 --- a/rust/src/runtime/schema.rs +++ b/rust/src/runtime/schema.rs @@ -124,6 +124,8 @@ pub(super) async fn php_source_catalog(connection: &mut SqliteConnection) -> Res include_str!("../../upgrade/php-2.9.7/sqlite.json"), include_str!("../../upgrade/php-2.11.1/sqlite.json"), include_str!("../../upgrade/php-2.11.1-from-2.9.7/sqlite.json"), + include_str!("../../upgrade/php-2.11.3/sqlite.json"), + include_str!("../../upgrade/php-2.11.3-from-2.9.7/sqlite.json"), ], |row| (row.0.clone(), row.1.clone()), )?; diff --git a/rust/src/runtime/upgrade.rs b/rust/src/runtime/upgrade.rs index 6ad8589c7..cbd0ffa98 100644 --- a/rust/src/runtime/upgrade.rs +++ b/rust/src/runtime/upgrade.rs @@ -2,6 +2,7 @@ pub mod external_reference; pub mod fingerprint; pub mod takeover; +mod dispatch_scan; mod payload_scan; mod schedule_scan; mod storage_scan; @@ -24,17 +25,54 @@ use tokio::time::{Instant, timeout, timeout_at}; const BUDGET: Duration = Duration::from_secs(30); -/// Published PHP layouts after the frozen one, oldest first. Each backend -/// records one delta per layout, applied to the layout before it. -pub(super) const SOURCE_LAYOUTS: [&str; 4] = ["php-2.8.0", "php-2.9.7", "php-2.11.1", "php-2.11.1-from-2.9.7"]; +/// Published PHP layouts after the frozen one, each with the layout its +/// recorded delta applies to. A parent comes before its children. Each backend +/// records one delta per layout, in this order. +/// +/// A release can have several layouts: core migrations run in file order, so +/// a database an older release created can order columns differently from a +/// fresh one. Each such variant descends from the matching older variant. +pub(super) const SOURCE_LAYOUTS: [(&str, &str); 6] = [ + ("php-2.8.0", "frozen"), + ("php-2.9.7", "php-2.8.0"), + ("php-2.11.1", "php-2.9.7"), + ("php-2.11.1-from-2.9.7", "php-2.11.1"), + ("php-2.11.3", "php-2.11.1"), + ("php-2.11.3-from-2.9.7", "php-2.11.1-from-2.9.7"), +]; + +/// The number of recorded layouts after the frozen one. +pub(super) const LAYOUTS: usize = SOURCE_LAYOUTS.len(); + +/// The recorded layouts from the oldest ancestor down to `name`, as indexes +/// into `SOURCE_LAYOUTS`; empty for the frozen layout or an unknown name. +pub(super) fn layout_lineage(name: &str) -> Vec { + let mut lineage = Vec::new(); + let mut current = name; + while let Some(index) = SOURCE_LAYOUTS.iter().position(|(layout, _)| *layout == current) { + lineage.insert(0, index); + current = SOURCE_LAYOUTS[index].1; + } + lineage +} + +/// Whether `layout` (a recorded layout, with or without the MariaDB driver +/// suffix) includes `release`'s schema changes: its lineage passes through +/// that release or one of its variants. +pub(super) fn has_release(layout: &str, release: &str) -> bool { + layout_lineage(layout.trim_end_matches("+mariadb-driver")).into_iter().any(|index| { + let name = SOURCE_LAYOUTS[index].0; + name == release || name.strip_prefix(release).is_some_and(|rest| rest.starts_with("-from-")) + }) +} /// Names the qualified layout `actual` matches exactly: the frozen layout, or -/// one reached by applying the recorded deltas in order. Rows are compared in +/// one reached by applying its lineage's recorded deltas. Rows are compared in /// `order`, so only presentation order, never a definition, is normalized. pub(super) fn source_layout( actual: &[T], frozen: Vec, - deltas: [&str; 4], + deltas: [&str; LAYOUTS], order: impl Fn(&T) -> K, ) -> Result<&'static str> where @@ -46,15 +84,20 @@ where rows }; let actual = sorted(actual); - let mut layout = sorted(&frozen); - if actual == layout { + let mut layouts = vec![sorted(&frozen)]; + if actual == layouts[0] { return Ok("frozen"); } - for (name, delta) in SOURCE_LAYOUTS.into_iter().zip(deltas) { - layout = sorted(&source_catalog_variant(&layout, delta)?); + for ((name, parent), delta) in SOURCE_LAYOUTS.into_iter().zip(deltas) { + let base = match SOURCE_LAYOUTS.iter().position(|(layout, _)| *layout == parent) { + Some(index) => &layouts[index + 1], + None => &layouts[0], + }; + let layout = sorted(&source_catalog_variant(base, delta)?); if actual == layout { return Ok(name); } + layouts.push(layout); } Err(refuse( StatusCode::CONFLICT, @@ -211,6 +254,7 @@ fn report( payload_inspection: Option, schedule_inspection: Value, storage_inspection: Value, + dispatch_inspection: Value, ) -> Result { let digest = Sha256::digest(serde_json::to_vec(catalog)?) .iter() @@ -243,6 +287,10 @@ fn report( report["status"] = json!("unqualified"); } report["storage_inspection"] = storage_inspection; + if dispatch_inspection["pending"] != 0 { + report["status"] = json!("unqualified"); + } + report["service_call_dispatch_inspection"] = dispatch_inspection; Ok(report) } @@ -417,6 +465,7 @@ pub(super) async fn sqlite_inventory( }; let schedule_inspection = schedule_scan::inspect::(&mut *tx).await?; let storage_inspection = storage_scan::inspect::(&mut *tx).await?; + let dispatch_inspection = dispatch_scan::inspect::(&mut *tx, &counts).await?; report( "sqlite", &catalog, @@ -426,6 +475,7 @@ pub(super) async fn sqlite_inventory( payload_inspection, schedule_inspection, storage_inspection, + dispatch_inspection, ) } @@ -690,6 +740,7 @@ pub(super) async fn mysql_inventory( }; let schedule_inspection = schedule_scan::inspect::(&mut *tx).await?; let storage_inspection = storage_scan::inspect::(&mut *tx).await?; + let dispatch_inspection = dispatch_scan::inspect::(&mut *tx, &counts).await?; // MySQL's metadata is not an MVCC snapshot. Refuse a changed visible // catalog rather than joining counts to a different schema. if mysql_source_catalog(&mut *tx, fenced).await?.0 != catalog { @@ -707,6 +758,7 @@ pub(super) async fn mysql_inventory( payload_inspection, schedule_inspection, storage_inspection, + dispatch_inspection, ) } @@ -775,6 +827,7 @@ pub(super) async fn postgres_inventory( }; let schedule_inspection = schedule_scan::inspect::(&mut *tx).await?; let storage_inspection = storage_scan::inspect::(&mut *tx).await?; + let dispatch_inspection = dispatch_scan::inspect::(&mut *tx, &counts).await?; report( "pgsql", &catalog, @@ -784,6 +837,7 @@ pub(super) async fn postgres_inventory( payload_inspection, schedule_inspection, storage_inspection, + dispatch_inspection, ) } diff --git a/rust/src/runtime/upgrade/dispatch_scan.rs b/rust/src/runtime/upgrade/dispatch_scan.rs new file mode 100644 index 000000000..666c8c03b --- /dev/null +++ b/rust/src/runtime/upgrade/dispatch_scan.rs @@ -0,0 +1,31 @@ +//! PHP 2.11.3's service-call dispatch outbox. PHP schedules a call's handler +//! there and a later pass dispatches it; native runs the handler when it +//! accepts the call, so it cannot resume a dispatch that is still scheduled or +//! claimed. A completed dispatch is only PHP's bookkeeping. The report counts +//! rows and never names calls. +use crate::runtime::{Result, backend::Backend}; +use serde_json::{Value, json}; +use sqlx::{Executor, IntoArguments}; +use std::collections::BTreeMap; + +const PENDING: &str = "SELECT COUNT(*) AS n FROM workflow_service_call_dispatches WHERE state<>'completed'"; + +pub(super) async fn inspect( + connection: &mut DB::Connection, + counts: &BTreeMap, +) -> Result +where + for<'c> &'c mut DB::Connection: Executor<'c, Database = DB>, + DB::Arguments: IntoArguments, +{ + let rows = counts.get("workflow_service_call_dispatches").copied().unwrap_or(0); + let pending = if rows == 0 { + 0 + } else { + let row = sqlx::query(sqlx::AssertSqlSafe(DB::statement(PENDING))) + .fetch_one(&mut *connection) + .await?; + DB::number(&row, "n")? + }; + Ok(json!({"dispatches": rows, "pending": pending})) +} diff --git a/rust/src/runtime/upgrade/takeover.rs b/rust/src/runtime/upgrade/takeover.rs index 3d8aec310..7c0c251ab 100644 --- a/rust/src/runtime/upgrade/takeover.rs +++ b/rust/src/runtime/upgrade/takeover.rs @@ -221,9 +221,7 @@ fn report(backend: &str, layout: &str, digest: &Value, inventory: &Value, conver /// Published PHP features native does not implement yet. A source holding any /// of their rows is refused, rather than taken over without them. Derived /// projections and PHP's own queue and poll bookkeeping are left in place. -const UNSUPPORTED_FEATURES: [(&str, &[&str]); 3] = [ - ("upgrade_source_runtime_credentials_unsupported", &["runtime_credentials"]), - ( +const UNSUPPORTED_FEATURES: [(&str, &[&str]); 1] = [ ( "upgrade_source_message_streams_unsupported", &[ "workflow_messages", @@ -233,7 +231,6 @@ const UNSUPPORTED_FEATURES: [(&str, &[&str]); 3] = [ "workflow_inbound_stream_items", ], ), - ("upgrade_source_update_validation_unsupported", &["workflow_update_validation_tasks"]), ]; /// Takeover needs every inspection result it relies on, and refuses what @@ -256,6 +253,14 @@ fn qualified(inventory: &Value) -> Result<&str> { "upgrade_source_external_payload_storage_unsupported", )); } + // A service call PHP 2.11.3 scheduled but has not dispatched would never + // run under native. + if inventory["service_call_dispatch_inspection"]["pending"] != 0 { + return Err(refuse( + StatusCode::CONFLICT, + "upgrade_source_service_call_dispatches_pending", + )); + } // A schedule native cannot run would stop firing after takeover. if inventory["schedule_inspection"]["unsupported"] != 0 { return Err(refuse( @@ -336,7 +341,7 @@ where fn php_incarnations(layout: &str) -> Result { match layout.trim_end_matches("+mariadb-driver") { "frozen" => Ok(false), - "php-2.8.0" | "php-2.9.7" | "php-2.11.1" | "php-2.11.1-from-2.9.7" => Ok(true), + layout if !super::layout_lineage(layout).is_empty() => Ok(true), _ => Err(refuse( StatusCode::CONFLICT, "upgrade_source_layout_unconverted", @@ -358,7 +363,7 @@ where { let php_incarnations = php_incarnations(layout)?; let (copied, created) = adopt_incarnations::(tx, php_incarnations).await?; - let holders = adopt_session_holders::(tx).await?; + let holders = adopt_session_holders::(tx, super::has_release(layout, "php-2.11.3")).await?; // The frozen layout has no call deadlines; every later one does. let deadlines = if php_incarnations { DB::affected(query::(SERVICE_CALL_DEADLINES).execute(&mut *tx).await?) @@ -375,7 +380,7 @@ where .execute(&mut *tx) .await?; } - let delivery_references = if matches!(layout, "php-2.9.7" | "php-2.11.1" | "php-2.11.1-from-2.9.7") { + let delivery_references = if super::has_release(layout, "php-2.9.7") { // PHP's own queue delivery lookup metadata; native does not use PHP's // queue, and the queue inspection bound every job to its task row. let references = count::(tx, DELIVERY_REFERENCES).await?; @@ -386,7 +391,7 @@ where } else { 0 }; - let observability_contexts = if matches!(layout, "php-2.11.1" | "php-2.11.1-from-2.9.7") { + let observability_contexts = if super::has_release(layout, "php-2.11.1") { // Core 2.10's trace carriers on runs, commands and tasks (decided on // #325). Native has no carrier; they are not business state or // history, and the verified backup keeps them. @@ -401,15 +406,53 @@ where } else { 0 }; - Ok(json!({ + let release_2113 = if super::has_release(layout, "php-2.11.3") { + let messages = count::(tx, FULL_FAILURE_MESSAGES).await?; + let dispatches = count::(tx, COMPLETED_DISPATCHES).await?; + sqlx::raw_sql(RELEASE_2113_SQL).execute(&mut *tx).await?; + json!({"full_failure_messages_removed": messages, "service_call_dispatches_removed": dispatches}) + } else { + json!({}) + }; + let mut converted = json!({ "incarnations_copied":copied, "incarnations_created":created, "session_holders_recorded":holders, "service_call_deadlines_copied":deadlines, "queue_delivery_references_removed":delivery_references, "observability_contexts_removed":observability_contexts - })) + }); + if let (Value::Object(converted), Value::Object(release)) = (&mut converted, release_2113) { + converted.extend(release); + } + Ok(converted) } +/// Core 2.14's full failure messages: a message beyond the old TEXT capacity, +/// or with a NUL, keeps a labelled preview in `message` and its full bytes in +/// `message_full`. Native has no second column; it reads the preview, as +/// PHP's own older readers do, and retained history keeps the full failure. +pub(super) const FULL_FAILURE_MESSAGES: &str = + "SELECT COUNT(*) AS n FROM workflow_failures WHERE message_full IS NOT NULL"; + +/// Dispatches PHP 2.11.3 completed; takeover refused any still pending. +pub(super) const COMPLETED_DISPATCHES: &str = + "SELECT COUNT(*) AS n FROM workflow_service_call_dispatches"; + +/// The rest of PHP 2.11.3's additions, which native does not keep: +/// - the service-call idempotency scope digest and its unique index, derived +/// from the call's own target namespace, names and key; +/// - the completed dispatch outbox; +/// - each session's holder incarnation, copied to native's holders first; +/// - each durable stream's reclaim deadline, which only a stream has, and +/// takeover refuses any stream. +const RELEASE_2113_SQL: &str = "ALTER TABLE workflow_failures DROP COLUMN message_full; \ + DROP INDEX wf_service_calls_idem_scope_unique; \ + ALTER TABLE workflow_service_calls DROP COLUMN idempotency_scope_hash; \ + DROP TABLE workflow_service_call_dispatches; \ + ALTER TABLE workflow_worker_sessions DROP COLUMN lease_owner_incarnation; \ + DROP INDEX wf_durable_streams_ns_reclaim_idx; \ + ALTER TABLE workflow_durable_streams DROP COLUMN reclaim_at;"; + /// The tables Core 2.10 gave a trace carrier, with the count of carried values. pub(super) const OBSERVABILITY_CONTEXTS: [(&str, &str); 3] = [ ("workflow_runs", "SELECT COUNT(*) AS n FROM workflow_runs WHERE observability_context IS NOT NULL"), @@ -420,16 +463,26 @@ pub(super) const OBSERVABILITY_CONTEXTS: [(&str, &str); 3] = [ const DELIVERY_REFERENCES: &str = "SELECT COUNT(*) AS n FROM workflow_tasks WHERE queue_delivery_reference IS NOT NULL"; -/// Records each active PHP worker session's holder as native records one: -/// the lease owner's current registration incarnation, which native compares -/// on every read. Without it native would read the session as held by a -/// replaced registration and orphan it. Returns how many were recorded. -pub(super) async fn adopt_session_holders(tx: &mut DB::Connection) -> Result +/// Records each active PHP worker session's holder as native records one, +/// which native compares on every read. Without it native would read the +/// session as held by a replaced registration and orphan it. PHP 2.11.3 +/// records the holder itself (`lease_owner_incarnation`, the same SHA-256 of +/// the registration token), so a session PHP already orphaned stays orphaned. +/// Earlier releases record none: the lease owner's current registration +/// holds it. Returns how many were recorded. +pub(super) async fn adopt_session_holders(tx: &mut DB::Connection, recorded: bool) -> Result where for<'c> &'c mut DB::Connection: Executor<'c, Database = DB>, DB::Arguments: IntoArguments, for<'q> String: Encode<'q, DB> + Type, { + if recorded { + let copied = query::("INSERT INTO dw_worker_session_holders(namespace,session_id,incarnation,updated_at) SELECT namespace,session_id,lease_owner_incarnation,$1 FROM workflow_worker_sessions WHERE status='active' AND lease_owner IS NOT NULL AND lease_owner_incarnation IS NOT NULL") + .bind(DB::bind_time(super::super::store::now())) + .execute(&mut *tx) + .await?; + return Ok(DB::affected(copied) as usize); + } // Newest active incarnation first, as native's current-token read picks. let rows = query::("SELECT s.namespace,s.session_id,i.token FROM workflow_worker_sessions s JOIN dw_worker_registration_incarnations i ON i.namespace=s.namespace AND i.worker_id=s.lease_owner AND i.status='active' WHERE s.status='active' AND s.lease_owner IS NOT NULL ORDER BY s.namespace,s.session_id,i.created_at DESC") .fetch_all(&mut *tx) diff --git a/rust/src/runtime/upgrade/takeover/mysql_journal.rs b/rust/src/runtime/upgrade/takeover/mysql_journal.rs index dd40f08e3..fad95eab0 100644 --- a/rust/src/runtime/upgrade/takeover/mysql_journal.rs +++ b/rust/src/runtime/upgrade/takeover/mysql_journal.rs @@ -27,7 +27,7 @@ const MARKED: i64 = 0; /// Sessions rechecked and the source fingerprinted again after the marker: /// nothing PHP wrote can be missing from the backup. const FENCED: i64 = 1; -const LAST: i64 = 9; +const LAST: i64 = 10; /// One takeover run: what its steps need and what its report says. struct Run<'a> { @@ -315,7 +315,7 @@ async fn perform(connection: &mut MySqlConnection, run: &mut Run<'_>, step: i64) // Rows, unlike DDL, commit with the step that records them. let mut tx = connection.begin().await?; let (copied, created) = adopt_incarnations::(&mut tx, php_incarnations).await?; - let holders = super::adopt_session_holders::(&mut tx).await?; + let holders = super::adopt_session_holders::(&mut tx, super::super::has_release(&run.layout, "php-2.11.3")).await?; advance(&mut *tx, run, step).await?; tx.commit().await?; run.converted @@ -389,6 +389,34 @@ async fn perform(connection: &mut MySqlConnection, run: &mut Run<'_>, step: i64) .await?; } } + 10 => { + // PHP 2.11.3's additions. Count before dropping, as step 9 does. + if column_exists(connection, "workflow_failures", "message_full").await? { + let messages = count::(connection, super::FULL_FAILURE_MESSAGES).await?; + run.converted.insert("full_failure_messages_removed".into(), json!(messages)); + connection.execute("ALTER TABLE workflow_failures DROP COLUMN message_full").await?; + } + if column_exists(connection, "workflow_service_calls", "idempotency_scope_hash").await? { + connection + .execute("ALTER TABLE workflow_service_calls DROP INDEX wf_service_calls_idem_scope_unique, DROP COLUMN idempotency_scope_hash") + .await?; + } + if column_exists(connection, "workflow_service_call_dispatches", "state").await? { + let dispatches = count::(connection, super::COMPLETED_DISPATCHES).await?; + run.converted.insert("service_call_dispatches_removed".into(), json!(dispatches)); + connection.execute("DROP TABLE workflow_service_call_dispatches").await?; + } + if column_exists(connection, "workflow_worker_sessions", "lease_owner_incarnation").await? { + connection + .execute("ALTER TABLE workflow_worker_sessions DROP COLUMN lease_owner_incarnation") + .await?; + } + if column_exists(connection, "workflow_durable_streams", "reclaim_at").await? { + connection + .execute("ALTER TABLE workflow_durable_streams DROP INDEX wf_durable_streams_ns_reclaim_idx, DROP COLUMN reclaim_at") + .await?; + } + } _ => {} } advance(&mut *connection, run, step).await diff --git a/rust/src/runtime/upgrade_tests.rs b/rust/src/runtime/upgrade_tests.rs index 2acef0699..b9bcdb4d7 100644 --- a/rust/src/runtime/upgrade_tests.rs +++ b/rust/src/runtime/upgrade_tests.rs @@ -848,6 +848,8 @@ fn source_layout_names_the_release_each_recorded_delta_reaches() { r#"{"remove":[],"add":[["c","3"]]}"#, r#"{"remove":[["c","3"]],"add":[["c","3 changed"]]}"#, r#"{"remove":[["b","2"]],"add":[["b","2 reordered"]]}"#, + r#"{"remove":[],"add":[["d","4"]]}"#, + r#"{"remove":[],"add":[["d","4 after reorder"]]}"#, ]; let layout = |actual: &[(&str, &str)]| { source_layout(&rows(actual), frozen.clone(), deltas, |row| row.0.clone()) @@ -867,6 +869,19 @@ fn source_layout_names_the_release_each_recorded_delta_reaches() { layout(&[("c", "3 changed"), ("a", "1 changed"), ("b", "2 reordered")]).unwrap(), "php-2.11.1-from-2.9.7" ); + // 2.11.3 descends from each 2.11.1 variant separately. + assert_eq!( + layout(&[("c", "3 changed"), ("a", "1 changed"), ("b", "2"), ("d", "4")]).unwrap(), + "php-2.11.3" + ); + assert_eq!( + layout(&[("c", "3 changed"), ("a", "1 changed"), ("b", "2 reordered"), ("d", "4 after reorder")]).unwrap(), + "php-2.11.3-from-2.9.7" + ); + reason( + layout(&[("c", "3 changed"), ("a", "1 changed"), ("b", "2"), ("d", "4 after reorder")]), + "upgrade_source_catalog_unqualified", + ); // A later release's addition without the earlier change is no recorded layout. reason( layout(&[("a", "1"), ("b", "2"), ("c", "3")]), @@ -875,6 +890,19 @@ fn source_layout_names_the_release_each_recorded_delta_reaches() { reason(layout(&[("a", "1")]), "upgrade_source_catalog_unqualified"); } +#[test] +fn every_recorded_layout_descends_from_an_earlier_one() { + for (index, (name, parent)) in SOURCE_LAYOUTS.iter().enumerate() { + assert!( + *parent == "frozen" || SOURCE_LAYOUTS[..index].iter().any(|(layout, _)| layout == parent), + "{name} names a parent recorded before it" + ); + } + assert_eq!(layout_lineage("php-2.11.3"), [0, 1, 2, 4]); + assert_eq!(layout_lineage("php-2.11.3-from-2.9.7"), [0, 1, 2, 3, 5]); + assert!(layout_lineage("frozen").is_empty()); +} + #[tokio::test] async fn frozen_layout_is_reported_by_inspection_and_fingerprint() { let (_directory, path) = php_layout().await; @@ -911,6 +939,24 @@ const TRACE_CARRIERS: &[&str] = &[ r#"ALTER TABLE "workflow_tasks" ADD COLUMN "observability_context" text"#, ]; +/// PHP 2.11.3's changes (Core 2.14.2 and Server), in its migrations' order. +const RELEASE_2113: &[&str] = &[ + // Core 2.14: full failure messages. + r#"ALTER TABLE "workflow_failures" ADD COLUMN "message_full" text"#, + // Core 2.14: the service-call idempotency scope. + r#"ALTER TABLE "workflow_service_calls" ADD COLUMN "idempotency_scope_hash" varchar"#, + r#"CREATE UNIQUE INDEX "wf_service_calls_idem_scope_unique" on "workflow_service_calls" ("idempotency_scope_hash")"#, + // Core 2.14: the service-call dispatch outbox. + r#"CREATE TABLE "workflow_service_call_dispatches" ("service_call_id" varchar not null, "state" varchar not null, "dispatch_options" text not null, "attempt" integer not null default '1', "max_attempts" integer not null default '1', "available_at" datetime not null, "claim_generation" integer not null default '0', "claim_token" varchar, "claimed_at" datetime, "claim_expires_at" datetime, "authorized_catalog_revision" varchar, "authorized_policy_revision" varchar, "dispatch_attempted_at" datetime, "repair_available_at" datetime, "completed_at" datetime, "last_error" text, "created_at" datetime, "updated_at" datetime, primary key ("service_call_id"))"#, + r#"CREATE INDEX "wf_service_dispatch_due_idx" on "workflow_service_call_dispatches" ("state", "available_at")"#, + r#"CREATE INDEX "wf_service_dispatch_claim_idx" on "workflow_service_call_dispatches" ("state", "claim_expires_at")"#, + r#"CREATE INDEX "wf_service_dispatch_repair_idx" on "workflow_service_call_dispatches" ("state", "repair_available_at")"#, + // Server: each session's holder incarnation and each stream's reclaim deadline. + r#"ALTER TABLE "workflow_worker_sessions" ADD COLUMN "lease_owner_incarnation" varchar"#, + r#"ALTER TABLE "workflow_durable_streams" ADD COLUMN "reclaim_at" datetime"#, + r#"CREATE INDEX "wf_durable_streams_ns_reclaim_idx" on "workflow_durable_streams" ("namespace", "reclaim_at", "id")"#, +]; + /// The frozen test layout with a release's changes applied in order. async fn php_release_layout(changes: &[&[&'static str]]) -> (tempfile::TempDir, String) { let (directory, path) = php_layout().await; @@ -1030,6 +1076,95 @@ async fn takeover_converts_a_php_2111_database(release: &str, changes: &[&[&'sta runtime.close().await; } +/// PHP 2.11.3 databases, fresh or created by 2.9.x, convert. Native keeps +/// each active session's recorded holder, so a session PHP already orphaned +/// stays orphaned, and drops what it does not keep: full failure messages +/// (the labelled preview and retained history remain), the idempotency scope +/// digest, completed dispatches and the stream reclaim deadline. +#[tokio::test] +async fn takeover_converts_php_2113_databases() { + for (release, changes) in [ + ("php-2.11.3", [PHP_280_CHANGES, TRACE_CARRIERS, QUEUE_DELIVERY, RELEASE_2113]), + ("php-2.11.3-from-2.9.7", [PHP_280_CHANGES, QUEUE_DELIVERY, TRACE_CARRIERS, RELEASE_2113]), + ] { + takeover_converts_a_php_2113_database(release, &changes).await; + } +} + +async fn takeover_converts_a_php_2113_database(release: &str, changes: &[&[&'static str]]) { + use sha2::{Digest, Sha256}; + let (_directory, path) = php_release_layout(changes).await; + assert_eq!(inspect_sqlite(&path).await.unwrap()["source_layout"], release); + let digest = |token: &str| -> String { Sha256::digest(token.as_bytes()).iter().map(|byte| format!("{byte:02x}")).collect() }; + let at = |offset: i64| (chrono::Utc::now() + chrono::Duration::seconds(offset)).format("%Y-%m-%d %H:%M:%S").to_string(); + let token = "0123456789abcdef0123456789abcdef"; + let statements = [ + format!("INSERT INTO workflow_worker_registrations(worker_id,namespace,task_queue,runtime,registration_token,status,last_heartbeat_at,created_at,updated_at) VALUES ('worker-a','default','q','php','{token}','active','{}','{}','{}')", at(0), at(-60), at(0)), + format!("INSERT INTO workflow_worker_registration_incarnations VALUES ('{token}','default','worker-a','active',0,NULL,'{}','{}')", at(-60), at(-60)), + // Held by the current registration, as PHP recorded it. + format!("INSERT INTO workflow_worker_sessions(namespace,session_id,queue,status,lease_owner,lease_owner_incarnation,lease_expires_at,ttl_expires_at,last_heartbeat_at,created_at,updated_at) VALUES ('default','held','q','active','worker-a','{}','{}','{}','{}','{}','{}')", digest(token), at(600), at(1800), at(-5), at(-60), at(-5)), + // Held by a registration the worker has since replaced. + format!("INSERT INTO workflow_worker_sessions(namespace,session_id,queue,status,lease_owner,lease_owner_incarnation,lease_expires_at,ttl_expires_at,last_heartbeat_at,created_at,updated_at) VALUES ('default','replaced','q','active','worker-a','{}','{}','{}','{}','{}','{}')", digest("an earlier registration token"), at(600), at(1800), at(-5), at(-60), at(-5)), + "INSERT INTO workflow_failures(id,workflow_run_id,source_kind,source_id,propagation_kind,exception_class,message,file,message_full,created_at,updated_at) VALUES ('01m5wvy8d1j3dtr13es05kfail','01jrun0000000000000000000a','activity_execution','a','activity','RuntimeException','preview\n[full message stored separately]','','base64:ZnVsbA==','2026-10-26 10:00:00','2026-10-26 10:00:00')".to_owned(), + "INSERT INTO workflow_service_calls(id,namespace,endpoint_name,service_name,operation_name,caller_namespace,target_namespace,status,outcome,operation_mode,idempotency_key,idempotency_scope_hash,accepted_at,completed_at,created_at,updated_at) VALUES ('01m5wvy8d1j3dtr13es05kcall','default','pay','billing','charge','default','default','completed','succeeded','async','order-1','0000000000000000000000000000000000000000000000000000000000000001','2026-10-26 10:00:00','2026-10-26 10:00:01','2026-10-26 10:00:00','2026-10-26 10:00:01')".to_owned(), + "INSERT INTO workflow_service_call_dispatches(service_call_id,state,dispatch_options,available_at,completed_at,created_at,updated_at) VALUES ('01m5wvy8d1j3dtr13es05kcall','completed','{}','2026-10-26 10:00:00','2026-10-26 10:00:01','2026-10-26 10:00:00','2026-10-26 10:00:01')".to_owned(), + ]; + for statement in &statements { + mutate(&path, Box::leak(statement.clone().into_boxed_str())).await; + } + let fingerprint = backup_fingerprint(&path).await; + let report = takeover::take_over_sqlite(&path, &fingerprint, &queue_config()).await.unwrap(); + assert_eq!(report["status"], "taken_over", "{report}"); + assert_eq!(report["source_layout"], release); + assert_eq!( + [&report["converted"]["session_holders_recorded"], &report["converted"]["full_failure_messages_removed"], &report["converted"]["service_call_dispatches_removed"]], + [&json!(2), &json!(1), &json!(1)], + "{report}" + ); + let mut connection = SqliteConnection::connect(&format!("sqlite://{path}")).await.unwrap(); + let message: String = sqlx::query_scalar("SELECT message FROM workflow_failures").fetch_one(&mut connection).await.unwrap(); + assert_eq!(message, "preview\n[full message stored separately]", "the labelled preview stays"); + let left: i64 = sqlx::query_scalar(concat!( + "SELECT (SELECT COUNT(*) FROM pragma_table_info('workflow_failures') WHERE name='message_full')", + "+(SELECT COUNT(*) FROM pragma_table_info('workflow_service_calls') WHERE name='idempotency_scope_hash')", + "+(SELECT COUNT(*) FROM pragma_table_info('workflow_worker_sessions') WHERE name='lease_owner_incarnation')", + "+(SELECT COUNT(*) FROM pragma_table_info('workflow_durable_streams') WHERE name='reclaim_at')", + "+(SELECT COUNT(*) FROM sqlite_master WHERE name='workflow_service_call_dispatches')")) + .fetch_one(&mut connection).await.unwrap(); + assert_eq!(left, 0, "PHP 2.11.3's additions are gone"); + connection.close().await.unwrap(); + let runtime = crate::runtime::Runtime::open(&path, "test-token".into()).await.unwrap(); + let held = runtime.show_worker_session("held").await.unwrap(); + assert_eq!((held["session"]["status"].clone(), held["session"]["failure_reason"].clone()), (json!("active"), Value::Null), "{held}"); + let replaced = runtime.show_worker_session("replaced").await.unwrap(); + assert_eq!( + (replaced["session"]["status"].clone(), replaced["session"]["failure_reason"].clone()), + (json!("orphaned"), json!("worker_registration_replaced")), + "{replaced}" + ); + runtime.close().await; +} + +/// Native runs a service call's handler when it accepts the call, so it +/// cannot resume a dispatch PHP 2.11.3 scheduled or claimed. Inspection +/// reports it and takeover refuses before any change. +#[tokio::test] +async fn takeover_refuses_a_php_2113_dispatch_still_pending() { + let (_directory, path) = php_release_layout(&[PHP_280_CHANGES, TRACE_CARRIERS, QUEUE_DELIVERY, RELEASE_2113]).await; + mutate(&path, "INSERT INTO workflow_service_calls(id,namespace,endpoint_name,service_name,operation_name,caller_namespace,target_namespace,status,outcome,operation_mode,accepted_at,created_at,updated_at) VALUES ('01m5wvy8d1j3dtr13es05kcall','default','pay','billing','charge','default','default','accepted','accepted','async','2026-10-26 10:00:00','2026-10-26 10:00:00','2026-10-26 10:00:00')").await; + mutate(&path, "INSERT INTO workflow_service_call_dispatches(service_call_id,state,dispatch_options,available_at,created_at,updated_at) VALUES ('01m5wvy8d1j3dtr13es05kcall','scheduled','{}','2026-10-26 10:00:00','2026-10-26 10:00:00','2026-10-26 10:00:00')").await; + let inspection = inspect_sqlite(&path).await.unwrap(); + assert_eq!(inspection["status"], "unqualified", "{inspection}"); + assert_eq!(inspection["service_call_dispatch_inspection"], json!({"dispatches": 1, "pending": 1})); + let before = std::fs::read(&path).unwrap(); + let fingerprint = backup_fingerprint(&path).await; + reason( + takeover::take_over_sqlite(&path, &fingerprint, &queue_config()).await, + "upgrade_source_service_call_dispatches_pending", + ); + assert_eq!(std::fs::read(&path).unwrap(), before, "the PHP database is unchanged"); +} + #[tokio::test] #[ignore = "only invoked as a real subprocess by the takeover interruption test"] async fn sqlite_takeover_child() { @@ -1141,10 +1276,6 @@ async fn takeover_refusals_leave_the_php_database_unchanged() { "upgrade_source_external_payloads_unsupported", ), // Features native lacks: their rows would be left behind unread. - ( - "INSERT INTO runtime_credentials(id,subject,roles,tenant,token_prefix,token_hash) VALUES ('c','worker','[]','default','dw_','h')", - "upgrade_source_runtime_credentials_unsupported", - ), ( "INSERT INTO workflow_messages(workflow_instance_id,direction,channel,stream_key,sequence) VALUES ('w','inbound','c','k',1)", "upgrade_source_message_streams_unsupported", @@ -1271,6 +1402,53 @@ async fn takeover_keeps_php_service_calls_and_their_deadlines() { runtime.close().await; } +/// PHP's update validation tasks carry over unchanged: native validates +/// updates in the same `workflow_update_validation_tasks` rows, with no state +/// of its own. A pending task whose caller was waiting on PHP is then what a +/// PHP restart leaves: a validator may still claim it, and a caller's retry +/// with the same request reads its recorded outcome. +#[tokio::test] +async fn takeover_keeps_php_update_validation_tasks() { + let (_directory, path) = php_release_layout(&[PHP_280_CHANGES, TRACE_CARRIERS, QUEUE_DELIVERY]).await; + let row = |id: &str, status: &str, decided: &str| format!( + "INSERT INTO workflow_update_validation_tasks(id,idempotency_key,namespace,workflow_instance_id,workflow_run_id,workflow_type,task_queue,update_name,request_id,input_hash,payload_codec,arguments,status,attempt_count,approved_at,created_at,updated_at) VALUES ('{id}','key-{id}','default','order-1','01jrun0000000000000000000a','orders','q','approve','req-{id}','{}','avro','[]','{status}',{},{decided},'2026-10-26 10:00:00','2026-10-26 10:00:00')", + "0".repeat(64), if status == "pending" { 0 } else { 1 }); + for statement in [row("01m5wvy8d1j3dtr13es05kval1", "pending", "NULL"), row("01m5wvy8d1j3dtr13es05kval2", "approved", "'2026-10-26 10:00:01'")] { + mutate(&path, Box::leak(statement.into_boxed_str())).await; + } + let read = |path: String| async move { + let mut connection = SqliteConnection::connect(&format!("sqlite://{path}")).await.unwrap(); + let rows: Vec<(String, String, i64, Option)> = sqlx::query_as("SELECT id,status,attempt_count,approved_at FROM workflow_update_validation_tasks ORDER BY id") + .fetch_all(&mut connection).await.unwrap(); + connection.close().await.unwrap(); + rows + }; + let before = read(path.clone()).await; + let fingerprint = backup_fingerprint(&path).await; + let report = takeover::take_over_sqlite(&path, &fingerprint, &queue_config()).await.unwrap(); + assert_eq!(report["status"], "taken_over", "{report}"); + assert_eq!(read(path.clone()).await, before, "the tasks carry over unchanged"); + let runtime = crate::runtime::Runtime::open(&path, "test-token".into()).await.unwrap(); + runtime.close().await; +} + +/// PHP's runtime credentials carry over: native stores and checks the same +/// SHA-256 token hashes in the same table, so a worker's PHP-issued token +/// still authenticates after takeover, and a revoked one still does not. +#[tokio::test] +async fn takeover_keeps_php_runtime_credentials_that_native_then_accepts() { + let (_directory, path) = php_release_layout(&[PHP_280_CHANGES, TRACE_CARRIERS, QUEUE_DELIVERY]).await; + for statement in crate::runtime::runtime_credentials::PHP_CREDENTIAL_ROWS { + mutate(&path, statement).await; + } + let fingerprint = backup_fingerprint(&path).await; + let report = takeover::take_over_sqlite(&path, &fingerprint, &queue_config()).await.unwrap(); + assert_eq!(report["status"], "taken_over", "{report}"); + let runtime = crate::runtime::Runtime::open(&path, "test-token".into()).await.unwrap(); + crate::runtime::runtime_credentials::assert_php_credentials_served(&runtime).await; + runtime.close().await; +} + /// The frozen layout in PHP Server's default WAL mode, copied while its /// writer was still open: one committed row is only in the WAL, as a crashed /// PHP would leave it. diff --git a/rust/tests/execution/repair_pass.rs b/rust/tests/execution/repair_pass.rs index e4597687c..03d0401ed 100644 --- a/rust/tests/execution/repair_pass.rs +++ b/rust/tests/execution/repair_pass.rs @@ -102,7 +102,10 @@ async fn repair_pass_recovers_tasks_and_runs_like_php() { #[tokio::test] async fn repair_pass_creates_deadline_tasks_settles_leases_and_reports_failures() { let database = TestDatabase::new().await; - let app = router(database.open().await.unwrap()); + let runtime = database.open().await.unwrap(); + // This test counts what one requested pass repairs. + runtime.set_maintenance_repair(false); + let app = router(runtime); register(&app, "worker", json!(["echo"]), json!(["act"])).await; let deadline = started_on(&app, "deadline", "repair-d").await; @@ -173,3 +176,29 @@ async fn repair_pass_creates_deadline_tasks_settles_leases_and_reports_failures( assert_eq!(invalid["errors"]["run_ids.0"], json!(["The run_ids.0 field must be a string.", "The run_ids.0 field must be at least 1 characters."])); assert_eq!(invalid["errors"]["queue"], json!(["The queue field must not be greater than 128 characters."])); } + +// PHP's scheduler container runs the repair pass every few seconds; native's +// ticker does the same when it takes that role. An idle run past its +// execution deadline gets the workflow task that closes it without anyone +// asking for a pass. +#[tokio::test] +async fn the_maintenance_loop_gives_an_overdue_idle_run_its_deadline_task() { + let database = TestDatabase::new().await; + let runtime = database.open().await.unwrap(); + // `DW_RUST_MAINTENANCE_LOOP=1`, as the native image sets it. + runtime.set_maintenance_repair(true); + let app = router(runtime); + let deadline = started_on(&app, "maintained", "repair-m").await; + let deadline = deadline["run_id"].as_str().unwrap(); + execute(&database, format!("UPDATE workflow_tasks SET status='completed' WHERE workflow_run_id='{deadline}'")).await; + execute(&database, format!("UPDATE workflow_runs SET execution_deadline_at={LONG_AGO} WHERE id='{deadline}'")).await; + let ready = format!("SELECT COUNT(*) FROM workflow_tasks WHERE workflow_run_id='{deadline}' AND status='ready'"); + // The loop's throttle is five seconds; allow two turns. + for _ in 0..48 { + if count(&database, ready.clone()).await == 1 { + return; + } + tokio::time::sleep(std::time::Duration::from_millis(250)).await; + } + panic!("no deadline task after twelve seconds of maintenance"); +} diff --git a/rust/tests/execution/worker_pressure.rs b/rust/tests/execution/worker_pressure.rs index 5e06dd0fb..55c8dc1bd 100644 --- a/rust/tests/execution/worker_pressure.rs +++ b/rust/tests/execution/worker_pressure.rs @@ -92,6 +92,8 @@ impl TestDatabase { async fn real_shutdown_lock_pressure_preserves_original_leases_and_retries_same_token() { let database = TestDatabase::new().await; let runtime = database.open().await.unwrap(); + // Expired leases must stay as the shutdown left them until the test retries. + runtime.set_maintenance_repair(false); let app = router(runtime.clone()); let worker = "pressure-worker"; let accepted = registration(&app, worker).await; diff --git a/rust/upgrade/php-2.11.3-from-2.9.7/README.md b/rust/upgrade/php-2.11.3-from-2.9.7/README.md new file mode 100644 index 000000000..e47c90f0b --- /dev/null +++ b/rust/upgrade/php-2.11.3-from-2.9.7/README.md @@ -0,0 +1,21 @@ +# Exact PHP source-catalog variant: Server 2.11.3 migrated from 2.9.7 + +These deltas apply to the [PHP 2.11.1 layout migrated from +2.9.7](../php-2.11.1-from-2.9.7/README.md). They make the same changes as the +fresh [PHP 2.11.3 delta](../php-2.11.3/README.md); only the parent differs. +2.9.7 had already added `workflow_tasks.queue_delivery_reference`, so its +task column order survives every later release. + +The same delta was captured from two databases: one that published Server +2.9.7 created (image index +`sha256:30c34c5f3256a812fbccbe505a068309b58b0c14d01ffff0465d88604cb36d50`) +and published Server 2.11.3 (image index +`sha256:923a8924ecad13485562d7af30dc87d7fd6d9d7d4758ec8113bac0766fa8603e`) +then migrated, and one that 2.9.7 created, 2.11.1 (image +`sha256:bf8bed13f5f809ee3425233180a581353eac4683adbee5c4ecbf63bb18bbd102`) +migrated and 2.11.3 migrated again. + +Definitions were read from actual published-image databases: SQLite, MySQL +8.0, MariaDB 10.11 through the `mysql` connection, and PostgreSQL 17. No +application row values or credentials are recorded. Takeover converts it as +it converts fresh 2.11.3. diff --git a/rust/upgrade/php-2.11.3-from-2.9.7/mariadb.json b/rust/upgrade/php-2.11.3-from-2.9.7/mariadb.json new file mode 100644 index 000000000..71db587f2 --- /dev/null +++ b/rust/upgrade/php-2.11.3-from-2.9.7/mariadb.json @@ -0,0 +1,42 @@ +{ + "remove": [ + [ + "workflow_durable_streams", + "CREATE TABLE `workflow_durable_streams` (\n `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n `namespace` varchar(128) NOT NULL,\n `workflow_instance_id` varchar(191) NOT NULL,\n `workflow_run_id` varchar(26) NOT NULL,\n `stream_name` varchar(191) NOT NULL,\n `status` varchar(16) NOT NULL DEFAULT 'open',\n `last_offset` bigint(20) NOT NULL DEFAULT -1,\n `total_items` bigint(20) unsigned NOT NULL DEFAULT 0,\n `retention_seconds` int(10) unsigned DEFAULT NULL,\n `opened_at` timestamp(6) NULL DEFAULT NULL,\n `last_appended_at` timestamp(6) NULL DEFAULT NULL,\n `closed_at` timestamp(6) NULL DEFAULT NULL,\n `error_reason` varchar(191) DEFAULT NULL,\n `pending_items` bigint(20) unsigned NOT NULL DEFAULT 0,\n `metadata` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`metadata`)),\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `wf_durable_streams_run_name_unique` (`workflow_run_id`,`stream_name`),\n KEY `wf_durable_streams_ns_instance_name_idx` (`namespace`,`workflow_instance_id`,`stream_name`),\n KEY `wf_durable_streams_ns_status_idx` (`namespace`,`status`),\n KEY `workflow_durable_streams_namespace_index` (`namespace`),\n KEY `workflow_durable_streams_workflow_instance_id_index` (`workflow_instance_id`),\n KEY `workflow_durable_streams_workflow_run_id_index` (`workflow_run_id`),\n KEY `workflow_durable_streams_status_index` (`status`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_failures", + "CREATE TABLE `workflow_failures` (\n `id` varchar(26) NOT NULL,\n `workflow_run_id` varchar(26) NOT NULL,\n `source_kind` varchar(255) NOT NULL,\n `source_id` varchar(255) NOT NULL,\n `propagation_kind` varchar(255) NOT NULL,\n `failure_category` varchar(255) DEFAULT NULL,\n `non_retryable` tinyint(1) NOT NULL DEFAULT 0,\n `handled` tinyint(1) NOT NULL DEFAULT 0,\n `exception_class` varchar(255) NOT NULL,\n `message` text NOT NULL,\n `file` text NOT NULL,\n `line` int(10) unsigned DEFAULT NULL,\n `trace_preview` longtext DEFAULT NULL,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n KEY `workflow_failures_source_kind_source_id_index` (`source_kind`,`source_id`),\n KEY `workflow_failures_workflow_run_id_index` (`workflow_run_id`),\n KEY `workflow_failures_failure_category_index` (`failure_category`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_worker_sessions", + "CREATE TABLE `workflow_worker_sessions` (\n `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n `namespace` varchar(128) NOT NULL,\n `session_id` varchar(255) NOT NULL,\n `connection` varchar(255) DEFAULT NULL,\n `queue` varchar(255) DEFAULT NULL,\n `requirements` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`requirements`)),\n `status` varchar(32) NOT NULL DEFAULT 'active',\n `lease_owner` varchar(255) DEFAULT NULL,\n `lease_expires_at` timestamp NULL DEFAULT NULL,\n `ttl_expires_at` timestamp NULL DEFAULT NULL,\n `closed_at` timestamp NULL DEFAULT NULL,\n `failure_reason` varchar(128) DEFAULT NULL,\n `lease_seconds` int(10) unsigned NOT NULL DEFAULT 120,\n `ttl_seconds` int(10) unsigned NOT NULL DEFAULT 1800,\n `max_concurrent_activities` int(10) unsigned NOT NULL DEFAULT 1,\n `create_if_missing` tinyint(1) NOT NULL DEFAULT 1,\n `allow_reacquire_after_failure` tinyint(1) NOT NULL DEFAULT 1,\n `last_heartbeat_at` timestamp NULL DEFAULT NULL,\n `created_at` timestamp NULL DEFAULT NULL,\n `updated_at` timestamp NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `workflow_worker_sessions_namespace_session_id_unique` (`namespace`,`session_id`),\n KEY `workflow_worker_sessions_namespace_status_index` (`namespace`,`status`),\n KEY `workflow_worker_sessions_namespace_lease_owner_status_index` (`namespace`,`lease_owner`,`status`),\n KEY `workflow_worker_sessions_namespace_queue_status_index` (`namespace`,`queue`,`status`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_service_calls", + "CREATE TABLE `workflow_service_calls` (\n `id` varchar(26) NOT NULL,\n `workflow_service_endpoint_id` varchar(26) DEFAULT NULL,\n `workflow_service_id` varchar(26) DEFAULT NULL,\n `workflow_service_operation_id` varchar(26) DEFAULT NULL,\n `namespace` varchar(255) DEFAULT NULL,\n `endpoint_name` varchar(191) NOT NULL,\n `service_name` varchar(191) NOT NULL,\n `operation_name` varchar(191) NOT NULL,\n `caller_namespace` varchar(255) DEFAULT NULL,\n `caller_workflow_instance_id` varchar(191) DEFAULT NULL,\n `caller_workflow_run_id` varchar(26) DEFAULT NULL,\n `target_namespace` varchar(255) DEFAULT NULL,\n `linked_workflow_instance_id` varchar(191) DEFAULT NULL,\n `linked_workflow_run_id` varchar(26) DEFAULT NULL,\n `linked_workflow_update_id` varchar(26) DEFAULT NULL,\n `status` varchar(32) NOT NULL,\n `outcome` varchar(64) DEFAULT NULL,\n `operation_mode` varchar(32) NOT NULL,\n `resolved_binding_kind` varchar(64) DEFAULT NULL,\n `resolved_target_reference` varchar(191) DEFAULT NULL,\n `payload_codec` varchar(255) DEFAULT NULL,\n `input_payload_reference` varchar(191) DEFAULT NULL,\n `output_payload_reference` varchar(191) DEFAULT NULL,\n `failure_payload_reference` varchar(191) DEFAULT NULL,\n `failure_message` text DEFAULT NULL,\n `idempotency_key` varchar(191) DEFAULT NULL,\n `deadline_policy` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`deadline_policy`)),\n `idempotency_policy` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`idempotency_policy`)),\n `cancellation_policy` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`cancellation_policy`)),\n `retry_policy` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`retry_policy`)),\n `boundary_policy` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`boundary_policy`)),\n `metadata` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`metadata`)),\n `outcome_category` varchar(32) DEFAULT NULL,\n `outcome_reason` varchar(191) DEFAULT NULL,\n `outcome_message` text DEFAULT NULL,\n `outcome_metadata` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`outcome_metadata`)),\n `policy_name` varchar(191) DEFAULT NULL,\n `retry_after_seconds` int(10) unsigned DEFAULT NULL,\n `caller_principal_subject` varchar(191) DEFAULT NULL,\n `caller_principal_method` varchar(64) DEFAULT NULL,\n `caller_principal_roles` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`caller_principal_roles`)),\n `caller_principal_tenant` varchar(191) DEFAULT NULL,\n `caller_principal_claims` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`caller_principal_claims`)),\n `accepted_at` timestamp(6) NULL DEFAULT NULL,\n `started_at` timestamp(6) NULL DEFAULT NULL,\n `completed_at` timestamp(6) NULL DEFAULT NULL,\n `failed_at` timestamp(6) NULL DEFAULT NULL,\n `cancelled_at` timestamp(6) NULL DEFAULT NULL,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n `call_deadline_at` datetime(6) DEFAULT NULL,\n PRIMARY KEY (`id`),\n KEY `wf_service_calls_namespace_status_idx` (`namespace`,`status`),\n KEY `wf_service_calls_caller_status_idx` (`caller_namespace`,`status`),\n KEY `wf_service_calls_target_status_idx` (`target_namespace`,`status`),\n KEY `wf_service_calls_namespace_outcome_idx` (`namespace`,`outcome`),\n KEY `wf_service_calls_caller_outcome_idx` (`caller_namespace`,`outcome`),\n KEY `wf_service_calls_target_outcome_idx` (`target_namespace`,`outcome`),\n KEY `wf_service_calls_endpoint_idx` (`workflow_service_endpoint_id`),\n KEY `wf_service_calls_service_idx` (`workflow_service_id`),\n KEY `wf_service_calls_operation_idx` (`workflow_service_operation_id`),\n KEY `wf_service_calls_namespace_idx` (`namespace`),\n KEY `wf_service_calls_caller_namespace_idx` (`caller_namespace`),\n KEY `wf_service_calls_caller_instance_idx` (`caller_workflow_instance_id`),\n KEY `wf_service_calls_caller_run_idx` (`caller_workflow_run_id`),\n KEY `wf_service_calls_target_namespace_idx` (`target_namespace`),\n KEY `wf_service_calls_linked_instance_idx` (`linked_workflow_instance_id`),\n KEY `wf_service_calls_linked_run_idx` (`linked_workflow_run_id`),\n KEY `wf_service_calls_linked_update_idx` (`linked_workflow_update_id`),\n KEY `wf_service_calls_status_idx` (`status`),\n KEY `wf_service_calls_outcome_idx` (`outcome`),\n KEY `wf_service_calls_mode_idx` (`operation_mode`),\n KEY `wf_service_calls_binding_kind_idx` (`resolved_binding_kind`),\n KEY `wf_service_calls_idempotency_idx` (`idempotency_key`),\n KEY `wf_service_calls_outcome_category_idx` (`outcome_category`),\n KEY `wf_service_calls_policy_idx` (`policy_name`),\n KEY `wf_service_calls_principal_subject_idx` (`caller_principal_subject`),\n KEY `wf_service_calls_accepted_at_idx` (`accepted_at`),\n KEY `wf_service_calls_deadline_idx` (`status`,`call_deadline_at`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ] + ], + "add": [ + [ + "workflow_durable_streams", + "CREATE TABLE `workflow_durable_streams` (\n `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n `namespace` varchar(128) NOT NULL,\n `workflow_instance_id` varchar(191) NOT NULL,\n `workflow_run_id` varchar(26) NOT NULL,\n `stream_name` varchar(191) NOT NULL,\n `status` varchar(16) NOT NULL DEFAULT 'open',\n `last_offset` bigint(20) NOT NULL DEFAULT -1,\n `total_items` bigint(20) unsigned NOT NULL DEFAULT 0,\n `retention_seconds` int(10) unsigned DEFAULT NULL,\n `opened_at` timestamp(6) NULL DEFAULT NULL,\n `last_appended_at` timestamp(6) NULL DEFAULT NULL,\n `closed_at` timestamp(6) NULL DEFAULT NULL,\n `error_reason` varchar(191) DEFAULT NULL,\n `pending_items` bigint(20) unsigned NOT NULL DEFAULT 0,\n `metadata` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`metadata`)),\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n `reclaim_at` timestamp(6) NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `wf_durable_streams_run_name_unique` (`workflow_run_id`,`stream_name`),\n KEY `wf_durable_streams_ns_instance_name_idx` (`namespace`,`workflow_instance_id`,`stream_name`),\n KEY `wf_durable_streams_ns_status_idx` (`namespace`,`status`),\n KEY `workflow_durable_streams_namespace_index` (`namespace`),\n KEY `workflow_durable_streams_workflow_instance_id_index` (`workflow_instance_id`),\n KEY `workflow_durable_streams_workflow_run_id_index` (`workflow_run_id`),\n KEY `workflow_durable_streams_status_index` (`status`),\n KEY `wf_durable_streams_ns_reclaim_idx` (`namespace`,`reclaim_at`,`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_failures", + "CREATE TABLE `workflow_failures` (\n `id` varchar(26) NOT NULL,\n `workflow_run_id` varchar(26) NOT NULL,\n `source_kind` varchar(255) NOT NULL,\n `source_id` varchar(255) NOT NULL,\n `propagation_kind` varchar(255) NOT NULL,\n `failure_category` varchar(255) DEFAULT NULL,\n `non_retryable` tinyint(1) NOT NULL DEFAULT 0,\n `handled` tinyint(1) NOT NULL DEFAULT 0,\n `exception_class` varchar(255) NOT NULL,\n `message` text NOT NULL,\n `file` text NOT NULL,\n `line` int(10) unsigned DEFAULT NULL,\n `trace_preview` longtext DEFAULT NULL,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n `message_full` longtext DEFAULT NULL,\n PRIMARY KEY (`id`),\n KEY `workflow_failures_source_kind_source_id_index` (`source_kind`,`source_id`),\n KEY `workflow_failures_workflow_run_id_index` (`workflow_run_id`),\n KEY `workflow_failures_failure_category_index` (`failure_category`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_service_call_dispatches", + "CREATE TABLE `workflow_service_call_dispatches` (\n `service_call_id` varchar(26) NOT NULL,\n `state` varchar(32) NOT NULL,\n `dispatch_options` longtext COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`dispatch_options`)),\n `attempt` int(10) unsigned NOT NULL DEFAULT 1,\n `max_attempts` int(10) unsigned NOT NULL DEFAULT 1,\n `available_at` timestamp(6) NOT NULL,\n `claim_generation` bigint(20) unsigned NOT NULL DEFAULT 0,\n `claim_token` char(64) DEFAULT NULL,\n `claimed_at` timestamp(6) NULL DEFAULT NULL,\n `claim_expires_at` timestamp(6) NULL DEFAULT NULL,\n `authorized_catalog_revision` char(64) DEFAULT NULL,\n `authorized_policy_revision` char(64) DEFAULT NULL,\n `dispatch_attempted_at` timestamp(6) NULL DEFAULT NULL,\n `repair_available_at` timestamp(6) NULL DEFAULT NULL,\n `completed_at` timestamp(6) NULL DEFAULT NULL,\n `last_error` text DEFAULT NULL,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n PRIMARY KEY (`service_call_id`),\n KEY `wf_service_dispatch_due_idx` (`state`,`available_at`),\n KEY `wf_service_dispatch_claim_idx` (`state`,`claim_expires_at`),\n KEY `wf_service_dispatch_repair_idx` (`state`,`repair_available_at`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_service_calls", + "CREATE TABLE `workflow_service_calls` (\n `id` varchar(26) NOT NULL,\n `workflow_service_endpoint_id` varchar(26) DEFAULT NULL,\n `workflow_service_id` varchar(26) DEFAULT NULL,\n `workflow_service_operation_id` varchar(26) DEFAULT NULL,\n `namespace` varchar(255) DEFAULT NULL,\n `endpoint_name` varchar(191) NOT NULL,\n `service_name` varchar(191) NOT NULL,\n `operation_name` varchar(191) NOT NULL,\n `caller_namespace` varchar(255) DEFAULT NULL,\n `caller_workflow_instance_id` varchar(191) DEFAULT NULL,\n `caller_workflow_run_id` varchar(26) DEFAULT NULL,\n `target_namespace` varchar(255) DEFAULT NULL,\n `linked_workflow_instance_id` varchar(191) DEFAULT NULL,\n `linked_workflow_run_id` varchar(26) DEFAULT NULL,\n `linked_workflow_update_id` varchar(26) DEFAULT NULL,\n `status` varchar(32) NOT NULL,\n `outcome` varchar(64) DEFAULT NULL,\n `operation_mode` varchar(32) NOT NULL,\n `resolved_binding_kind` varchar(64) DEFAULT NULL,\n `resolved_target_reference` varchar(191) DEFAULT NULL,\n `payload_codec` varchar(255) DEFAULT NULL,\n `input_payload_reference` varchar(191) DEFAULT NULL,\n `output_payload_reference` varchar(191) DEFAULT NULL,\n `failure_payload_reference` varchar(191) DEFAULT NULL,\n `failure_message` text DEFAULT NULL,\n `idempotency_key` varchar(191) DEFAULT NULL,\n `deadline_policy` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`deadline_policy`)),\n `idempotency_policy` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`idempotency_policy`)),\n `cancellation_policy` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`cancellation_policy`)),\n `retry_policy` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`retry_policy`)),\n `boundary_policy` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`boundary_policy`)),\n `metadata` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`metadata`)),\n `outcome_category` varchar(32) DEFAULT NULL,\n `outcome_reason` varchar(191) DEFAULT NULL,\n `outcome_message` text DEFAULT NULL,\n `outcome_metadata` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`outcome_metadata`)),\n `policy_name` varchar(191) DEFAULT NULL,\n `retry_after_seconds` int(10) unsigned DEFAULT NULL,\n `caller_principal_subject` varchar(191) DEFAULT NULL,\n `caller_principal_method` varchar(64) DEFAULT NULL,\n `caller_principal_roles` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`caller_principal_roles`)),\n `caller_principal_tenant` varchar(191) DEFAULT NULL,\n `caller_principal_claims` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`caller_principal_claims`)),\n `accepted_at` timestamp(6) NULL DEFAULT NULL,\n `started_at` timestamp(6) NULL DEFAULT NULL,\n `completed_at` timestamp(6) NULL DEFAULT NULL,\n `failed_at` timestamp(6) NULL DEFAULT NULL,\n `cancelled_at` timestamp(6) NULL DEFAULT NULL,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n `call_deadline_at` datetime(6) DEFAULT NULL,\n `idempotency_scope_hash` char(64) DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `wf_service_calls_idem_scope_unique` (`idempotency_scope_hash`),\n KEY `wf_service_calls_namespace_status_idx` (`namespace`,`status`),\n KEY `wf_service_calls_caller_status_idx` (`caller_namespace`,`status`),\n KEY `wf_service_calls_target_status_idx` (`target_namespace`,`status`),\n KEY `wf_service_calls_namespace_outcome_idx` (`namespace`,`outcome`),\n KEY `wf_service_calls_caller_outcome_idx` (`caller_namespace`,`outcome`),\n KEY `wf_service_calls_target_outcome_idx` (`target_namespace`,`outcome`),\n KEY `wf_service_calls_endpoint_idx` (`workflow_service_endpoint_id`),\n KEY `wf_service_calls_service_idx` (`workflow_service_id`),\n KEY `wf_service_calls_operation_idx` (`workflow_service_operation_id`),\n KEY `wf_service_calls_namespace_idx` (`namespace`),\n KEY `wf_service_calls_caller_namespace_idx` (`caller_namespace`),\n KEY `wf_service_calls_caller_instance_idx` (`caller_workflow_instance_id`),\n KEY `wf_service_calls_caller_run_idx` (`caller_workflow_run_id`),\n KEY `wf_service_calls_target_namespace_idx` (`target_namespace`),\n KEY `wf_service_calls_linked_instance_idx` (`linked_workflow_instance_id`),\n KEY `wf_service_calls_linked_run_idx` (`linked_workflow_run_id`),\n KEY `wf_service_calls_linked_update_idx` (`linked_workflow_update_id`),\n KEY `wf_service_calls_status_idx` (`status`),\n KEY `wf_service_calls_outcome_idx` (`outcome`),\n KEY `wf_service_calls_mode_idx` (`operation_mode`),\n KEY `wf_service_calls_binding_kind_idx` (`resolved_binding_kind`),\n KEY `wf_service_calls_idempotency_idx` (`idempotency_key`),\n KEY `wf_service_calls_outcome_category_idx` (`outcome_category`),\n KEY `wf_service_calls_policy_idx` (`policy_name`),\n KEY `wf_service_calls_principal_subject_idx` (`caller_principal_subject`),\n KEY `wf_service_calls_accepted_at_idx` (`accepted_at`),\n KEY `wf_service_calls_deadline_idx` (`status`,`call_deadline_at`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_worker_sessions", + "CREATE TABLE `workflow_worker_sessions` (\n `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n `namespace` varchar(128) NOT NULL,\n `session_id` varchar(255) NOT NULL,\n `connection` varchar(255) DEFAULT NULL,\n `queue` varchar(255) DEFAULT NULL,\n `requirements` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`requirements`)),\n `status` varchar(32) NOT NULL DEFAULT 'active',\n `lease_owner` varchar(255) DEFAULT NULL,\n `lease_owner_incarnation` char(64) DEFAULT NULL,\n `lease_expires_at` timestamp NULL DEFAULT NULL,\n `ttl_expires_at` timestamp NULL DEFAULT NULL,\n `closed_at` timestamp NULL DEFAULT NULL,\n `failure_reason` varchar(128) DEFAULT NULL,\n `lease_seconds` int(10) unsigned NOT NULL DEFAULT 120,\n `ttl_seconds` int(10) unsigned NOT NULL DEFAULT 1800,\n `max_concurrent_activities` int(10) unsigned NOT NULL DEFAULT 1,\n `create_if_missing` tinyint(1) NOT NULL DEFAULT 1,\n `allow_reacquire_after_failure` tinyint(1) NOT NULL DEFAULT 1,\n `last_heartbeat_at` timestamp NULL DEFAULT NULL,\n `created_at` timestamp NULL DEFAULT NULL,\n `updated_at` timestamp NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `workflow_worker_sessions_namespace_session_id_unique` (`namespace`,`session_id`),\n KEY `workflow_worker_sessions_namespace_status_index` (`namespace`,`status`),\n KEY `workflow_worker_sessions_namespace_lease_owner_status_index` (`namespace`,`lease_owner`,`status`),\n KEY `workflow_worker_sessions_namespace_queue_status_index` (`namespace`,`queue`,`status`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ] + ] +} diff --git a/rust/upgrade/php-2.11.3-from-2.9.7/mysql.json b/rust/upgrade/php-2.11.3-from-2.9.7/mysql.json new file mode 100644 index 000000000..1a69d1df3 --- /dev/null +++ b/rust/upgrade/php-2.11.3-from-2.9.7/mysql.json @@ -0,0 +1,42 @@ +{ + "remove": [ + [ + "workflow_durable_streams", + "CREATE TABLE `workflow_durable_streams` (\n `id` bigint unsigned NOT NULL AUTO_INCREMENT,\n `namespace` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL,\n `workflow_instance_id` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,\n `workflow_run_id` varchar(26) COLLATE utf8mb4_unicode_ci NOT NULL,\n `stream_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,\n `status` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'open',\n `last_offset` bigint NOT NULL DEFAULT '-1',\n `total_items` bigint unsigned NOT NULL DEFAULT '0',\n `retention_seconds` int unsigned DEFAULT NULL,\n `opened_at` timestamp(6) NULL DEFAULT NULL,\n `last_appended_at` timestamp(6) NULL DEFAULT NULL,\n `closed_at` timestamp(6) NULL DEFAULT NULL,\n `error_reason` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `pending_items` bigint unsigned NOT NULL DEFAULT '0',\n `metadata` json DEFAULT NULL,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `wf_durable_streams_run_name_unique` (`workflow_run_id`,`stream_name`),\n KEY `wf_durable_streams_ns_instance_name_idx` (`namespace`,`workflow_instance_id`,`stream_name`),\n KEY `wf_durable_streams_ns_status_idx` (`namespace`,`status`),\n KEY `workflow_durable_streams_namespace_index` (`namespace`),\n KEY `workflow_durable_streams_workflow_instance_id_index` (`workflow_instance_id`),\n KEY `workflow_durable_streams_workflow_run_id_index` (`workflow_run_id`),\n KEY `workflow_durable_streams_status_index` (`status`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_failures", + "CREATE TABLE `workflow_failures` (\n `id` varchar(26) COLLATE utf8mb4_unicode_ci NOT NULL,\n `workflow_run_id` varchar(26) COLLATE utf8mb4_unicode_ci NOT NULL,\n `source_kind` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n `source_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n `propagation_kind` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n `failure_category` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `non_retryable` tinyint(1) NOT NULL DEFAULT '0',\n `handled` tinyint(1) NOT NULL DEFAULT '0',\n `exception_class` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n `message` text COLLATE utf8mb4_unicode_ci NOT NULL,\n `file` text COLLATE utf8mb4_unicode_ci NOT NULL,\n `line` int unsigned DEFAULT NULL,\n `trace_preview` longtext COLLATE utf8mb4_unicode_ci,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n KEY `workflow_failures_source_kind_source_id_index` (`source_kind`,`source_id`),\n KEY `workflow_failures_workflow_run_id_index` (`workflow_run_id`),\n KEY `workflow_failures_failure_category_index` (`failure_category`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_worker_sessions", + "CREATE TABLE `workflow_worker_sessions` (\n `id` bigint unsigned NOT NULL AUTO_INCREMENT,\n `namespace` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL,\n `session_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n `connection` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `queue` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `requirements` json DEFAULT NULL,\n `status` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'active',\n `lease_owner` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `lease_expires_at` timestamp NULL DEFAULT NULL,\n `ttl_expires_at` timestamp NULL DEFAULT NULL,\n `closed_at` timestamp NULL DEFAULT NULL,\n `failure_reason` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `lease_seconds` int unsigned NOT NULL DEFAULT '120',\n `ttl_seconds` int unsigned NOT NULL DEFAULT '1800',\n `max_concurrent_activities` int unsigned NOT NULL DEFAULT '1',\n `create_if_missing` tinyint(1) NOT NULL DEFAULT '1',\n `allow_reacquire_after_failure` tinyint(1) NOT NULL DEFAULT '1',\n `last_heartbeat_at` timestamp NULL DEFAULT NULL,\n `created_at` timestamp NULL DEFAULT NULL,\n `updated_at` timestamp NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `workflow_worker_sessions_namespace_session_id_unique` (`namespace`,`session_id`),\n KEY `workflow_worker_sessions_namespace_status_index` (`namespace`,`status`),\n KEY `workflow_worker_sessions_namespace_lease_owner_status_index` (`namespace`,`lease_owner`,`status`),\n KEY `workflow_worker_sessions_namespace_queue_status_index` (`namespace`,`queue`,`status`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_service_calls", + "CREATE TABLE `workflow_service_calls` (\n `id` varchar(26) COLLATE utf8mb4_unicode_ci NOT NULL,\n `workflow_service_endpoint_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `workflow_service_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `workflow_service_operation_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `namespace` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `endpoint_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,\n `service_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,\n `operation_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,\n `caller_namespace` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `caller_workflow_instance_id` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `caller_workflow_run_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `target_namespace` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `linked_workflow_instance_id` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `linked_workflow_run_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `linked_workflow_update_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `status` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,\n `outcome` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `operation_mode` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,\n `resolved_binding_kind` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `resolved_target_reference` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `payload_codec` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `input_payload_reference` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `output_payload_reference` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `failure_payload_reference` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `failure_message` text COLLATE utf8mb4_unicode_ci,\n `idempotency_key` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `deadline_policy` json DEFAULT NULL,\n `idempotency_policy` json DEFAULT NULL,\n `cancellation_policy` json DEFAULT NULL,\n `retry_policy` json DEFAULT NULL,\n `boundary_policy` json DEFAULT NULL,\n `metadata` json DEFAULT NULL,\n `outcome_category` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `outcome_reason` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `outcome_message` text COLLATE utf8mb4_unicode_ci,\n `outcome_metadata` json DEFAULT NULL,\n `policy_name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `retry_after_seconds` int unsigned DEFAULT NULL,\n `caller_principal_subject` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `caller_principal_method` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `caller_principal_roles` json DEFAULT NULL,\n `caller_principal_tenant` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `caller_principal_claims` json DEFAULT NULL,\n `accepted_at` timestamp(6) NULL DEFAULT NULL,\n `started_at` timestamp(6) NULL DEFAULT NULL,\n `completed_at` timestamp(6) NULL DEFAULT NULL,\n `failed_at` timestamp(6) NULL DEFAULT NULL,\n `cancelled_at` timestamp(6) NULL DEFAULT NULL,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n `call_deadline_at` datetime(6) DEFAULT NULL,\n PRIMARY KEY (`id`),\n KEY `wf_service_calls_namespace_status_idx` (`namespace`,`status`),\n KEY `wf_service_calls_caller_status_idx` (`caller_namespace`,`status`),\n KEY `wf_service_calls_target_status_idx` (`target_namespace`,`status`),\n KEY `wf_service_calls_namespace_outcome_idx` (`namespace`,`outcome`),\n KEY `wf_service_calls_caller_outcome_idx` (`caller_namespace`,`outcome`),\n KEY `wf_service_calls_target_outcome_idx` (`target_namespace`,`outcome`),\n KEY `wf_service_calls_endpoint_idx` (`workflow_service_endpoint_id`),\n KEY `wf_service_calls_service_idx` (`workflow_service_id`),\n KEY `wf_service_calls_operation_idx` (`workflow_service_operation_id`),\n KEY `wf_service_calls_namespace_idx` (`namespace`),\n KEY `wf_service_calls_caller_namespace_idx` (`caller_namespace`),\n KEY `wf_service_calls_caller_instance_idx` (`caller_workflow_instance_id`),\n KEY `wf_service_calls_caller_run_idx` (`caller_workflow_run_id`),\n KEY `wf_service_calls_target_namespace_idx` (`target_namespace`),\n KEY `wf_service_calls_linked_instance_idx` (`linked_workflow_instance_id`),\n KEY `wf_service_calls_linked_run_idx` (`linked_workflow_run_id`),\n KEY `wf_service_calls_linked_update_idx` (`linked_workflow_update_id`),\n KEY `wf_service_calls_status_idx` (`status`),\n KEY `wf_service_calls_outcome_idx` (`outcome`),\n KEY `wf_service_calls_mode_idx` (`operation_mode`),\n KEY `wf_service_calls_binding_kind_idx` (`resolved_binding_kind`),\n KEY `wf_service_calls_idempotency_idx` (`idempotency_key`),\n KEY `wf_service_calls_outcome_category_idx` (`outcome_category`),\n KEY `wf_service_calls_policy_idx` (`policy_name`),\n KEY `wf_service_calls_principal_subject_idx` (`caller_principal_subject`),\n KEY `wf_service_calls_accepted_at_idx` (`accepted_at`),\n KEY `wf_service_calls_deadline_idx` (`status`,`call_deadline_at`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ] + ], + "add": [ + [ + "workflow_durable_streams", + "CREATE TABLE `workflow_durable_streams` (\n `id` bigint unsigned NOT NULL AUTO_INCREMENT,\n `namespace` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL,\n `workflow_instance_id` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,\n `workflow_run_id` varchar(26) COLLATE utf8mb4_unicode_ci NOT NULL,\n `stream_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,\n `status` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'open',\n `last_offset` bigint NOT NULL DEFAULT '-1',\n `total_items` bigint unsigned NOT NULL DEFAULT '0',\n `retention_seconds` int unsigned DEFAULT NULL,\n `opened_at` timestamp(6) NULL DEFAULT NULL,\n `last_appended_at` timestamp(6) NULL DEFAULT NULL,\n `closed_at` timestamp(6) NULL DEFAULT NULL,\n `error_reason` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `pending_items` bigint unsigned NOT NULL DEFAULT '0',\n `metadata` json DEFAULT NULL,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n `reclaim_at` timestamp(6) NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `wf_durable_streams_run_name_unique` (`workflow_run_id`,`stream_name`),\n KEY `wf_durable_streams_ns_instance_name_idx` (`namespace`,`workflow_instance_id`,`stream_name`),\n KEY `wf_durable_streams_ns_status_idx` (`namespace`,`status`),\n KEY `workflow_durable_streams_namespace_index` (`namespace`),\n KEY `workflow_durable_streams_workflow_instance_id_index` (`workflow_instance_id`),\n KEY `workflow_durable_streams_workflow_run_id_index` (`workflow_run_id`),\n KEY `workflow_durable_streams_status_index` (`status`),\n KEY `wf_durable_streams_ns_reclaim_idx` (`namespace`,`reclaim_at`,`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_failures", + "CREATE TABLE `workflow_failures` (\n `id` varchar(26) COLLATE utf8mb4_unicode_ci NOT NULL,\n `workflow_run_id` varchar(26) COLLATE utf8mb4_unicode_ci NOT NULL,\n `source_kind` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n `source_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n `propagation_kind` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n `failure_category` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `non_retryable` tinyint(1) NOT NULL DEFAULT '0',\n `handled` tinyint(1) NOT NULL DEFAULT '0',\n `exception_class` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n `message` text COLLATE utf8mb4_unicode_ci NOT NULL,\n `file` text COLLATE utf8mb4_unicode_ci NOT NULL,\n `line` int unsigned DEFAULT NULL,\n `trace_preview` longtext COLLATE utf8mb4_unicode_ci,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n `message_full` longtext COLLATE utf8mb4_unicode_ci,\n PRIMARY KEY (`id`),\n KEY `workflow_failures_source_kind_source_id_index` (`source_kind`,`source_id`),\n KEY `workflow_failures_workflow_run_id_index` (`workflow_run_id`),\n KEY `workflow_failures_failure_category_index` (`failure_category`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_service_call_dispatches", + "CREATE TABLE `workflow_service_call_dispatches` (\n `service_call_id` varchar(26) COLLATE utf8mb4_unicode_ci NOT NULL,\n `state` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,\n `dispatch_options` json NOT NULL,\n `attempt` int unsigned NOT NULL DEFAULT '1',\n `max_attempts` int unsigned NOT NULL DEFAULT '1',\n `available_at` timestamp(6) NOT NULL,\n `claim_generation` bigint unsigned NOT NULL DEFAULT '0',\n `claim_token` char(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `claimed_at` timestamp(6) NULL DEFAULT NULL,\n `claim_expires_at` timestamp(6) NULL DEFAULT NULL,\n `authorized_catalog_revision` char(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `authorized_policy_revision` char(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `dispatch_attempted_at` timestamp(6) NULL DEFAULT NULL,\n `repair_available_at` timestamp(6) NULL DEFAULT NULL,\n `completed_at` timestamp(6) NULL DEFAULT NULL,\n `last_error` text COLLATE utf8mb4_unicode_ci,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n PRIMARY KEY (`service_call_id`),\n KEY `wf_service_dispatch_due_idx` (`state`,`available_at`),\n KEY `wf_service_dispatch_claim_idx` (`state`,`claim_expires_at`),\n KEY `wf_service_dispatch_repair_idx` (`state`,`repair_available_at`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_service_calls", + "CREATE TABLE `workflow_service_calls` (\n `id` varchar(26) COLLATE utf8mb4_unicode_ci NOT NULL,\n `workflow_service_endpoint_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `workflow_service_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `workflow_service_operation_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `namespace` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `endpoint_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,\n `service_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,\n `operation_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,\n `caller_namespace` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `caller_workflow_instance_id` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `caller_workflow_run_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `target_namespace` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `linked_workflow_instance_id` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `linked_workflow_run_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `linked_workflow_update_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `status` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,\n `outcome` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `operation_mode` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,\n `resolved_binding_kind` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `resolved_target_reference` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `payload_codec` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `input_payload_reference` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `output_payload_reference` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `failure_payload_reference` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `failure_message` text COLLATE utf8mb4_unicode_ci,\n `idempotency_key` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `deadline_policy` json DEFAULT NULL,\n `idempotency_policy` json DEFAULT NULL,\n `cancellation_policy` json DEFAULT NULL,\n `retry_policy` json DEFAULT NULL,\n `boundary_policy` json DEFAULT NULL,\n `metadata` json DEFAULT NULL,\n `outcome_category` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `outcome_reason` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `outcome_message` text COLLATE utf8mb4_unicode_ci,\n `outcome_metadata` json DEFAULT NULL,\n `policy_name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `retry_after_seconds` int unsigned DEFAULT NULL,\n `caller_principal_subject` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `caller_principal_method` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `caller_principal_roles` json DEFAULT NULL,\n `caller_principal_tenant` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `caller_principal_claims` json DEFAULT NULL,\n `accepted_at` timestamp(6) NULL DEFAULT NULL,\n `started_at` timestamp(6) NULL DEFAULT NULL,\n `completed_at` timestamp(6) NULL DEFAULT NULL,\n `failed_at` timestamp(6) NULL DEFAULT NULL,\n `cancelled_at` timestamp(6) NULL DEFAULT NULL,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n `call_deadline_at` datetime(6) DEFAULT NULL,\n `idempotency_scope_hash` char(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `wf_service_calls_idem_scope_unique` (`idempotency_scope_hash`),\n KEY `wf_service_calls_namespace_status_idx` (`namespace`,`status`),\n KEY `wf_service_calls_caller_status_idx` (`caller_namespace`,`status`),\n KEY `wf_service_calls_target_status_idx` (`target_namespace`,`status`),\n KEY `wf_service_calls_namespace_outcome_idx` (`namespace`,`outcome`),\n KEY `wf_service_calls_caller_outcome_idx` (`caller_namespace`,`outcome`),\n KEY `wf_service_calls_target_outcome_idx` (`target_namespace`,`outcome`),\n KEY `wf_service_calls_endpoint_idx` (`workflow_service_endpoint_id`),\n KEY `wf_service_calls_service_idx` (`workflow_service_id`),\n KEY `wf_service_calls_operation_idx` (`workflow_service_operation_id`),\n KEY `wf_service_calls_namespace_idx` (`namespace`),\n KEY `wf_service_calls_caller_namespace_idx` (`caller_namespace`),\n KEY `wf_service_calls_caller_instance_idx` (`caller_workflow_instance_id`),\n KEY `wf_service_calls_caller_run_idx` (`caller_workflow_run_id`),\n KEY `wf_service_calls_target_namespace_idx` (`target_namespace`),\n KEY `wf_service_calls_linked_instance_idx` (`linked_workflow_instance_id`),\n KEY `wf_service_calls_linked_run_idx` (`linked_workflow_run_id`),\n KEY `wf_service_calls_linked_update_idx` (`linked_workflow_update_id`),\n KEY `wf_service_calls_status_idx` (`status`),\n KEY `wf_service_calls_outcome_idx` (`outcome`),\n KEY `wf_service_calls_mode_idx` (`operation_mode`),\n KEY `wf_service_calls_binding_kind_idx` (`resolved_binding_kind`),\n KEY `wf_service_calls_idempotency_idx` (`idempotency_key`),\n KEY `wf_service_calls_outcome_category_idx` (`outcome_category`),\n KEY `wf_service_calls_policy_idx` (`policy_name`),\n KEY `wf_service_calls_principal_subject_idx` (`caller_principal_subject`),\n KEY `wf_service_calls_accepted_at_idx` (`accepted_at`),\n KEY `wf_service_calls_deadline_idx` (`status`,`call_deadline_at`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_worker_sessions", + "CREATE TABLE `workflow_worker_sessions` (\n `id` bigint unsigned NOT NULL AUTO_INCREMENT,\n `namespace` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL,\n `session_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n `connection` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `queue` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `requirements` json DEFAULT NULL,\n `status` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'active',\n `lease_owner` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `lease_owner_incarnation` char(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `lease_expires_at` timestamp NULL DEFAULT NULL,\n `ttl_expires_at` timestamp NULL DEFAULT NULL,\n `closed_at` timestamp NULL DEFAULT NULL,\n `failure_reason` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `lease_seconds` int unsigned NOT NULL DEFAULT '120',\n `ttl_seconds` int unsigned NOT NULL DEFAULT '1800',\n `max_concurrent_activities` int unsigned NOT NULL DEFAULT '1',\n `create_if_missing` tinyint(1) NOT NULL DEFAULT '1',\n `allow_reacquire_after_failure` tinyint(1) NOT NULL DEFAULT '1',\n `last_heartbeat_at` timestamp NULL DEFAULT NULL,\n `created_at` timestamp NULL DEFAULT NULL,\n `updated_at` timestamp NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `workflow_worker_sessions_namespace_session_id_unique` (`namespace`,`session_id`),\n KEY `workflow_worker_sessions_namespace_status_index` (`namespace`,`status`),\n KEY `workflow_worker_sessions_namespace_lease_owner_status_index` (`namespace`,`lease_owner`,`status`),\n KEY `workflow_worker_sessions_namespace_queue_status_index` (`namespace`,`queue`,`status`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ] + ] +} diff --git a/rust/upgrade/php-2.11.3-from-2.9.7/postgres.json b/rust/upgrade/php-2.11.3-from-2.9.7/postgres.json new file mode 100644 index 000000000..dae058de7 --- /dev/null +++ b/rust/upgrade/php-2.11.3-from-2.9.7/postgres.json @@ -0,0 +1,382 @@ +{ + "remove": [], + "add": [ + [ + "column", + "workflow_durable_streams.18", + { + "collation": null, + "default": null, + "generated": "", + "identity": "", + "name": "reclaim_at", + "not_null": false, + "type": "timestamp(6) without time zone" + } + ], + [ + "column", + "workflow_failures.16", + { + "collation": "default", + "default": null, + "generated": "", + "identity": "", + "name": "message_full", + "not_null": false, + "type": "text" + } + ], + [ + "column", + "workflow_service_call_dispatches.1", + { + "collation": "default", + "default": null, + "generated": "", + "identity": "", + "name": "service_call_id", + "not_null": true, + "type": "character varying(26)" + } + ], + [ + "column", + "workflow_service_call_dispatches.10", + { + "collation": null, + "default": null, + "generated": "", + "identity": "", + "name": "claim_expires_at", + "not_null": false, + "type": "timestamp(6) without time zone" + } + ], + [ + "column", + "workflow_service_call_dispatches.11", + { + "collation": "default", + "default": null, + "generated": "", + "identity": "", + "name": "authorized_catalog_revision", + "not_null": false, + "type": "character(64)" + } + ], + [ + "column", + "workflow_service_call_dispatches.12", + { + "collation": "default", + "default": null, + "generated": "", + "identity": "", + "name": "authorized_policy_revision", + "not_null": false, + "type": "character(64)" + } + ], + [ + "column", + "workflow_service_call_dispatches.13", + { + "collation": null, + "default": null, + "generated": "", + "identity": "", + "name": "dispatch_attempted_at", + "not_null": false, + "type": "timestamp(6) without time zone" + } + ], + [ + "column", + "workflow_service_call_dispatches.14", + { + "collation": null, + "default": null, + "generated": "", + "identity": "", + "name": "repair_available_at", + "not_null": false, + "type": "timestamp(6) without time zone" + } + ], + [ + "column", + "workflow_service_call_dispatches.15", + { + "collation": null, + "default": null, + "generated": "", + "identity": "", + "name": "completed_at", + "not_null": false, + "type": "timestamp(6) without time zone" + } + ], + [ + "column", + "workflow_service_call_dispatches.16", + { + "collation": "default", + "default": null, + "generated": "", + "identity": "", + "name": "last_error", + "not_null": false, + "type": "text" + } + ], + [ + "column", + "workflow_service_call_dispatches.17", + { + "collation": null, + "default": null, + "generated": "", + "identity": "", + "name": "created_at", + "not_null": false, + "type": "timestamp(6) without time zone" + } + ], + [ + "column", + "workflow_service_call_dispatches.18", + { + "collation": null, + "default": null, + "generated": "", + "identity": "", + "name": "updated_at", + "not_null": false, + "type": "timestamp(6) without time zone" + } + ], + [ + "column", + "workflow_service_call_dispatches.2", + { + "collation": "default", + "default": null, + "generated": "", + "identity": "", + "name": "state", + "not_null": true, + "type": "character varying(32)" + } + ], + [ + "column", + "workflow_service_call_dispatches.3", + { + "collation": null, + "default": null, + "generated": "", + "identity": "", + "name": "dispatch_options", + "not_null": true, + "type": "json" + } + ], + [ + "column", + "workflow_service_call_dispatches.4", + { + "collation": null, + "default": "1", + "generated": "", + "identity": "", + "name": "attempt", + "not_null": true, + "type": "integer" + } + ], + [ + "column", + "workflow_service_call_dispatches.5", + { + "collation": null, + "default": "1", + "generated": "", + "identity": "", + "name": "max_attempts", + "not_null": true, + "type": "integer" + } + ], + [ + "column", + "workflow_service_call_dispatches.6", + { + "collation": null, + "default": null, + "generated": "", + "identity": "", + "name": "available_at", + "not_null": true, + "type": "timestamp(6) without time zone" + } + ], + [ + "column", + "workflow_service_call_dispatches.7", + { + "collation": null, + "default": "'0'::bigint", + "generated": "", + "identity": "", + "name": "claim_generation", + "not_null": true, + "type": "bigint" + } + ], + [ + "column", + "workflow_service_call_dispatches.8", + { + "collation": "default", + "default": null, + "generated": "", + "identity": "", + "name": "claim_token", + "not_null": false, + "type": "character(64)" + } + ], + [ + "column", + "workflow_service_call_dispatches.9", + { + "collation": null, + "default": null, + "generated": "", + "identity": "", + "name": "claimed_at", + "not_null": false, + "type": "timestamp(6) without time zone" + } + ], + [ + "column", + "workflow_service_calls.52", + { + "collation": "default", + "default": null, + "generated": "", + "identity": "", + "name": "idempotency_scope_hash", + "not_null": false, + "type": "character(64)" + } + ], + [ + "column", + "workflow_worker_sessions.21", + { + "collation": "default", + "default": null, + "generated": "", + "identity": "", + "name": "lease_owner_incarnation", + "not_null": false, + "type": "character(64)" + } + ], + [ + "constraint", + "workflow_service_call_dispatches.workflow_service_call_dispatches_pkey", + { + "deferrable": false, + "deferred": false, + "definition": "PRIMARY KEY (service_call_id)", + "validated": true + } + ], + [ + "index", + "wf_durable_streams_ns_reclaim_idx", + { + "definition": "CREATE INDEX wf_durable_streams_ns_reclaim_idx ON public.workflow_durable_streams USING btree (namespace, reclaim_at, id)", + "primary": false, + "ready": true, + "unique": false, + "valid": true + } + ], + [ + "index", + "wf_service_calls_idem_scope_unique", + { + "definition": "CREATE UNIQUE INDEX wf_service_calls_idem_scope_unique ON public.workflow_service_calls USING btree (idempotency_scope_hash)", + "primary": false, + "ready": true, + "unique": true, + "valid": true + } + ], + [ + "index", + "wf_service_dispatch_claim_idx", + { + "definition": "CREATE INDEX wf_service_dispatch_claim_idx ON public.workflow_service_call_dispatches USING btree (state, claim_expires_at)", + "primary": false, + "ready": true, + "unique": false, + "valid": true + } + ], + [ + "index", + "wf_service_dispatch_due_idx", + { + "definition": "CREATE INDEX wf_service_dispatch_due_idx ON public.workflow_service_call_dispatches USING btree (state, available_at)", + "primary": false, + "ready": true, + "unique": false, + "valid": true + } + ], + [ + "index", + "wf_service_dispatch_repair_idx", + { + "definition": "CREATE INDEX wf_service_dispatch_repair_idx ON public.workflow_service_call_dispatches USING btree (state, repair_available_at)", + "primary": false, + "ready": true, + "unique": false, + "valid": true + } + ], + [ + "index", + "workflow_service_call_dispatches_pkey", + { + "definition": "CREATE UNIQUE INDEX workflow_service_call_dispatches_pkey ON public.workflow_service_call_dispatches USING btree (service_call_id)", + "primary": true, + "ready": true, + "unique": true, + "valid": true + } + ], + [ + "relation", + "workflow_service_call_dispatches", + { + "access_method": "heap", + "force_row_security": false, + "kind": "r", + "options": null, + "partition": false, + "partition_key": null, + "persistence": "p", + "row_security": false, + "view": null + } + ] + ] +} diff --git a/rust/upgrade/php-2.11.3-from-2.9.7/sqlite.json b/rust/upgrade/php-2.11.3-from-2.9.7/sqlite.json new file mode 100644 index 000000000..aeb8845b3 --- /dev/null +++ b/rust/upgrade/php-2.11.3-from-2.9.7/sqlite.json @@ -0,0 +1,90 @@ +{ + "remove": [ + [ + "table", + "workflow_durable_streams", + "workflow_durable_streams", + "CREATE TABLE \"workflow_durable_streams\" (\"id\" integer primary key autoincrement not null, \"namespace\" varchar not null, \"workflow_instance_id\" varchar not null, \"workflow_run_id\" varchar not null, \"stream_name\" varchar not null, \"status\" varchar not null default 'open', \"last_offset\" integer not null default '-1', \"total_items\" integer not null default '0', \"retention_seconds\" integer, \"opened_at\" datetime, \"last_appended_at\" datetime, \"closed_at\" datetime, \"error_reason\" varchar, \"pending_items\" integer not null default '0', \"metadata\" text, \"created_at\" datetime, \"updated_at\" datetime)" + ], + [ + "table", + "workflow_failures", + "workflow_failures", + "CREATE TABLE \"workflow_failures\" (\"id\" varchar not null, \"workflow_run_id\" varchar not null, \"source_kind\" varchar not null, \"source_id\" varchar not null, \"propagation_kind\" varchar not null, \"failure_category\" varchar, \"non_retryable\" tinyint(1) not null default '0', \"handled\" tinyint(1) not null default '0', \"exception_class\" varchar not null, \"message\" text not null, \"file\" text not null, \"line\" integer, \"trace_preview\" text, \"created_at\" datetime, \"updated_at\" datetime, primary key (\"id\"))" + ], + [ + "table", + "workflow_worker_sessions", + "workflow_worker_sessions", + "CREATE TABLE \"workflow_worker_sessions\" (\"id\" integer primary key autoincrement not null, \"namespace\" varchar not null, \"session_id\" varchar not null, \"connection\" varchar, \"queue\" varchar, \"requirements\" text, \"status\" varchar not null default 'active', \"lease_owner\" varchar, \"lease_expires_at\" datetime, \"ttl_expires_at\" datetime, \"closed_at\" datetime, \"failure_reason\" varchar, \"lease_seconds\" integer not null default '120', \"ttl_seconds\" integer not null default '1800', \"max_concurrent_activities\" integer not null default '1', \"create_if_missing\" tinyint(1) not null default '1', \"allow_reacquire_after_failure\" tinyint(1) not null default '1', \"last_heartbeat_at\" datetime, \"created_at\" datetime, \"updated_at\" datetime)" + ], + [ + "table", + "workflow_service_calls", + "workflow_service_calls", + "CREATE TABLE \"workflow_service_calls\" (\"id\" varchar not null, \"workflow_service_endpoint_id\" varchar, \"workflow_service_id\" varchar, \"workflow_service_operation_id\" varchar, \"namespace\" varchar, \"endpoint_name\" varchar not null, \"service_name\" varchar not null, \"operation_name\" varchar not null, \"caller_namespace\" varchar, \"caller_workflow_instance_id\" varchar, \"caller_workflow_run_id\" varchar, \"target_namespace\" varchar, \"linked_workflow_instance_id\" varchar, \"linked_workflow_run_id\" varchar, \"linked_workflow_update_id\" varchar, \"status\" varchar not null, \"outcome\" varchar, \"operation_mode\" varchar not null, \"resolved_binding_kind\" varchar, \"resolved_target_reference\" varchar, \"payload_codec\" varchar, \"input_payload_reference\" varchar, \"output_payload_reference\" varchar, \"failure_payload_reference\" varchar, \"failure_message\" text, \"idempotency_key\" varchar, \"deadline_policy\" text, \"idempotency_policy\" text, \"cancellation_policy\" text, \"retry_policy\" text, \"boundary_policy\" text, \"metadata\" text, \"outcome_category\" varchar, \"outcome_reason\" varchar, \"outcome_message\" text, \"outcome_metadata\" text, \"policy_name\" varchar, \"retry_after_seconds\" integer, \"caller_principal_subject\" varchar, \"caller_principal_method\" varchar, \"caller_principal_roles\" text, \"caller_principal_tenant\" varchar, \"caller_principal_claims\" text, \"accepted_at\" datetime, \"started_at\" datetime, \"completed_at\" datetime, \"failed_at\" datetime, \"cancelled_at\" datetime, \"created_at\" datetime, \"updated_at\" datetime, \"call_deadline_at\" datetime, primary key (\"id\"))" + ] + ], + "add": [ + [ + "index", + "wf_durable_streams_ns_reclaim_idx", + "workflow_durable_streams", + "CREATE INDEX \"wf_durable_streams_ns_reclaim_idx\" on \"workflow_durable_streams\" (\"namespace\", \"reclaim_at\", \"id\")" + ], + [ + "index", + "wf_service_calls_idem_scope_unique", + "workflow_service_calls", + "CREATE UNIQUE INDEX \"wf_service_calls_idem_scope_unique\" on \"workflow_service_calls\" (\"idempotency_scope_hash\")" + ], + [ + "index", + "wf_service_dispatch_claim_idx", + "workflow_service_call_dispatches", + "CREATE INDEX \"wf_service_dispatch_claim_idx\" on \"workflow_service_call_dispatches\" (\"state\", \"claim_expires_at\")" + ], + [ + "index", + "wf_service_dispatch_due_idx", + "workflow_service_call_dispatches", + "CREATE INDEX \"wf_service_dispatch_due_idx\" on \"workflow_service_call_dispatches\" (\"state\", \"available_at\")" + ], + [ + "index", + "wf_service_dispatch_repair_idx", + "workflow_service_call_dispatches", + "CREATE INDEX \"wf_service_dispatch_repair_idx\" on \"workflow_service_call_dispatches\" (\"state\", \"repair_available_at\")" + ], + [ + "table", + "workflow_durable_streams", + "workflow_durable_streams", + "CREATE TABLE \"workflow_durable_streams\" (\"id\" integer primary key autoincrement not null, \"namespace\" varchar not null, \"workflow_instance_id\" varchar not null, \"workflow_run_id\" varchar not null, \"stream_name\" varchar not null, \"status\" varchar not null default 'open', \"last_offset\" integer not null default '-1', \"total_items\" integer not null default '0', \"retention_seconds\" integer, \"opened_at\" datetime, \"last_appended_at\" datetime, \"closed_at\" datetime, \"error_reason\" varchar, \"pending_items\" integer not null default '0', \"metadata\" text, \"created_at\" datetime, \"updated_at\" datetime, \"reclaim_at\" datetime)" + ], + [ + "table", + "workflow_failures", + "workflow_failures", + "CREATE TABLE \"workflow_failures\" (\"id\" varchar not null, \"workflow_run_id\" varchar not null, \"source_kind\" varchar not null, \"source_id\" varchar not null, \"propagation_kind\" varchar not null, \"failure_category\" varchar, \"non_retryable\" tinyint(1) not null default '0', \"handled\" tinyint(1) not null default '0', \"exception_class\" varchar not null, \"message\" text not null, \"file\" text not null, \"line\" integer, \"trace_preview\" text, \"created_at\" datetime, \"updated_at\" datetime, \"message_full\" text, primary key (\"id\"))" + ], + [ + "table", + "workflow_service_call_dispatches", + "workflow_service_call_dispatches", + "CREATE TABLE \"workflow_service_call_dispatches\" (\"service_call_id\" varchar not null, \"state\" varchar not null, \"dispatch_options\" text not null, \"attempt\" integer not null default '1', \"max_attempts\" integer not null default '1', \"available_at\" datetime not null, \"claim_generation\" integer not null default '0', \"claim_token\" varchar, \"claimed_at\" datetime, \"claim_expires_at\" datetime, \"authorized_catalog_revision\" varchar, \"authorized_policy_revision\" varchar, \"dispatch_attempted_at\" datetime, \"repair_available_at\" datetime, \"completed_at\" datetime, \"last_error\" text, \"created_at\" datetime, \"updated_at\" datetime, primary key (\"service_call_id\"))" + ], + [ + "table", + "workflow_service_calls", + "workflow_service_calls", + "CREATE TABLE \"workflow_service_calls\" (\"id\" varchar not null, \"workflow_service_endpoint_id\" varchar, \"workflow_service_id\" varchar, \"workflow_service_operation_id\" varchar, \"namespace\" varchar, \"endpoint_name\" varchar not null, \"service_name\" varchar not null, \"operation_name\" varchar not null, \"caller_namespace\" varchar, \"caller_workflow_instance_id\" varchar, \"caller_workflow_run_id\" varchar, \"target_namespace\" varchar, \"linked_workflow_instance_id\" varchar, \"linked_workflow_run_id\" varchar, \"linked_workflow_update_id\" varchar, \"status\" varchar not null, \"outcome\" varchar, \"operation_mode\" varchar not null, \"resolved_binding_kind\" varchar, \"resolved_target_reference\" varchar, \"payload_codec\" varchar, \"input_payload_reference\" varchar, \"output_payload_reference\" varchar, \"failure_payload_reference\" varchar, \"failure_message\" text, \"idempotency_key\" varchar, \"deadline_policy\" text, \"idempotency_policy\" text, \"cancellation_policy\" text, \"retry_policy\" text, \"boundary_policy\" text, \"metadata\" text, \"outcome_category\" varchar, \"outcome_reason\" varchar, \"outcome_message\" text, \"outcome_metadata\" text, \"policy_name\" varchar, \"retry_after_seconds\" integer, \"caller_principal_subject\" varchar, \"caller_principal_method\" varchar, \"caller_principal_roles\" text, \"caller_principal_tenant\" varchar, \"caller_principal_claims\" text, \"accepted_at\" datetime, \"started_at\" datetime, \"completed_at\" datetime, \"failed_at\" datetime, \"cancelled_at\" datetime, \"created_at\" datetime, \"updated_at\" datetime, \"call_deadline_at\" datetime, \"idempotency_scope_hash\" varchar, primary key (\"id\"))" + ], + [ + "table", + "workflow_worker_sessions", + "workflow_worker_sessions", + "CREATE TABLE \"workflow_worker_sessions\" (\"id\" integer primary key autoincrement not null, \"namespace\" varchar not null, \"session_id\" varchar not null, \"connection\" varchar, \"queue\" varchar, \"requirements\" text, \"status\" varchar not null default 'active', \"lease_owner\" varchar, \"lease_expires_at\" datetime, \"ttl_expires_at\" datetime, \"closed_at\" datetime, \"failure_reason\" varchar, \"lease_seconds\" integer not null default '120', \"ttl_seconds\" integer not null default '1800', \"max_concurrent_activities\" integer not null default '1', \"create_if_missing\" tinyint(1) not null default '1', \"allow_reacquire_after_failure\" tinyint(1) not null default '1', \"last_heartbeat_at\" datetime, \"created_at\" datetime, \"updated_at\" datetime, \"lease_owner_incarnation\" varchar)" + ] + ] +} diff --git a/rust/upgrade/php-2.11.3/README.md b/rust/upgrade/php-2.11.3/README.md new file mode 100644 index 000000000..a73d6004e --- /dev/null +++ b/rust/upgrade/php-2.11.3/README.md @@ -0,0 +1,48 @@ +# Exact PHP source-catalog variant: Server 2.11.3 + +These deltas apply to the fresh [PHP 2.11.1 layout](../php-2.11.1/README.md). +They come from normal bootstrap of published Server 2.11.3, tag `2.11.3`, +source `8009904aac5d0a2bf082f06ec0f9f35b816d885a`, image index +`sha256:923a8924ecad13485562d7af30dc87d7fd6d9d7d4758ec8113bac0766fa8603e`, +which pins Core (Workflow package) 2.14.2. + +The physical changes from 2.11.1: + +- Core `2026_10_23_000100_add_full_failure_message_storage`: nullable + `workflow_failures.message_full`. +- Core `2026_10_25_000100_add_service_call_idempotency_scope`: nullable + `workflow_service_calls.idempotency_scope_hash` and its unique index + `wf_service_calls_idem_scope_unique`. +- Core `2026_10_26_000100_create_workflow_service_call_dispatches_table`: the + `workflow_service_call_dispatches` table and its three indexes. +- Server: nullable `workflow_worker_sessions.lease_owner_incarnation`, and + nullable `workflow_durable_streams.reclaim_at` with its index + `wf_durable_streams_ns_reclaim_idx`. + +The same delta was captured from four databases: a fresh 2.11.3 database, +one 2.11.1 created and 2.11.3 migrated, and one 2.8.0 created and 2.11.3 +migrated. The new migrations follow every earlier one, so all three end with +the same layout. A database 2.9.x created keeps its own task column order; +that is the [`php-2.11.3-from-2.9.7`](../php-2.11.3-from-2.9.7/README.md) +variant. + +Definitions were read from actual published-image databases: SQLite, MySQL +8.0, MariaDB 10.11 through the `mysql` connection, and PostgreSQL 17. No +application row values or credentials are recorded. + +Takeover: + +- copies each active session's recorded holder incarnation into + `dw_worker_session_holders`. PHP and native both use the SHA-256 of the + holder registration's token, so a session PHP already orphaned stays + orphaned; +- counts and drops `message_full` (`full_failure_messages_removed`). Native + reads the labelled preview in `message`, as PHP's own older readers do; + retained history and the verified backup keep the full message; +- drops the idempotency scope digest and its index. The digest is derived + from the call's own target namespace, names and key; +- refuses while any dispatch is still scheduled or claimed + (`upgrade_source_service_call_dispatches_pending`), and otherwise counts + and drops the completed outbox (`service_call_dispatches_removed`); +- drops `reclaim_at` and its index. Only a durable stream has one, and + takeover still refuses any message stream. diff --git a/rust/upgrade/php-2.11.3/mariadb.json b/rust/upgrade/php-2.11.3/mariadb.json new file mode 100644 index 000000000..71db587f2 --- /dev/null +++ b/rust/upgrade/php-2.11.3/mariadb.json @@ -0,0 +1,42 @@ +{ + "remove": [ + [ + "workflow_durable_streams", + "CREATE TABLE `workflow_durable_streams` (\n `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n `namespace` varchar(128) NOT NULL,\n `workflow_instance_id` varchar(191) NOT NULL,\n `workflow_run_id` varchar(26) NOT NULL,\n `stream_name` varchar(191) NOT NULL,\n `status` varchar(16) NOT NULL DEFAULT 'open',\n `last_offset` bigint(20) NOT NULL DEFAULT -1,\n `total_items` bigint(20) unsigned NOT NULL DEFAULT 0,\n `retention_seconds` int(10) unsigned DEFAULT NULL,\n `opened_at` timestamp(6) NULL DEFAULT NULL,\n `last_appended_at` timestamp(6) NULL DEFAULT NULL,\n `closed_at` timestamp(6) NULL DEFAULT NULL,\n `error_reason` varchar(191) DEFAULT NULL,\n `pending_items` bigint(20) unsigned NOT NULL DEFAULT 0,\n `metadata` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`metadata`)),\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `wf_durable_streams_run_name_unique` (`workflow_run_id`,`stream_name`),\n KEY `wf_durable_streams_ns_instance_name_idx` (`namespace`,`workflow_instance_id`,`stream_name`),\n KEY `wf_durable_streams_ns_status_idx` (`namespace`,`status`),\n KEY `workflow_durable_streams_namespace_index` (`namespace`),\n KEY `workflow_durable_streams_workflow_instance_id_index` (`workflow_instance_id`),\n KEY `workflow_durable_streams_workflow_run_id_index` (`workflow_run_id`),\n KEY `workflow_durable_streams_status_index` (`status`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_failures", + "CREATE TABLE `workflow_failures` (\n `id` varchar(26) NOT NULL,\n `workflow_run_id` varchar(26) NOT NULL,\n `source_kind` varchar(255) NOT NULL,\n `source_id` varchar(255) NOT NULL,\n `propagation_kind` varchar(255) NOT NULL,\n `failure_category` varchar(255) DEFAULT NULL,\n `non_retryable` tinyint(1) NOT NULL DEFAULT 0,\n `handled` tinyint(1) NOT NULL DEFAULT 0,\n `exception_class` varchar(255) NOT NULL,\n `message` text NOT NULL,\n `file` text NOT NULL,\n `line` int(10) unsigned DEFAULT NULL,\n `trace_preview` longtext DEFAULT NULL,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n KEY `workflow_failures_source_kind_source_id_index` (`source_kind`,`source_id`),\n KEY `workflow_failures_workflow_run_id_index` (`workflow_run_id`),\n KEY `workflow_failures_failure_category_index` (`failure_category`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_worker_sessions", + "CREATE TABLE `workflow_worker_sessions` (\n `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n `namespace` varchar(128) NOT NULL,\n `session_id` varchar(255) NOT NULL,\n `connection` varchar(255) DEFAULT NULL,\n `queue` varchar(255) DEFAULT NULL,\n `requirements` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`requirements`)),\n `status` varchar(32) NOT NULL DEFAULT 'active',\n `lease_owner` varchar(255) DEFAULT NULL,\n `lease_expires_at` timestamp NULL DEFAULT NULL,\n `ttl_expires_at` timestamp NULL DEFAULT NULL,\n `closed_at` timestamp NULL DEFAULT NULL,\n `failure_reason` varchar(128) DEFAULT NULL,\n `lease_seconds` int(10) unsigned NOT NULL DEFAULT 120,\n `ttl_seconds` int(10) unsigned NOT NULL DEFAULT 1800,\n `max_concurrent_activities` int(10) unsigned NOT NULL DEFAULT 1,\n `create_if_missing` tinyint(1) NOT NULL DEFAULT 1,\n `allow_reacquire_after_failure` tinyint(1) NOT NULL DEFAULT 1,\n `last_heartbeat_at` timestamp NULL DEFAULT NULL,\n `created_at` timestamp NULL DEFAULT NULL,\n `updated_at` timestamp NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `workflow_worker_sessions_namespace_session_id_unique` (`namespace`,`session_id`),\n KEY `workflow_worker_sessions_namespace_status_index` (`namespace`,`status`),\n KEY `workflow_worker_sessions_namespace_lease_owner_status_index` (`namespace`,`lease_owner`,`status`),\n KEY `workflow_worker_sessions_namespace_queue_status_index` (`namespace`,`queue`,`status`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_service_calls", + "CREATE TABLE `workflow_service_calls` (\n `id` varchar(26) NOT NULL,\n `workflow_service_endpoint_id` varchar(26) DEFAULT NULL,\n `workflow_service_id` varchar(26) DEFAULT NULL,\n `workflow_service_operation_id` varchar(26) DEFAULT NULL,\n `namespace` varchar(255) DEFAULT NULL,\n `endpoint_name` varchar(191) NOT NULL,\n `service_name` varchar(191) NOT NULL,\n `operation_name` varchar(191) NOT NULL,\n `caller_namespace` varchar(255) DEFAULT NULL,\n `caller_workflow_instance_id` varchar(191) DEFAULT NULL,\n `caller_workflow_run_id` varchar(26) DEFAULT NULL,\n `target_namespace` varchar(255) DEFAULT NULL,\n `linked_workflow_instance_id` varchar(191) DEFAULT NULL,\n `linked_workflow_run_id` varchar(26) DEFAULT NULL,\n `linked_workflow_update_id` varchar(26) DEFAULT NULL,\n `status` varchar(32) NOT NULL,\n `outcome` varchar(64) DEFAULT NULL,\n `operation_mode` varchar(32) NOT NULL,\n `resolved_binding_kind` varchar(64) DEFAULT NULL,\n `resolved_target_reference` varchar(191) DEFAULT NULL,\n `payload_codec` varchar(255) DEFAULT NULL,\n `input_payload_reference` varchar(191) DEFAULT NULL,\n `output_payload_reference` varchar(191) DEFAULT NULL,\n `failure_payload_reference` varchar(191) DEFAULT NULL,\n `failure_message` text DEFAULT NULL,\n `idempotency_key` varchar(191) DEFAULT NULL,\n `deadline_policy` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`deadline_policy`)),\n `idempotency_policy` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`idempotency_policy`)),\n `cancellation_policy` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`cancellation_policy`)),\n `retry_policy` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`retry_policy`)),\n `boundary_policy` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`boundary_policy`)),\n `metadata` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`metadata`)),\n `outcome_category` varchar(32) DEFAULT NULL,\n `outcome_reason` varchar(191) DEFAULT NULL,\n `outcome_message` text DEFAULT NULL,\n `outcome_metadata` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`outcome_metadata`)),\n `policy_name` varchar(191) DEFAULT NULL,\n `retry_after_seconds` int(10) unsigned DEFAULT NULL,\n `caller_principal_subject` varchar(191) DEFAULT NULL,\n `caller_principal_method` varchar(64) DEFAULT NULL,\n `caller_principal_roles` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`caller_principal_roles`)),\n `caller_principal_tenant` varchar(191) DEFAULT NULL,\n `caller_principal_claims` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`caller_principal_claims`)),\n `accepted_at` timestamp(6) NULL DEFAULT NULL,\n `started_at` timestamp(6) NULL DEFAULT NULL,\n `completed_at` timestamp(6) NULL DEFAULT NULL,\n `failed_at` timestamp(6) NULL DEFAULT NULL,\n `cancelled_at` timestamp(6) NULL DEFAULT NULL,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n `call_deadline_at` datetime(6) DEFAULT NULL,\n PRIMARY KEY (`id`),\n KEY `wf_service_calls_namespace_status_idx` (`namespace`,`status`),\n KEY `wf_service_calls_caller_status_idx` (`caller_namespace`,`status`),\n KEY `wf_service_calls_target_status_idx` (`target_namespace`,`status`),\n KEY `wf_service_calls_namespace_outcome_idx` (`namespace`,`outcome`),\n KEY `wf_service_calls_caller_outcome_idx` (`caller_namespace`,`outcome`),\n KEY `wf_service_calls_target_outcome_idx` (`target_namespace`,`outcome`),\n KEY `wf_service_calls_endpoint_idx` (`workflow_service_endpoint_id`),\n KEY `wf_service_calls_service_idx` (`workflow_service_id`),\n KEY `wf_service_calls_operation_idx` (`workflow_service_operation_id`),\n KEY `wf_service_calls_namespace_idx` (`namespace`),\n KEY `wf_service_calls_caller_namespace_idx` (`caller_namespace`),\n KEY `wf_service_calls_caller_instance_idx` (`caller_workflow_instance_id`),\n KEY `wf_service_calls_caller_run_idx` (`caller_workflow_run_id`),\n KEY `wf_service_calls_target_namespace_idx` (`target_namespace`),\n KEY `wf_service_calls_linked_instance_idx` (`linked_workflow_instance_id`),\n KEY `wf_service_calls_linked_run_idx` (`linked_workflow_run_id`),\n KEY `wf_service_calls_linked_update_idx` (`linked_workflow_update_id`),\n KEY `wf_service_calls_status_idx` (`status`),\n KEY `wf_service_calls_outcome_idx` (`outcome`),\n KEY `wf_service_calls_mode_idx` (`operation_mode`),\n KEY `wf_service_calls_binding_kind_idx` (`resolved_binding_kind`),\n KEY `wf_service_calls_idempotency_idx` (`idempotency_key`),\n KEY `wf_service_calls_outcome_category_idx` (`outcome_category`),\n KEY `wf_service_calls_policy_idx` (`policy_name`),\n KEY `wf_service_calls_principal_subject_idx` (`caller_principal_subject`),\n KEY `wf_service_calls_accepted_at_idx` (`accepted_at`),\n KEY `wf_service_calls_deadline_idx` (`status`,`call_deadline_at`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ] + ], + "add": [ + [ + "workflow_durable_streams", + "CREATE TABLE `workflow_durable_streams` (\n `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n `namespace` varchar(128) NOT NULL,\n `workflow_instance_id` varchar(191) NOT NULL,\n `workflow_run_id` varchar(26) NOT NULL,\n `stream_name` varchar(191) NOT NULL,\n `status` varchar(16) NOT NULL DEFAULT 'open',\n `last_offset` bigint(20) NOT NULL DEFAULT -1,\n `total_items` bigint(20) unsigned NOT NULL DEFAULT 0,\n `retention_seconds` int(10) unsigned DEFAULT NULL,\n `opened_at` timestamp(6) NULL DEFAULT NULL,\n `last_appended_at` timestamp(6) NULL DEFAULT NULL,\n `closed_at` timestamp(6) NULL DEFAULT NULL,\n `error_reason` varchar(191) DEFAULT NULL,\n `pending_items` bigint(20) unsigned NOT NULL DEFAULT 0,\n `metadata` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`metadata`)),\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n `reclaim_at` timestamp(6) NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `wf_durable_streams_run_name_unique` (`workflow_run_id`,`stream_name`),\n KEY `wf_durable_streams_ns_instance_name_idx` (`namespace`,`workflow_instance_id`,`stream_name`),\n KEY `wf_durable_streams_ns_status_idx` (`namespace`,`status`),\n KEY `workflow_durable_streams_namespace_index` (`namespace`),\n KEY `workflow_durable_streams_workflow_instance_id_index` (`workflow_instance_id`),\n KEY `workflow_durable_streams_workflow_run_id_index` (`workflow_run_id`),\n KEY `workflow_durable_streams_status_index` (`status`),\n KEY `wf_durable_streams_ns_reclaim_idx` (`namespace`,`reclaim_at`,`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_failures", + "CREATE TABLE `workflow_failures` (\n `id` varchar(26) NOT NULL,\n `workflow_run_id` varchar(26) NOT NULL,\n `source_kind` varchar(255) NOT NULL,\n `source_id` varchar(255) NOT NULL,\n `propagation_kind` varchar(255) NOT NULL,\n `failure_category` varchar(255) DEFAULT NULL,\n `non_retryable` tinyint(1) NOT NULL DEFAULT 0,\n `handled` tinyint(1) NOT NULL DEFAULT 0,\n `exception_class` varchar(255) NOT NULL,\n `message` text NOT NULL,\n `file` text NOT NULL,\n `line` int(10) unsigned DEFAULT NULL,\n `trace_preview` longtext DEFAULT NULL,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n `message_full` longtext DEFAULT NULL,\n PRIMARY KEY (`id`),\n KEY `workflow_failures_source_kind_source_id_index` (`source_kind`,`source_id`),\n KEY `workflow_failures_workflow_run_id_index` (`workflow_run_id`),\n KEY `workflow_failures_failure_category_index` (`failure_category`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_service_call_dispatches", + "CREATE TABLE `workflow_service_call_dispatches` (\n `service_call_id` varchar(26) NOT NULL,\n `state` varchar(32) NOT NULL,\n `dispatch_options` longtext COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`dispatch_options`)),\n `attempt` int(10) unsigned NOT NULL DEFAULT 1,\n `max_attempts` int(10) unsigned NOT NULL DEFAULT 1,\n `available_at` timestamp(6) NOT NULL,\n `claim_generation` bigint(20) unsigned NOT NULL DEFAULT 0,\n `claim_token` char(64) DEFAULT NULL,\n `claimed_at` timestamp(6) NULL DEFAULT NULL,\n `claim_expires_at` timestamp(6) NULL DEFAULT NULL,\n `authorized_catalog_revision` char(64) DEFAULT NULL,\n `authorized_policy_revision` char(64) DEFAULT NULL,\n `dispatch_attempted_at` timestamp(6) NULL DEFAULT NULL,\n `repair_available_at` timestamp(6) NULL DEFAULT NULL,\n `completed_at` timestamp(6) NULL DEFAULT NULL,\n `last_error` text DEFAULT NULL,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n PRIMARY KEY (`service_call_id`),\n KEY `wf_service_dispatch_due_idx` (`state`,`available_at`),\n KEY `wf_service_dispatch_claim_idx` (`state`,`claim_expires_at`),\n KEY `wf_service_dispatch_repair_idx` (`state`,`repair_available_at`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_service_calls", + "CREATE TABLE `workflow_service_calls` (\n `id` varchar(26) NOT NULL,\n `workflow_service_endpoint_id` varchar(26) DEFAULT NULL,\n `workflow_service_id` varchar(26) DEFAULT NULL,\n `workflow_service_operation_id` varchar(26) DEFAULT NULL,\n `namespace` varchar(255) DEFAULT NULL,\n `endpoint_name` varchar(191) NOT NULL,\n `service_name` varchar(191) NOT NULL,\n `operation_name` varchar(191) NOT NULL,\n `caller_namespace` varchar(255) DEFAULT NULL,\n `caller_workflow_instance_id` varchar(191) DEFAULT NULL,\n `caller_workflow_run_id` varchar(26) DEFAULT NULL,\n `target_namespace` varchar(255) DEFAULT NULL,\n `linked_workflow_instance_id` varchar(191) DEFAULT NULL,\n `linked_workflow_run_id` varchar(26) DEFAULT NULL,\n `linked_workflow_update_id` varchar(26) DEFAULT NULL,\n `status` varchar(32) NOT NULL,\n `outcome` varchar(64) DEFAULT NULL,\n `operation_mode` varchar(32) NOT NULL,\n `resolved_binding_kind` varchar(64) DEFAULT NULL,\n `resolved_target_reference` varchar(191) DEFAULT NULL,\n `payload_codec` varchar(255) DEFAULT NULL,\n `input_payload_reference` varchar(191) DEFAULT NULL,\n `output_payload_reference` varchar(191) DEFAULT NULL,\n `failure_payload_reference` varchar(191) DEFAULT NULL,\n `failure_message` text DEFAULT NULL,\n `idempotency_key` varchar(191) DEFAULT NULL,\n `deadline_policy` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`deadline_policy`)),\n `idempotency_policy` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`idempotency_policy`)),\n `cancellation_policy` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`cancellation_policy`)),\n `retry_policy` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`retry_policy`)),\n `boundary_policy` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`boundary_policy`)),\n `metadata` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`metadata`)),\n `outcome_category` varchar(32) DEFAULT NULL,\n `outcome_reason` varchar(191) DEFAULT NULL,\n `outcome_message` text DEFAULT NULL,\n `outcome_metadata` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`outcome_metadata`)),\n `policy_name` varchar(191) DEFAULT NULL,\n `retry_after_seconds` int(10) unsigned DEFAULT NULL,\n `caller_principal_subject` varchar(191) DEFAULT NULL,\n `caller_principal_method` varchar(64) DEFAULT NULL,\n `caller_principal_roles` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`caller_principal_roles`)),\n `caller_principal_tenant` varchar(191) DEFAULT NULL,\n `caller_principal_claims` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`caller_principal_claims`)),\n `accepted_at` timestamp(6) NULL DEFAULT NULL,\n `started_at` timestamp(6) NULL DEFAULT NULL,\n `completed_at` timestamp(6) NULL DEFAULT NULL,\n `failed_at` timestamp(6) NULL DEFAULT NULL,\n `cancelled_at` timestamp(6) NULL DEFAULT NULL,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n `call_deadline_at` datetime(6) DEFAULT NULL,\n `idempotency_scope_hash` char(64) DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `wf_service_calls_idem_scope_unique` (`idempotency_scope_hash`),\n KEY `wf_service_calls_namespace_status_idx` (`namespace`,`status`),\n KEY `wf_service_calls_caller_status_idx` (`caller_namespace`,`status`),\n KEY `wf_service_calls_target_status_idx` (`target_namespace`,`status`),\n KEY `wf_service_calls_namespace_outcome_idx` (`namespace`,`outcome`),\n KEY `wf_service_calls_caller_outcome_idx` (`caller_namespace`,`outcome`),\n KEY `wf_service_calls_target_outcome_idx` (`target_namespace`,`outcome`),\n KEY `wf_service_calls_endpoint_idx` (`workflow_service_endpoint_id`),\n KEY `wf_service_calls_service_idx` (`workflow_service_id`),\n KEY `wf_service_calls_operation_idx` (`workflow_service_operation_id`),\n KEY `wf_service_calls_namespace_idx` (`namespace`),\n KEY `wf_service_calls_caller_namespace_idx` (`caller_namespace`),\n KEY `wf_service_calls_caller_instance_idx` (`caller_workflow_instance_id`),\n KEY `wf_service_calls_caller_run_idx` (`caller_workflow_run_id`),\n KEY `wf_service_calls_target_namespace_idx` (`target_namespace`),\n KEY `wf_service_calls_linked_instance_idx` (`linked_workflow_instance_id`),\n KEY `wf_service_calls_linked_run_idx` (`linked_workflow_run_id`),\n KEY `wf_service_calls_linked_update_idx` (`linked_workflow_update_id`),\n KEY `wf_service_calls_status_idx` (`status`),\n KEY `wf_service_calls_outcome_idx` (`outcome`),\n KEY `wf_service_calls_mode_idx` (`operation_mode`),\n KEY `wf_service_calls_binding_kind_idx` (`resolved_binding_kind`),\n KEY `wf_service_calls_idempotency_idx` (`idempotency_key`),\n KEY `wf_service_calls_outcome_category_idx` (`outcome_category`),\n KEY `wf_service_calls_policy_idx` (`policy_name`),\n KEY `wf_service_calls_principal_subject_idx` (`caller_principal_subject`),\n KEY `wf_service_calls_accepted_at_idx` (`accepted_at`),\n KEY `wf_service_calls_deadline_idx` (`status`,`call_deadline_at`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_worker_sessions", + "CREATE TABLE `workflow_worker_sessions` (\n `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n `namespace` varchar(128) NOT NULL,\n `session_id` varchar(255) NOT NULL,\n `connection` varchar(255) DEFAULT NULL,\n `queue` varchar(255) DEFAULT NULL,\n `requirements` longtext COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`requirements`)),\n `status` varchar(32) NOT NULL DEFAULT 'active',\n `lease_owner` varchar(255) DEFAULT NULL,\n `lease_owner_incarnation` char(64) DEFAULT NULL,\n `lease_expires_at` timestamp NULL DEFAULT NULL,\n `ttl_expires_at` timestamp NULL DEFAULT NULL,\n `closed_at` timestamp NULL DEFAULT NULL,\n `failure_reason` varchar(128) DEFAULT NULL,\n `lease_seconds` int(10) unsigned NOT NULL DEFAULT 120,\n `ttl_seconds` int(10) unsigned NOT NULL DEFAULT 1800,\n `max_concurrent_activities` int(10) unsigned NOT NULL DEFAULT 1,\n `create_if_missing` tinyint(1) NOT NULL DEFAULT 1,\n `allow_reacquire_after_failure` tinyint(1) NOT NULL DEFAULT 1,\n `last_heartbeat_at` timestamp NULL DEFAULT NULL,\n `created_at` timestamp NULL DEFAULT NULL,\n `updated_at` timestamp NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `workflow_worker_sessions_namespace_session_id_unique` (`namespace`,`session_id`),\n KEY `workflow_worker_sessions_namespace_status_index` (`namespace`,`status`),\n KEY `workflow_worker_sessions_namespace_lease_owner_status_index` (`namespace`,`lease_owner`,`status`),\n KEY `workflow_worker_sessions_namespace_queue_status_index` (`namespace`,`queue`,`status`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ] + ] +} diff --git a/rust/upgrade/php-2.11.3/mysql.json b/rust/upgrade/php-2.11.3/mysql.json new file mode 100644 index 000000000..1a69d1df3 --- /dev/null +++ b/rust/upgrade/php-2.11.3/mysql.json @@ -0,0 +1,42 @@ +{ + "remove": [ + [ + "workflow_durable_streams", + "CREATE TABLE `workflow_durable_streams` (\n `id` bigint unsigned NOT NULL AUTO_INCREMENT,\n `namespace` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL,\n `workflow_instance_id` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,\n `workflow_run_id` varchar(26) COLLATE utf8mb4_unicode_ci NOT NULL,\n `stream_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,\n `status` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'open',\n `last_offset` bigint NOT NULL DEFAULT '-1',\n `total_items` bigint unsigned NOT NULL DEFAULT '0',\n `retention_seconds` int unsigned DEFAULT NULL,\n `opened_at` timestamp(6) NULL DEFAULT NULL,\n `last_appended_at` timestamp(6) NULL DEFAULT NULL,\n `closed_at` timestamp(6) NULL DEFAULT NULL,\n `error_reason` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `pending_items` bigint unsigned NOT NULL DEFAULT '0',\n `metadata` json DEFAULT NULL,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `wf_durable_streams_run_name_unique` (`workflow_run_id`,`stream_name`),\n KEY `wf_durable_streams_ns_instance_name_idx` (`namespace`,`workflow_instance_id`,`stream_name`),\n KEY `wf_durable_streams_ns_status_idx` (`namespace`,`status`),\n KEY `workflow_durable_streams_namespace_index` (`namespace`),\n KEY `workflow_durable_streams_workflow_instance_id_index` (`workflow_instance_id`),\n KEY `workflow_durable_streams_workflow_run_id_index` (`workflow_run_id`),\n KEY `workflow_durable_streams_status_index` (`status`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_failures", + "CREATE TABLE `workflow_failures` (\n `id` varchar(26) COLLATE utf8mb4_unicode_ci NOT NULL,\n `workflow_run_id` varchar(26) COLLATE utf8mb4_unicode_ci NOT NULL,\n `source_kind` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n `source_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n `propagation_kind` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n `failure_category` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `non_retryable` tinyint(1) NOT NULL DEFAULT '0',\n `handled` tinyint(1) NOT NULL DEFAULT '0',\n `exception_class` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n `message` text COLLATE utf8mb4_unicode_ci NOT NULL,\n `file` text COLLATE utf8mb4_unicode_ci NOT NULL,\n `line` int unsigned DEFAULT NULL,\n `trace_preview` longtext COLLATE utf8mb4_unicode_ci,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n KEY `workflow_failures_source_kind_source_id_index` (`source_kind`,`source_id`),\n KEY `workflow_failures_workflow_run_id_index` (`workflow_run_id`),\n KEY `workflow_failures_failure_category_index` (`failure_category`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_worker_sessions", + "CREATE TABLE `workflow_worker_sessions` (\n `id` bigint unsigned NOT NULL AUTO_INCREMENT,\n `namespace` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL,\n `session_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n `connection` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `queue` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `requirements` json DEFAULT NULL,\n `status` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'active',\n `lease_owner` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `lease_expires_at` timestamp NULL DEFAULT NULL,\n `ttl_expires_at` timestamp NULL DEFAULT NULL,\n `closed_at` timestamp NULL DEFAULT NULL,\n `failure_reason` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `lease_seconds` int unsigned NOT NULL DEFAULT '120',\n `ttl_seconds` int unsigned NOT NULL DEFAULT '1800',\n `max_concurrent_activities` int unsigned NOT NULL DEFAULT '1',\n `create_if_missing` tinyint(1) NOT NULL DEFAULT '1',\n `allow_reacquire_after_failure` tinyint(1) NOT NULL DEFAULT '1',\n `last_heartbeat_at` timestamp NULL DEFAULT NULL,\n `created_at` timestamp NULL DEFAULT NULL,\n `updated_at` timestamp NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `workflow_worker_sessions_namespace_session_id_unique` (`namespace`,`session_id`),\n KEY `workflow_worker_sessions_namespace_status_index` (`namespace`,`status`),\n KEY `workflow_worker_sessions_namespace_lease_owner_status_index` (`namespace`,`lease_owner`,`status`),\n KEY `workflow_worker_sessions_namespace_queue_status_index` (`namespace`,`queue`,`status`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_service_calls", + "CREATE TABLE `workflow_service_calls` (\n `id` varchar(26) COLLATE utf8mb4_unicode_ci NOT NULL,\n `workflow_service_endpoint_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `workflow_service_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `workflow_service_operation_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `namespace` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `endpoint_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,\n `service_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,\n `operation_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,\n `caller_namespace` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `caller_workflow_instance_id` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `caller_workflow_run_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `target_namespace` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `linked_workflow_instance_id` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `linked_workflow_run_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `linked_workflow_update_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `status` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,\n `outcome` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `operation_mode` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,\n `resolved_binding_kind` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `resolved_target_reference` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `payload_codec` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `input_payload_reference` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `output_payload_reference` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `failure_payload_reference` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `failure_message` text COLLATE utf8mb4_unicode_ci,\n `idempotency_key` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `deadline_policy` json DEFAULT NULL,\n `idempotency_policy` json DEFAULT NULL,\n `cancellation_policy` json DEFAULT NULL,\n `retry_policy` json DEFAULT NULL,\n `boundary_policy` json DEFAULT NULL,\n `metadata` json DEFAULT NULL,\n `outcome_category` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `outcome_reason` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `outcome_message` text COLLATE utf8mb4_unicode_ci,\n `outcome_metadata` json DEFAULT NULL,\n `policy_name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `retry_after_seconds` int unsigned DEFAULT NULL,\n `caller_principal_subject` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `caller_principal_method` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `caller_principal_roles` json DEFAULT NULL,\n `caller_principal_tenant` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `caller_principal_claims` json DEFAULT NULL,\n `accepted_at` timestamp(6) NULL DEFAULT NULL,\n `started_at` timestamp(6) NULL DEFAULT NULL,\n `completed_at` timestamp(6) NULL DEFAULT NULL,\n `failed_at` timestamp(6) NULL DEFAULT NULL,\n `cancelled_at` timestamp(6) NULL DEFAULT NULL,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n `call_deadline_at` datetime(6) DEFAULT NULL,\n PRIMARY KEY (`id`),\n KEY `wf_service_calls_namespace_status_idx` (`namespace`,`status`),\n KEY `wf_service_calls_caller_status_idx` (`caller_namespace`,`status`),\n KEY `wf_service_calls_target_status_idx` (`target_namespace`,`status`),\n KEY `wf_service_calls_namespace_outcome_idx` (`namespace`,`outcome`),\n KEY `wf_service_calls_caller_outcome_idx` (`caller_namespace`,`outcome`),\n KEY `wf_service_calls_target_outcome_idx` (`target_namespace`,`outcome`),\n KEY `wf_service_calls_endpoint_idx` (`workflow_service_endpoint_id`),\n KEY `wf_service_calls_service_idx` (`workflow_service_id`),\n KEY `wf_service_calls_operation_idx` (`workflow_service_operation_id`),\n KEY `wf_service_calls_namespace_idx` (`namespace`),\n KEY `wf_service_calls_caller_namespace_idx` (`caller_namespace`),\n KEY `wf_service_calls_caller_instance_idx` (`caller_workflow_instance_id`),\n KEY `wf_service_calls_caller_run_idx` (`caller_workflow_run_id`),\n KEY `wf_service_calls_target_namespace_idx` (`target_namespace`),\n KEY `wf_service_calls_linked_instance_idx` (`linked_workflow_instance_id`),\n KEY `wf_service_calls_linked_run_idx` (`linked_workflow_run_id`),\n KEY `wf_service_calls_linked_update_idx` (`linked_workflow_update_id`),\n KEY `wf_service_calls_status_idx` (`status`),\n KEY `wf_service_calls_outcome_idx` (`outcome`),\n KEY `wf_service_calls_mode_idx` (`operation_mode`),\n KEY `wf_service_calls_binding_kind_idx` (`resolved_binding_kind`),\n KEY `wf_service_calls_idempotency_idx` (`idempotency_key`),\n KEY `wf_service_calls_outcome_category_idx` (`outcome_category`),\n KEY `wf_service_calls_policy_idx` (`policy_name`),\n KEY `wf_service_calls_principal_subject_idx` (`caller_principal_subject`),\n KEY `wf_service_calls_accepted_at_idx` (`accepted_at`),\n KEY `wf_service_calls_deadline_idx` (`status`,`call_deadline_at`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ] + ], + "add": [ + [ + "workflow_durable_streams", + "CREATE TABLE `workflow_durable_streams` (\n `id` bigint unsigned NOT NULL AUTO_INCREMENT,\n `namespace` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL,\n `workflow_instance_id` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,\n `workflow_run_id` varchar(26) COLLATE utf8mb4_unicode_ci NOT NULL,\n `stream_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,\n `status` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'open',\n `last_offset` bigint NOT NULL DEFAULT '-1',\n `total_items` bigint unsigned NOT NULL DEFAULT '0',\n `retention_seconds` int unsigned DEFAULT NULL,\n `opened_at` timestamp(6) NULL DEFAULT NULL,\n `last_appended_at` timestamp(6) NULL DEFAULT NULL,\n `closed_at` timestamp(6) NULL DEFAULT NULL,\n `error_reason` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `pending_items` bigint unsigned NOT NULL DEFAULT '0',\n `metadata` json DEFAULT NULL,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n `reclaim_at` timestamp(6) NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `wf_durable_streams_run_name_unique` (`workflow_run_id`,`stream_name`),\n KEY `wf_durable_streams_ns_instance_name_idx` (`namespace`,`workflow_instance_id`,`stream_name`),\n KEY `wf_durable_streams_ns_status_idx` (`namespace`,`status`),\n KEY `workflow_durable_streams_namespace_index` (`namespace`),\n KEY `workflow_durable_streams_workflow_instance_id_index` (`workflow_instance_id`),\n KEY `workflow_durable_streams_workflow_run_id_index` (`workflow_run_id`),\n KEY `workflow_durable_streams_status_index` (`status`),\n KEY `wf_durable_streams_ns_reclaim_idx` (`namespace`,`reclaim_at`,`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_failures", + "CREATE TABLE `workflow_failures` (\n `id` varchar(26) COLLATE utf8mb4_unicode_ci NOT NULL,\n `workflow_run_id` varchar(26) COLLATE utf8mb4_unicode_ci NOT NULL,\n `source_kind` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n `source_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n `propagation_kind` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n `failure_category` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `non_retryable` tinyint(1) NOT NULL DEFAULT '0',\n `handled` tinyint(1) NOT NULL DEFAULT '0',\n `exception_class` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n `message` text COLLATE utf8mb4_unicode_ci NOT NULL,\n `file` text COLLATE utf8mb4_unicode_ci NOT NULL,\n `line` int unsigned DEFAULT NULL,\n `trace_preview` longtext COLLATE utf8mb4_unicode_ci,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n `message_full` longtext COLLATE utf8mb4_unicode_ci,\n PRIMARY KEY (`id`),\n KEY `workflow_failures_source_kind_source_id_index` (`source_kind`,`source_id`),\n KEY `workflow_failures_workflow_run_id_index` (`workflow_run_id`),\n KEY `workflow_failures_failure_category_index` (`failure_category`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_service_call_dispatches", + "CREATE TABLE `workflow_service_call_dispatches` (\n `service_call_id` varchar(26) COLLATE utf8mb4_unicode_ci NOT NULL,\n `state` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,\n `dispatch_options` json NOT NULL,\n `attempt` int unsigned NOT NULL DEFAULT '1',\n `max_attempts` int unsigned NOT NULL DEFAULT '1',\n `available_at` timestamp(6) NOT NULL,\n `claim_generation` bigint unsigned NOT NULL DEFAULT '0',\n `claim_token` char(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `claimed_at` timestamp(6) NULL DEFAULT NULL,\n `claim_expires_at` timestamp(6) NULL DEFAULT NULL,\n `authorized_catalog_revision` char(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `authorized_policy_revision` char(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `dispatch_attempted_at` timestamp(6) NULL DEFAULT NULL,\n `repair_available_at` timestamp(6) NULL DEFAULT NULL,\n `completed_at` timestamp(6) NULL DEFAULT NULL,\n `last_error` text COLLATE utf8mb4_unicode_ci,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n PRIMARY KEY (`service_call_id`),\n KEY `wf_service_dispatch_due_idx` (`state`,`available_at`),\n KEY `wf_service_dispatch_claim_idx` (`state`,`claim_expires_at`),\n KEY `wf_service_dispatch_repair_idx` (`state`,`repair_available_at`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_service_calls", + "CREATE TABLE `workflow_service_calls` (\n `id` varchar(26) COLLATE utf8mb4_unicode_ci NOT NULL,\n `workflow_service_endpoint_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `workflow_service_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `workflow_service_operation_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `namespace` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `endpoint_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,\n `service_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,\n `operation_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,\n `caller_namespace` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `caller_workflow_instance_id` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `caller_workflow_run_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `target_namespace` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `linked_workflow_instance_id` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `linked_workflow_run_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `linked_workflow_update_id` varchar(26) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `status` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,\n `outcome` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `operation_mode` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,\n `resolved_binding_kind` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `resolved_target_reference` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `payload_codec` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `input_payload_reference` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `output_payload_reference` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `failure_payload_reference` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `failure_message` text COLLATE utf8mb4_unicode_ci,\n `idempotency_key` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `deadline_policy` json DEFAULT NULL,\n `idempotency_policy` json DEFAULT NULL,\n `cancellation_policy` json DEFAULT NULL,\n `retry_policy` json DEFAULT NULL,\n `boundary_policy` json DEFAULT NULL,\n `metadata` json DEFAULT NULL,\n `outcome_category` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `outcome_reason` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `outcome_message` text COLLATE utf8mb4_unicode_ci,\n `outcome_metadata` json DEFAULT NULL,\n `policy_name` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `retry_after_seconds` int unsigned DEFAULT NULL,\n `caller_principal_subject` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `caller_principal_method` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `caller_principal_roles` json DEFAULT NULL,\n `caller_principal_tenant` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `caller_principal_claims` json DEFAULT NULL,\n `accepted_at` timestamp(6) NULL DEFAULT NULL,\n `started_at` timestamp(6) NULL DEFAULT NULL,\n `completed_at` timestamp(6) NULL DEFAULT NULL,\n `failed_at` timestamp(6) NULL DEFAULT NULL,\n `cancelled_at` timestamp(6) NULL DEFAULT NULL,\n `created_at` timestamp(6) NULL DEFAULT NULL,\n `updated_at` timestamp(6) NULL DEFAULT NULL,\n `call_deadline_at` datetime(6) DEFAULT NULL,\n `idempotency_scope_hash` char(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `wf_service_calls_idem_scope_unique` (`idempotency_scope_hash`),\n KEY `wf_service_calls_namespace_status_idx` (`namespace`,`status`),\n KEY `wf_service_calls_caller_status_idx` (`caller_namespace`,`status`),\n KEY `wf_service_calls_target_status_idx` (`target_namespace`,`status`),\n KEY `wf_service_calls_namespace_outcome_idx` (`namespace`,`outcome`),\n KEY `wf_service_calls_caller_outcome_idx` (`caller_namespace`,`outcome`),\n KEY `wf_service_calls_target_outcome_idx` (`target_namespace`,`outcome`),\n KEY `wf_service_calls_endpoint_idx` (`workflow_service_endpoint_id`),\n KEY `wf_service_calls_service_idx` (`workflow_service_id`),\n KEY `wf_service_calls_operation_idx` (`workflow_service_operation_id`),\n KEY `wf_service_calls_namespace_idx` (`namespace`),\n KEY `wf_service_calls_caller_namespace_idx` (`caller_namespace`),\n KEY `wf_service_calls_caller_instance_idx` (`caller_workflow_instance_id`),\n KEY `wf_service_calls_caller_run_idx` (`caller_workflow_run_id`),\n KEY `wf_service_calls_target_namespace_idx` (`target_namespace`),\n KEY `wf_service_calls_linked_instance_idx` (`linked_workflow_instance_id`),\n KEY `wf_service_calls_linked_run_idx` (`linked_workflow_run_id`),\n KEY `wf_service_calls_linked_update_idx` (`linked_workflow_update_id`),\n KEY `wf_service_calls_status_idx` (`status`),\n KEY `wf_service_calls_outcome_idx` (`outcome`),\n KEY `wf_service_calls_mode_idx` (`operation_mode`),\n KEY `wf_service_calls_binding_kind_idx` (`resolved_binding_kind`),\n KEY `wf_service_calls_idempotency_idx` (`idempotency_key`),\n KEY `wf_service_calls_outcome_category_idx` (`outcome_category`),\n KEY `wf_service_calls_policy_idx` (`policy_name`),\n KEY `wf_service_calls_principal_subject_idx` (`caller_principal_subject`),\n KEY `wf_service_calls_accepted_at_idx` (`accepted_at`),\n KEY `wf_service_calls_deadline_idx` (`status`,`call_deadline_at`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ], + [ + "workflow_worker_sessions", + "CREATE TABLE `workflow_worker_sessions` (\n `id` bigint unsigned NOT NULL AUTO_INCREMENT,\n `namespace` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL,\n `session_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,\n `connection` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `queue` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `requirements` json DEFAULT NULL,\n `status` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'active',\n `lease_owner` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `lease_owner_incarnation` char(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `lease_expires_at` timestamp NULL DEFAULT NULL,\n `ttl_expires_at` timestamp NULL DEFAULT NULL,\n `closed_at` timestamp NULL DEFAULT NULL,\n `failure_reason` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL,\n `lease_seconds` int unsigned NOT NULL DEFAULT '120',\n `ttl_seconds` int unsigned NOT NULL DEFAULT '1800',\n `max_concurrent_activities` int unsigned NOT NULL DEFAULT '1',\n `create_if_missing` tinyint(1) NOT NULL DEFAULT '1',\n `allow_reacquire_after_failure` tinyint(1) NOT NULL DEFAULT '1',\n `last_heartbeat_at` timestamp NULL DEFAULT NULL,\n `created_at` timestamp NULL DEFAULT NULL,\n `updated_at` timestamp NULL DEFAULT NULL,\n PRIMARY KEY (`id`),\n UNIQUE KEY `workflow_worker_sessions_namespace_session_id_unique` (`namespace`,`session_id`),\n KEY `workflow_worker_sessions_namespace_status_index` (`namespace`,`status`),\n KEY `workflow_worker_sessions_namespace_lease_owner_status_index` (`namespace`,`lease_owner`,`status`),\n KEY `workflow_worker_sessions_namespace_queue_status_index` (`namespace`,`queue`,`status`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" + ] + ] +} diff --git a/rust/upgrade/php-2.11.3/postgres.json b/rust/upgrade/php-2.11.3/postgres.json new file mode 100644 index 000000000..dae058de7 --- /dev/null +++ b/rust/upgrade/php-2.11.3/postgres.json @@ -0,0 +1,382 @@ +{ + "remove": [], + "add": [ + [ + "column", + "workflow_durable_streams.18", + { + "collation": null, + "default": null, + "generated": "", + "identity": "", + "name": "reclaim_at", + "not_null": false, + "type": "timestamp(6) without time zone" + } + ], + [ + "column", + "workflow_failures.16", + { + "collation": "default", + "default": null, + "generated": "", + "identity": "", + "name": "message_full", + "not_null": false, + "type": "text" + } + ], + [ + "column", + "workflow_service_call_dispatches.1", + { + "collation": "default", + "default": null, + "generated": "", + "identity": "", + "name": "service_call_id", + "not_null": true, + "type": "character varying(26)" + } + ], + [ + "column", + "workflow_service_call_dispatches.10", + { + "collation": null, + "default": null, + "generated": "", + "identity": "", + "name": "claim_expires_at", + "not_null": false, + "type": "timestamp(6) without time zone" + } + ], + [ + "column", + "workflow_service_call_dispatches.11", + { + "collation": "default", + "default": null, + "generated": "", + "identity": "", + "name": "authorized_catalog_revision", + "not_null": false, + "type": "character(64)" + } + ], + [ + "column", + "workflow_service_call_dispatches.12", + { + "collation": "default", + "default": null, + "generated": "", + "identity": "", + "name": "authorized_policy_revision", + "not_null": false, + "type": "character(64)" + } + ], + [ + "column", + "workflow_service_call_dispatches.13", + { + "collation": null, + "default": null, + "generated": "", + "identity": "", + "name": "dispatch_attempted_at", + "not_null": false, + "type": "timestamp(6) without time zone" + } + ], + [ + "column", + "workflow_service_call_dispatches.14", + { + "collation": null, + "default": null, + "generated": "", + "identity": "", + "name": "repair_available_at", + "not_null": false, + "type": "timestamp(6) without time zone" + } + ], + [ + "column", + "workflow_service_call_dispatches.15", + { + "collation": null, + "default": null, + "generated": "", + "identity": "", + "name": "completed_at", + "not_null": false, + "type": "timestamp(6) without time zone" + } + ], + [ + "column", + "workflow_service_call_dispatches.16", + { + "collation": "default", + "default": null, + "generated": "", + "identity": "", + "name": "last_error", + "not_null": false, + "type": "text" + } + ], + [ + "column", + "workflow_service_call_dispatches.17", + { + "collation": null, + "default": null, + "generated": "", + "identity": "", + "name": "created_at", + "not_null": false, + "type": "timestamp(6) without time zone" + } + ], + [ + "column", + "workflow_service_call_dispatches.18", + { + "collation": null, + "default": null, + "generated": "", + "identity": "", + "name": "updated_at", + "not_null": false, + "type": "timestamp(6) without time zone" + } + ], + [ + "column", + "workflow_service_call_dispatches.2", + { + "collation": "default", + "default": null, + "generated": "", + "identity": "", + "name": "state", + "not_null": true, + "type": "character varying(32)" + } + ], + [ + "column", + "workflow_service_call_dispatches.3", + { + "collation": null, + "default": null, + "generated": "", + "identity": "", + "name": "dispatch_options", + "not_null": true, + "type": "json" + } + ], + [ + "column", + "workflow_service_call_dispatches.4", + { + "collation": null, + "default": "1", + "generated": "", + "identity": "", + "name": "attempt", + "not_null": true, + "type": "integer" + } + ], + [ + "column", + "workflow_service_call_dispatches.5", + { + "collation": null, + "default": "1", + "generated": "", + "identity": "", + "name": "max_attempts", + "not_null": true, + "type": "integer" + } + ], + [ + "column", + "workflow_service_call_dispatches.6", + { + "collation": null, + "default": null, + "generated": "", + "identity": "", + "name": "available_at", + "not_null": true, + "type": "timestamp(6) without time zone" + } + ], + [ + "column", + "workflow_service_call_dispatches.7", + { + "collation": null, + "default": "'0'::bigint", + "generated": "", + "identity": "", + "name": "claim_generation", + "not_null": true, + "type": "bigint" + } + ], + [ + "column", + "workflow_service_call_dispatches.8", + { + "collation": "default", + "default": null, + "generated": "", + "identity": "", + "name": "claim_token", + "not_null": false, + "type": "character(64)" + } + ], + [ + "column", + "workflow_service_call_dispatches.9", + { + "collation": null, + "default": null, + "generated": "", + "identity": "", + "name": "claimed_at", + "not_null": false, + "type": "timestamp(6) without time zone" + } + ], + [ + "column", + "workflow_service_calls.52", + { + "collation": "default", + "default": null, + "generated": "", + "identity": "", + "name": "idempotency_scope_hash", + "not_null": false, + "type": "character(64)" + } + ], + [ + "column", + "workflow_worker_sessions.21", + { + "collation": "default", + "default": null, + "generated": "", + "identity": "", + "name": "lease_owner_incarnation", + "not_null": false, + "type": "character(64)" + } + ], + [ + "constraint", + "workflow_service_call_dispatches.workflow_service_call_dispatches_pkey", + { + "deferrable": false, + "deferred": false, + "definition": "PRIMARY KEY (service_call_id)", + "validated": true + } + ], + [ + "index", + "wf_durable_streams_ns_reclaim_idx", + { + "definition": "CREATE INDEX wf_durable_streams_ns_reclaim_idx ON public.workflow_durable_streams USING btree (namespace, reclaim_at, id)", + "primary": false, + "ready": true, + "unique": false, + "valid": true + } + ], + [ + "index", + "wf_service_calls_idem_scope_unique", + { + "definition": "CREATE UNIQUE INDEX wf_service_calls_idem_scope_unique ON public.workflow_service_calls USING btree (idempotency_scope_hash)", + "primary": false, + "ready": true, + "unique": true, + "valid": true + } + ], + [ + "index", + "wf_service_dispatch_claim_idx", + { + "definition": "CREATE INDEX wf_service_dispatch_claim_idx ON public.workflow_service_call_dispatches USING btree (state, claim_expires_at)", + "primary": false, + "ready": true, + "unique": false, + "valid": true + } + ], + [ + "index", + "wf_service_dispatch_due_idx", + { + "definition": "CREATE INDEX wf_service_dispatch_due_idx ON public.workflow_service_call_dispatches USING btree (state, available_at)", + "primary": false, + "ready": true, + "unique": false, + "valid": true + } + ], + [ + "index", + "wf_service_dispatch_repair_idx", + { + "definition": "CREATE INDEX wf_service_dispatch_repair_idx ON public.workflow_service_call_dispatches USING btree (state, repair_available_at)", + "primary": false, + "ready": true, + "unique": false, + "valid": true + } + ], + [ + "index", + "workflow_service_call_dispatches_pkey", + { + "definition": "CREATE UNIQUE INDEX workflow_service_call_dispatches_pkey ON public.workflow_service_call_dispatches USING btree (service_call_id)", + "primary": true, + "ready": true, + "unique": true, + "valid": true + } + ], + [ + "relation", + "workflow_service_call_dispatches", + { + "access_method": "heap", + "force_row_security": false, + "kind": "r", + "options": null, + "partition": false, + "partition_key": null, + "persistence": "p", + "row_security": false, + "view": null + } + ] + ] +} diff --git a/rust/upgrade/php-2.11.3/sqlite.json b/rust/upgrade/php-2.11.3/sqlite.json new file mode 100644 index 000000000..aeb8845b3 --- /dev/null +++ b/rust/upgrade/php-2.11.3/sqlite.json @@ -0,0 +1,90 @@ +{ + "remove": [ + [ + "table", + "workflow_durable_streams", + "workflow_durable_streams", + "CREATE TABLE \"workflow_durable_streams\" (\"id\" integer primary key autoincrement not null, \"namespace\" varchar not null, \"workflow_instance_id\" varchar not null, \"workflow_run_id\" varchar not null, \"stream_name\" varchar not null, \"status\" varchar not null default 'open', \"last_offset\" integer not null default '-1', \"total_items\" integer not null default '0', \"retention_seconds\" integer, \"opened_at\" datetime, \"last_appended_at\" datetime, \"closed_at\" datetime, \"error_reason\" varchar, \"pending_items\" integer not null default '0', \"metadata\" text, \"created_at\" datetime, \"updated_at\" datetime)" + ], + [ + "table", + "workflow_failures", + "workflow_failures", + "CREATE TABLE \"workflow_failures\" (\"id\" varchar not null, \"workflow_run_id\" varchar not null, \"source_kind\" varchar not null, \"source_id\" varchar not null, \"propagation_kind\" varchar not null, \"failure_category\" varchar, \"non_retryable\" tinyint(1) not null default '0', \"handled\" tinyint(1) not null default '0', \"exception_class\" varchar not null, \"message\" text not null, \"file\" text not null, \"line\" integer, \"trace_preview\" text, \"created_at\" datetime, \"updated_at\" datetime, primary key (\"id\"))" + ], + [ + "table", + "workflow_worker_sessions", + "workflow_worker_sessions", + "CREATE TABLE \"workflow_worker_sessions\" (\"id\" integer primary key autoincrement not null, \"namespace\" varchar not null, \"session_id\" varchar not null, \"connection\" varchar, \"queue\" varchar, \"requirements\" text, \"status\" varchar not null default 'active', \"lease_owner\" varchar, \"lease_expires_at\" datetime, \"ttl_expires_at\" datetime, \"closed_at\" datetime, \"failure_reason\" varchar, \"lease_seconds\" integer not null default '120', \"ttl_seconds\" integer not null default '1800', \"max_concurrent_activities\" integer not null default '1', \"create_if_missing\" tinyint(1) not null default '1', \"allow_reacquire_after_failure\" tinyint(1) not null default '1', \"last_heartbeat_at\" datetime, \"created_at\" datetime, \"updated_at\" datetime)" + ], + [ + "table", + "workflow_service_calls", + "workflow_service_calls", + "CREATE TABLE \"workflow_service_calls\" (\"id\" varchar not null, \"workflow_service_endpoint_id\" varchar, \"workflow_service_id\" varchar, \"workflow_service_operation_id\" varchar, \"namespace\" varchar, \"endpoint_name\" varchar not null, \"service_name\" varchar not null, \"operation_name\" varchar not null, \"caller_namespace\" varchar, \"caller_workflow_instance_id\" varchar, \"caller_workflow_run_id\" varchar, \"target_namespace\" varchar, \"linked_workflow_instance_id\" varchar, \"linked_workflow_run_id\" varchar, \"linked_workflow_update_id\" varchar, \"status\" varchar not null, \"outcome\" varchar, \"operation_mode\" varchar not null, \"resolved_binding_kind\" varchar, \"resolved_target_reference\" varchar, \"payload_codec\" varchar, \"input_payload_reference\" varchar, \"output_payload_reference\" varchar, \"failure_payload_reference\" varchar, \"failure_message\" text, \"idempotency_key\" varchar, \"deadline_policy\" text, \"idempotency_policy\" text, \"cancellation_policy\" text, \"retry_policy\" text, \"boundary_policy\" text, \"metadata\" text, \"outcome_category\" varchar, \"outcome_reason\" varchar, \"outcome_message\" text, \"outcome_metadata\" text, \"policy_name\" varchar, \"retry_after_seconds\" integer, \"caller_principal_subject\" varchar, \"caller_principal_method\" varchar, \"caller_principal_roles\" text, \"caller_principal_tenant\" varchar, \"caller_principal_claims\" text, \"accepted_at\" datetime, \"started_at\" datetime, \"completed_at\" datetime, \"failed_at\" datetime, \"cancelled_at\" datetime, \"created_at\" datetime, \"updated_at\" datetime, \"call_deadline_at\" datetime, primary key (\"id\"))" + ] + ], + "add": [ + [ + "index", + "wf_durable_streams_ns_reclaim_idx", + "workflow_durable_streams", + "CREATE INDEX \"wf_durable_streams_ns_reclaim_idx\" on \"workflow_durable_streams\" (\"namespace\", \"reclaim_at\", \"id\")" + ], + [ + "index", + "wf_service_calls_idem_scope_unique", + "workflow_service_calls", + "CREATE UNIQUE INDEX \"wf_service_calls_idem_scope_unique\" on \"workflow_service_calls\" (\"idempotency_scope_hash\")" + ], + [ + "index", + "wf_service_dispatch_claim_idx", + "workflow_service_call_dispatches", + "CREATE INDEX \"wf_service_dispatch_claim_idx\" on \"workflow_service_call_dispatches\" (\"state\", \"claim_expires_at\")" + ], + [ + "index", + "wf_service_dispatch_due_idx", + "workflow_service_call_dispatches", + "CREATE INDEX \"wf_service_dispatch_due_idx\" on \"workflow_service_call_dispatches\" (\"state\", \"available_at\")" + ], + [ + "index", + "wf_service_dispatch_repair_idx", + "workflow_service_call_dispatches", + "CREATE INDEX \"wf_service_dispatch_repair_idx\" on \"workflow_service_call_dispatches\" (\"state\", \"repair_available_at\")" + ], + [ + "table", + "workflow_durable_streams", + "workflow_durable_streams", + "CREATE TABLE \"workflow_durable_streams\" (\"id\" integer primary key autoincrement not null, \"namespace\" varchar not null, \"workflow_instance_id\" varchar not null, \"workflow_run_id\" varchar not null, \"stream_name\" varchar not null, \"status\" varchar not null default 'open', \"last_offset\" integer not null default '-1', \"total_items\" integer not null default '0', \"retention_seconds\" integer, \"opened_at\" datetime, \"last_appended_at\" datetime, \"closed_at\" datetime, \"error_reason\" varchar, \"pending_items\" integer not null default '0', \"metadata\" text, \"created_at\" datetime, \"updated_at\" datetime, \"reclaim_at\" datetime)" + ], + [ + "table", + "workflow_failures", + "workflow_failures", + "CREATE TABLE \"workflow_failures\" (\"id\" varchar not null, \"workflow_run_id\" varchar not null, \"source_kind\" varchar not null, \"source_id\" varchar not null, \"propagation_kind\" varchar not null, \"failure_category\" varchar, \"non_retryable\" tinyint(1) not null default '0', \"handled\" tinyint(1) not null default '0', \"exception_class\" varchar not null, \"message\" text not null, \"file\" text not null, \"line\" integer, \"trace_preview\" text, \"created_at\" datetime, \"updated_at\" datetime, \"message_full\" text, primary key (\"id\"))" + ], + [ + "table", + "workflow_service_call_dispatches", + "workflow_service_call_dispatches", + "CREATE TABLE \"workflow_service_call_dispatches\" (\"service_call_id\" varchar not null, \"state\" varchar not null, \"dispatch_options\" text not null, \"attempt\" integer not null default '1', \"max_attempts\" integer not null default '1', \"available_at\" datetime not null, \"claim_generation\" integer not null default '0', \"claim_token\" varchar, \"claimed_at\" datetime, \"claim_expires_at\" datetime, \"authorized_catalog_revision\" varchar, \"authorized_policy_revision\" varchar, \"dispatch_attempted_at\" datetime, \"repair_available_at\" datetime, \"completed_at\" datetime, \"last_error\" text, \"created_at\" datetime, \"updated_at\" datetime, primary key (\"service_call_id\"))" + ], + [ + "table", + "workflow_service_calls", + "workflow_service_calls", + "CREATE TABLE \"workflow_service_calls\" (\"id\" varchar not null, \"workflow_service_endpoint_id\" varchar, \"workflow_service_id\" varchar, \"workflow_service_operation_id\" varchar, \"namespace\" varchar, \"endpoint_name\" varchar not null, \"service_name\" varchar not null, \"operation_name\" varchar not null, \"caller_namespace\" varchar, \"caller_workflow_instance_id\" varchar, \"caller_workflow_run_id\" varchar, \"target_namespace\" varchar, \"linked_workflow_instance_id\" varchar, \"linked_workflow_run_id\" varchar, \"linked_workflow_update_id\" varchar, \"status\" varchar not null, \"outcome\" varchar, \"operation_mode\" varchar not null, \"resolved_binding_kind\" varchar, \"resolved_target_reference\" varchar, \"payload_codec\" varchar, \"input_payload_reference\" varchar, \"output_payload_reference\" varchar, \"failure_payload_reference\" varchar, \"failure_message\" text, \"idempotency_key\" varchar, \"deadline_policy\" text, \"idempotency_policy\" text, \"cancellation_policy\" text, \"retry_policy\" text, \"boundary_policy\" text, \"metadata\" text, \"outcome_category\" varchar, \"outcome_reason\" varchar, \"outcome_message\" text, \"outcome_metadata\" text, \"policy_name\" varchar, \"retry_after_seconds\" integer, \"caller_principal_subject\" varchar, \"caller_principal_method\" varchar, \"caller_principal_roles\" text, \"caller_principal_tenant\" varchar, \"caller_principal_claims\" text, \"accepted_at\" datetime, \"started_at\" datetime, \"completed_at\" datetime, \"failed_at\" datetime, \"cancelled_at\" datetime, \"created_at\" datetime, \"updated_at\" datetime, \"call_deadline_at\" datetime, \"idempotency_scope_hash\" varchar, primary key (\"id\"))" + ], + [ + "table", + "workflow_worker_sessions", + "workflow_worker_sessions", + "CREATE TABLE \"workflow_worker_sessions\" (\"id\" integer primary key autoincrement not null, \"namespace\" varchar not null, \"session_id\" varchar not null, \"connection\" varchar, \"queue\" varchar, \"requirements\" text, \"status\" varchar not null default 'active', \"lease_owner\" varchar, \"lease_expires_at\" datetime, \"ttl_expires_at\" datetime, \"closed_at\" datetime, \"failure_reason\" varchar, \"lease_seconds\" integer not null default '120', \"ttl_seconds\" integer not null default '1800', \"max_concurrent_activities\" integer not null default '1', \"create_if_missing\" tinyint(1) not null default '1', \"allow_reacquire_after_failure\" tinyint(1) not null default '1', \"last_heartbeat_at\" datetime, \"created_at\" datetime, \"updated_at\" datetime, \"lease_owner_incarnation\" varchar)" + ] + ] +} diff --git a/scripts/conformance/server-parity/upgrade-source-profile.sh b/scripts/conformance/server-parity/upgrade-source-profile.sh index 6d869e1f3..39529a1fe 100755 --- a/scripts/conformance/server-parity/upgrade-source-profile.sh +++ b/scripts/conformance/server-parity/upgrade-source-profile.sh @@ -134,7 +134,10 @@ snapshot() { } snapshot > "$evidence/before.json" inspect_source > "$evidence/inspection.json" 2> "$evidence/inspection.stderr" -jq -e --arg layout "$layout" '.status=="inspected" and .source_layout==$layout and (.table_rows|length)==50 and .table_rows.workflow_runs==4 +# PHP 2.11.3 adds one table, the service-call dispatch outbox. +tables=50 +if [[ $layout == php-2.11.3* ]]; then tables=51; fi +jq -e --arg layout "$layout" --argjson tables "$tables" '.status=="inspected" and .source_layout==$layout and (.table_rows|length)==$tables and .table_rows.workflow_runs==4 and .table_rows.activity_executions==1 and .catalog_visibility=="configured_role" and .source_mutated==false and .takeover_allowed==false and .backup_verified==false and .php_quiescence_verified==false and .stored_representation_compatibility_verified==false' "$evidence/inspection.json" >/dev/null @@ -165,8 +168,8 @@ fingerprint() { --entrypoint /target/debug/durable-workflow-server "$rust_image" upgrade-fingerprint } fingerprint "$database" > "$evidence/fingerprint.json" 2> "$evidence/fingerprint.stderr" -jq -e --arg layout "$layout" '.status=="fingerprinted" and .source_layout==$layout and (.fingerprint|test("^sha256:[0-9a-f]{64}$")) - and (.table_rows|length)==50 and .table_rows.workflow_runs==4 and .table_rows.jobs==1 +jq -e --arg layout "$layout" --argjson tables "$tables" '.status=="fingerprinted" and .source_layout==$layout and (.fingerprint|test("^sha256:[0-9a-f]{64}$")) + and (.table_rows|length)==$tables and .table_rows.workflow_runs==4 and .table_rows.jobs==1 and .source_mutated==false and .takeover_allowed==false and .backup_verified==false and .php_quiescence_verified==false and .stored_representation_compatibility_verified==false' "$evidence/fingerprint.json" >/dev/null source_fingerprint=$(jq -r .fingerprint "$evidence/fingerprint.json")