Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
d65bd81
Fingerprint a stopped PHP source database
durable-workflow-ops Oct 20, 2026
ff5356b
Compare MySQL column character sets as their collation
durable-workflow-ops Oct 20, 2026
eac4b5f
Merge remote-tracking branch 'origin/main' into rust-325-upgrade-fing…
durable-workflow-ops Oct 20, 2026
7afa5c1
Let the interrupted-bootstrap child publish its receipt atomically
durable-workflow-ops Oct 20, 2026
26328b8
Recognize the current PHP 2.9.7 layout and name the matched layout
durable-workflow-ops Oct 20, 2026
def78ff
Present stored history payloads the way PHP does
durable-workflow-ops Oct 20, 2026
834f74a
Take over a stopped PHP SQLite database backup-first
durable-workflow-ops Oct 20, 2026
b7b125c
Expect the command principal in projected control history
durable-workflow-ops Oct 20, 2026
094fc75
Share the source connection and row reads across fingerprint backends
durable-workflow-ops Oct 20, 2026
ed7b784
Merge remote-tracking branch 'origin/main' into rust-325-upgrade-fing…
durable-workflow-ops Oct 20, 2026
138c20b
Merge remote-tracking branch 'origin/rust-325-upgrade-fingerprint' in…
durable-workflow-ops Oct 20, 2026
f0217d8
Merge branch 'rust-325-upgrade-layouts' into rust-325-sqlite-takeover
durable-workflow-ops Oct 20, 2026
c14032c
Take over a stopped PHP PostgreSQL database backup-first
durable-workflow-ops Oct 20, 2026
3c1cc66
Take over a stopped PHP MySQL or MariaDB database in recorded steps
durable-workflow-ops Oct 20, 2026
d4a42d4
Let the cohort's containers write the restored SQLite copy
durable-workflow-ops Oct 20, 2026
925f81b
Merge remote-tracking branch 'origin/rust-325-upgrade-fingerprint' in…
durable-workflow-ops Oct 20, 2026
0952578
Merge remote-tracking branch 'origin/rust-325-upgrade-layouts' into r…
durable-workflow-ops Oct 20, 2026
376ca25
Let the cohort's containers write the SQLite takeover copy
durable-workflow-ops Oct 20, 2026
29e798d
Merge remote-tracking branch 'origin/rust-325-sqlite-takeover' into r…
durable-workflow-ops Oct 20, 2026
b868247
Merge remote-tracking branch 'origin/rust-325-postgres-takeover' into…
durable-workflow-ops Oct 20, 2026
48ced5b
Accept MariaDB's backend name in the cohort's takeover check
durable-workflow-ops Oct 20, 2026
3f01df9
Resume the two PHP originals in whichever order native offers them
durable-workflow-ops Oct 20, 2026
5826c17
Merge branch 'rust-325-postgres-takeover' into rust-325-mysql-takeover
durable-workflow-ops Oct 20, 2026
e4ec512
Merge remote-tracking branch 'origin/rust-325-sqlite-takeover' into r…
durable-workflow-ops Oct 20, 2026
132755a
Prepare a PHP SQLite source in WAL mode for the upgrade commands
durable-workflow-ops Oct 20, 2026
d3c3c34
Let SQLite source preparation wait briefly for a closing writer
durable-workflow-ops Oct 20, 2026
fa7695f
Resume a PHP-accepted signal after takeover and refuse unsupported fe…
durable-workflow-ops Oct 20, 2026
6978d60
Redeliver a PHP lease that expires after the takeover
durable-workflow-ops Oct 20, 2026
3dab83e
Merge remote-tracking branch 'origin/main' into rust-325-upgrade-signal
durable-workflow-ops Oct 20, 2026
70f610b
Compare restart history without the public principal; scope the SQLit…
durable-workflow-ops Oct 20, 2026
ffeccc9
Merge remote-tracking branch 'origin/main' into rust-325-upgrade-signal
durable-workflow-ops Oct 20, 2026
7670598
Merge pull request #539 from durable-workflow/rust-325-upgrade-signal
rmcdaniel Oct 20, 2026
3692b6d
Merge pull request #538 from durable-workflow/rust-325-sqlite-wal-source
rmcdaniel Oct 20, 2026
93319a7
Merge pull request #536 from durable-workflow/rust-325-mysql-takeover
rmcdaniel Oct 20, 2026
b2e88bb
Merge pull request #533 from durable-workflow/rust-325-postgres-takeover
rmcdaniel Oct 20, 2026
74538b9
Merge pull request #532 from durable-workflow/rust-325-sqlite-takeover
rmcdaniel Oct 20, 2026
821cda4
Merge pull request #529 from durable-workflow/rust-325-upgrade-layouts
rmcdaniel Oct 20, 2026
dcfc252
Merge remote-tracking branch 'origin/main' into rust-325-upgrade-fing…
durable-workflow-ops Oct 20, 2026
a7bd4d0
Supervise the takeover kill test's child off the async runtime
durable-workflow-ops Oct 20, 2026
fdf4d7b
Merge remote-tracking branch 'origin/main' into rust-325-upgrade-fing…
durable-workflow-ops Oct 20, 2026
d7dd2a8
Merge remote-tracking branch 'origin/main' into rust-325-upgrade-fing…
durable-workflow-ops Oct 20, 2026
bd29547
Merge remote-tracking branch 'origin/main' into rust-325-upgrade-fing…
durable-workflow-ops Oct 20, 2026
9c2f550
Merge remote-tracking branch 'origin/main' into rust-325-upgrade-fing…
durable-workflow-ops Oct 21, 2026
2656864
Merge remote-tracking branch 'origin/main' into rust-325-upgrade-fing…
durable-workflow-ops Oct 21, 2026
b7d631c
Merge remote-tracking branch 'origin/main' into rust-325-upgrade-fing…
durable-workflow-ops Oct 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 50 additions & 3 deletions .github/workflows/server-parity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -676,13 +676,28 @@ jobs:
cmp parity-evidence/catalog-before.json parity-evidence/catalog-after.json
- name: Compare separate configured role-token cohort
run: bash scripts/conformance/server-parity/role-profile.sh mysql
- name: Inspect separate published PHP upgrade source without takeover
- name: Back up, take over and resume a separate published PHP upgrade source
timeout-minutes: 8
run: bash scripts/conformance/server-parity/upgrade-source-profile.sh mysql
- name: Retire upgrade source cohort
if: always()
timeout-minutes: 3
run: bash scripts/conformance/server-parity/upgrade-source-cleanup.sh mysql
- name: Back up, take over and resume a published PHP 2.9.7 upgrade source
timeout-minutes: 8
env:
DW_PARITY_UPGRADE_LABEL: php297
DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.9.7
DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:30c34c5f3256a812fbccbe505a068309b58b0c14d01ffff0465d88604cb36d50
run: bash scripts/conformance/server-parity/upgrade-source-profile.sh mysql
- name: Retire PHP 2.9.7 upgrade source cohort
if: always()
timeout-minutes: 3
env:
DW_PARITY_UPGRADE_LABEL: php297
DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.9.7
DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:30c34c5f3256a812fbccbe505a068309b58b0c14d01ffff0465d88604cb36d50
run: bash scripts/conformance/server-parity/upgrade-source-cleanup.sh mysql
- name: Compare corrected markers, ranges, clocks and activity expiry
if: ${{ !cancelled() && steps.prepare_runtime_comparison.outcome == 'success' }}
# The preceding process-loss case deliberately kills native-a.
Expand Down Expand Up @@ -1160,13 +1175,28 @@ jobs:
env:
POSTGRES_IMAGE: ${{ matrix.image }}
run: bash scripts/conformance/server-parity/role-profile.sh pgsql
- name: Inspect separate published PHP upgrade source without takeover
- name: Back up, take over and resume a separate published PHP upgrade source
timeout-minutes: 8
run: bash scripts/conformance/server-parity/upgrade-source-profile.sh pgsql
- name: Retire upgrade source cohort
if: always()
timeout-minutes: 3
run: bash scripts/conformance/server-parity/upgrade-source-cleanup.sh pgsql
- name: Back up, take over and resume a published PHP 2.9.7 upgrade source
timeout-minutes: 8
env:
DW_PARITY_UPGRADE_LABEL: php297
DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.9.7
DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:30c34c5f3256a812fbccbe505a068309b58b0c14d01ffff0465d88604cb36d50
run: bash scripts/conformance/server-parity/upgrade-source-profile.sh pgsql
- name: Retire PHP 2.9.7 upgrade source cohort
if: always()
timeout-minutes: 3
env:
DW_PARITY_UPGRADE_LABEL: php297
DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.9.7
DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:30c34c5f3256a812fbccbe505a068309b58b0c14d01ffff0465d88604cb36d50
run: bash scripts/conformance/server-parity/upgrade-source-cleanup.sh pgsql
- name: Compare corrected markers, ranges, clocks and activity expiry
if: ${{ !cancelled() && steps.prepare_runtime_comparison.outcome == 'success' }}
env:
Expand Down Expand Up @@ -1635,7 +1665,7 @@ jobs:
env:
RESOURCE_SCOPE: dw-parity-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}
run: bash scripts/conformance/server-parity/role-profile.sh sqlite
- name: Inspect separate published PHP upgrade source without takeover
- name: Back up, take over and resume a separate published PHP upgrade source
timeout-minutes: 8
env:
RESOURCE_SCOPE: dw-parity-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}
Expand All @@ -1646,6 +1676,23 @@ jobs:
env:
RESOURCE_SCOPE: dw-parity-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}
run: bash scripts/conformance/server-parity/upgrade-source-cleanup.sh sqlite
- name: Back up, take over and resume a published PHP 2.9.7 upgrade source
timeout-minutes: 8
env:
RESOURCE_SCOPE: dw-parity-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}
DW_PARITY_UPGRADE_LABEL: php297
DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.9.7
DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:30c34c5f3256a812fbccbe505a068309b58b0c14d01ffff0465d88604cb36d50
run: bash scripts/conformance/server-parity/upgrade-source-profile.sh sqlite
- name: Retire PHP 2.9.7 upgrade source cohort
if: always()
timeout-minutes: 3
env:
RESOURCE_SCOPE: dw-parity-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}
DW_PARITY_UPGRADE_LABEL: php297
DW_PARITY_UPGRADE_EXPECTED_LAYOUT: php-2.9.7
DW_PARITY_UPGRADE_PHP_IMAGE: ghcr.io/durable-workflow/server@sha256:30c34c5f3256a812fbccbe505a068309b58b0c14d01ffff0465d88604cb36d50
run: bash scripts/conformance/server-parity/upgrade-source-cleanup.sh sqlite
- name: Compare corrected markers, ranges, clocks and activity expiry
if: ${{ !cancelled() && steps.prepare_runtime_comparison.outcome == 'success' }}
env:
Expand Down
56 changes: 52 additions & 4 deletions rust/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -412,10 +412,58 @@ version-1/version-2/version-3 development schemas are refused without conversion
needed old development data separately; this unpublished slice supplies no
older-development converter. Use a new isolated file for version 4.

This refusal is not the upgrade mechanism. Backup-first conversion of PHP's
stored representations and interrupted takeover remain required on all three
database families. The PHP ownership fence is merged in source, while the frozen
published PHP image still lacks it. **Never connect PHP to a Rust development database.**
This refusal is not the upgrade mechanism. A stopped PHP database can be taken
over with the development `upgrade-takeover` command, following the
[takeover design](../docs/rust-server-upgrade-takeover.md):

1. Run PHP Server 2.5.14 or later, which refuses a Rust-owned database, and
stop every PHP role.
PHP Server keeps SQLite in WAL mode, which the upgrade commands refuse. With
PHP stopped, run `upgrade-prepare-sqlite` on the source file (writable).
It has SQLite checkpoint every committed WAL frame into the file and switch
it to the rollback journal, refusing with `upgrade_source_in_use` while any
other process has the file open. The rows are unchanged; the backup below
is taken afterwards. A journal left by a crashed writer is rolled back the
same way.
2. Run `upgrade-fingerprint` on the source: a SQLite file mounted read-only,
or the database through a read-only account. Back the database up (a file
copy, `pg_dump` or `mysqldump --single-transaction`), restore the backup
separately, and fingerprint that too. The digests must be equal.
3. Run `upgrade-takeover` with `DW_UPGRADE_BACKUP_FINGERPRINT` set to that
digest and the original queue settings (`DW_RUST_UPGRADE_QUEUE_*`, as for
`upgrade-queue-inspect`). PostgreSQL uses the owner's `DB_*` settings.
MySQL and MariaDB use an account that owns the database and has the
`PROCESS` privilege, so the takeover can see every other session.

On SQLite and PostgreSQL, inspection, the fingerprint check, the conversion
of the recorded PHP release layout, the native relations, the migration ledger
and native's own startup verification all happen in one transaction. SQLite
holds the file
exclusively; PostgreSQL takes native's bootstrap lock and locks every table
before reading, and refuses with `upgrade_source_in_use` if a session still
holds one after five seconds. A refusal leaves the PHP database unchanged. An
interrupted takeover commits nothing: rerun it with the same digest. On
SQLite, the rerun lets SQLite roll back the journal the interrupted run left,
and the fingerprint check then proves the file is the backed-up one.

MySQL and MariaDB commit DDL implicitly, so their takeover runs in steps under
native's bootstrap lock. It refuses with `upgrade_source_in_use` while any
other session uses the database. Once the source is verified, it first writes
native's marker with a `rust-takeover:<layout>` row: PHP refuses the database
from then on, and native refuses it with `native_takeover_incomplete` until
the takeover finishes. It then checks the sessions and the fingerprint again,
and runs each conversion step, recording each in that row. An interrupted
takeover is rerun with the same digest and continues after the last recorded
step; a different digest is refused.

The command refuses:

- a digest that differs, so any write after the backup stops it;
- unrecognized queued jobs, external payloads and service calls;
- a layout other than the frozen one, PHP 2.8.0 or PHP 2.9.7 (with either
Laravel MariaDB driver), or a database native already owns.

Nested payload columns and multi-node qualification remain open. **Never connect PHP to a Rust development database.**
Named namespace isolation and configured worker/operator/admin roles have
representative shared qualification. Principal/custom-provider credentials and
the full authorization contract remain open. Broader timer/signal semantics, failure/retry policy, cancellation and the rest of the API also
Expand Down
102 changes: 102 additions & 0 deletions rust/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,113 @@ async fn main() -> Result<(), Box<dyn Error>> {
| "upgrade-inspect"
| "upgrade-queue-inspect"
| "upgrade-payload-inspect"
| "upgrade-fingerprint"
| "upgrade-prepare-sqlite"
| "upgrade-takeover"
)
{
return Err("unknown development command".into());
}
let backend = env::var("DB_CONNECTION").unwrap_or_default();
if command == "upgrade-prepare-sqlite" {
if backend != "sqlite" {
return Err("upgrade-prepare-sqlite requires DB_CONNECTION=sqlite".into());
}
let path = env::var("DB_DATABASE").map_err(|_| "DB_DATABASE is required")?;
return match durable_workflow_server::runtime::upgrade::prepare_sqlite_source(&path).await
{
Ok(report) => {
println!("{report}");
Ok(())
}
Err(error) => {
let reason = match error {
RuntimeError::Refused { reason, .. } => reason,
_ => "upgrade_prepare_unavailable",
};
eprintln!("{reason}");
Err("SQLite source preparation refused".into())
}
};
}
if command == "upgrade-takeover" {
use durable_workflow_server::runtime::upgrade::{
queue_binding::QueueSourceConfig, takeover,
};
if !matches!(backend.as_str(), "sqlite" | "pgsql" | "mysql" | "mariadb") {
eprintln!("upgrade_takeover_backend_unqualified");
return Err("upgrade takeover refused".into());
}
let fingerprint = env::var("DW_UPGRADE_BACKUP_FINGERPRINT")
.map_err(|_| "DW_UPGRADE_BACKUP_FINGERPRINT is required")?;
// The original Laravel queue transport, as for upgrade-queue-inspect.
let (connection, default_connection, default_queue) = (
env::var("DW_RUST_UPGRADE_QUEUE_CONNECTION")
.map_err(|_| "original queue connection is required")?,
env::var("DW_RUST_UPGRADE_QUEUE_DEFAULT_CONNECTION")
.map_err(|_| "original default queue connection is required")?,
env::var("DW_RUST_UPGRADE_QUEUE_DEFAULT_QUEUE")
.map_err(|_| "original default transport queue is required")?,
);
let queues = QueueSourceConfig {
connection: &connection,
default_connection: &default_connection,
default_queue: &default_queue,
};
let result = if backend == "pgsql" {
takeover::take_over_postgres(postgres_options()?, &fingerprint, &queues).await
} else if backend != "sqlite" {
takeover::take_over_mysql(mysql_options()?, &fingerprint, &queues).await
} else {
let path = env::var("DB_DATABASE").map_err(|_| "DB_DATABASE is required")?;
takeover::take_over_sqlite(&path, &fingerprint, &queues).await
};
return match result {
Ok(report) => {
println!("{report}");
Ok(())
}
Err(error) => {
let reason = match error {
RuntimeError::Refused { reason, .. } => reason,
_ => "upgrade_takeover_unavailable",
};
eprintln!("{reason}");
Err("upgrade takeover refused".into())
}
};
}
if command == "upgrade-fingerprint" {
use durable_workflow_server::runtime::upgrade::fingerprint;
let result = match backend.as_str() {
"sqlite" => {
let path = env::var("DB_DATABASE").map_err(|_| "DB_DATABASE is required")?;
fingerprint::fingerprint_sqlite_cli(&path).await
}
"mysql" | "mariadb" => fingerprint::fingerprint_mysql(mysql_options()?).await,
"pgsql" => fingerprint::fingerprint_postgres(postgres_options()?).await,
_ => {
return Err(
"upgrade fingerprint requires DB_CONNECTION=sqlite, pgsql, mysql or mariadb"
.into(),
);
}
};
return match result {
Ok(report) => {
println!("{report}");
Ok(())
}
Err(error) => {
let reason = match error {
RuntimeError::Refused { reason, .. } => reason,
_ => "upgrade_fingerprint_unavailable",
};
eprintln!("{reason}");
Err("upgrade source fingerprint refused".into())
}
};
}
if matches!(
command.as_str(),
"upgrade-inspect" | "upgrade-queue-inspect" | "upgrade-payload-inspect"
Expand Down
Loading
Loading