Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/phpunit-feature.yml
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ jobs:
python3 -m unittest tests/Unit/Support/php_apt_test.py
python3 scripts/ci/verify-php-apt-source.py

- name: Check the shared legacy command-key seed and authority scopes
run: python3 scripts/ci/test_command_key_legacy_fixture.py

- name: Check published failover image identity and recovery result gates
run: python3 -m unittest discover -s tests/Unit/Support -p 'single_region_failover*test.py'

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/server-parity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ on:
- config/dw-contract.php
- resources/platform-protocol-specs/carried-poll-v1-vectors.json
- resources/platform-protocol-specs/workflow-task-completion-limits-v1-vectors.json
- resources/platform-protocol-specs/command-key-legacy-v1-vectors.json
- tests/Fixtures/ActivityMetadataProjection/**
- tests/Fixtures/CodecRegression/**
- tests/Fixtures/CommandIntent/**
Expand Down
172 changes: 172 additions & 0 deletions resources/platform-protocol-specs/command-key-legacy-v1-vectors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
{
"schema": "durable-workflow.command-key-legacy-fixture.v1",
"reference_producer": {
"tag": "3.9.1",
"source_commit": "7d7ad9d6c9aee2e4592d74389ba9efd36e741f44",
"tests": "rust/tests/execution/command_keys.rs",
"scope": "Source control, not an installed-client receipt or a reusable database dump."
},
"synthetic_legacy_shape": {
"backend": "sqlite",
"source_helper": "legacy_key",
"scope": "Transform a stopped, disposable copy of real current-producer API admissions. This is not a physical pre-upgrade rehearsal.",
"preconditions": [
"Database belongs to the test cohort; no production input.",
"Server is stopped; use a disposable copy.",
"Selected command_id, command_type, workflow_instance_id and workflow_run_id equal the original API receipt.",
"Stored request_id equals the original caller key token and exactly one dw_command_intents row belongs to command_id."
],
"transaction": "BEGIN IMMEDIATE; verify preconditions before mutations; require one affected row from each statement; COMMIT only if all succeed, otherwise ROLLBACK.",
"statements": [
"DELETE FROM dw_command_intents WHERE command_id=:command_id",
"UPDATE workflow_commands SET request_id=:legacy_request_id WHERE id=:command_id"
],
"preserve": "Every other command column, related update/signal, task, history and payload-registry row remains byte-for-byte unchanged."
},
"keys": {
"original": "token-key",
"second": "second-key",
"case_exact": "CaseSensitive",
"case_other": "casesensitive",
"tokens": {
"signal": "signal-v1:7fd98d71570942fe2b98b2888d3d879c32e8130a904115032291500e856db4ba",
"update": "update-v1:7fd98d71570942fe2b98b2888d3d879c32e8130a904115032291500e856db4ba"
}
},
"legacy_readback": {
"types": [
"signal",
"update"
],
"states": [
"open_after_restart",
"completed_after_restart",
"continued_after_restart"
],
"original_input": 1,
"retry_input": 999,
"concurrency": 4,
"expected": {
"retained": true,
"intent_verified": false,
"same_fields": [
"command_id",
"run_id"
],
"update_same_fields": [
"update_id",
"accepted_at"
],
"writes": 0,
"success_statuses": {
"signal": [
202
],
"update": [
200,
202
]
}
},
"update_close_setup": [
"complete_update(original_update_id, null)",
"complete_workflow(null) or continue_as_new"
],
"closed_update_expected": {
"update_status": "completed",
"result": null
},
"explicit_original_run": "Read back the original receipt, including after continuation; do not claim the successor was targeted.",
"changed_name_or_explicit_run": {
"status": 409,
"reason": "request_id_conflict",
"original_id_field": "retained_command_id",
"writes": 0
}
},
"ambiguity": [
{
"shape": "two-rows",
"setup": "Admit original under token-key. Admit a second command under second-key; transform only that second command to raw token-key.",
"retry_key": "original"
},
{
"shape": "legacy-token",
"setup": "Admit under token-key; transform that command to raw keys.tokens[type], removing its intent.",
"retry_key": "original"
},
{
"shape": "modern-raw",
"setup": "Keep the original modern admission under token-key unchanged.",
"retry_key": "tokens[type]"
}
],
"ambiguity_expected": {
"status": 409,
"reason": "request_id_ambiguous",
"retained": true,
"intent_verified": false,
"retryable": false,
"writes": 0,
"must_not_select_receipt": true
},
"case_only": {
"setup": "Admit under CaseSensitive, then transform that command to raw CaseSensitive. Exact-byte retry reads back unverified.",
"retry_key": "casesensitive",
"mysql_mariadb_default_ci": {
"status": 409,
"reason": "request_id_ambiguous",
"retryable": false,
"writes": 0
},
"sqlite_postgresql_binary": {
"status": 202,
"identity": "new command; distinct key",
"zero_write_claim": false
}
},
"physical_upgrade": {
"scope": "Separate actual old-image admission and schema-upgrade cell. Do not substitute the synthetic transform or minimal orphan command-row schema tests.",
"baseline": {
"tag": "3.8.0",
"source_commit": "28f084513235f6647e740c4b3d9e3aace7a323b7",
"index_digest": "sha256:7988a23525d9e50e997a9f6f02e801300ed4f626de3cf6977033657ae7e9f58e"
},
"schemas": {
"sqlite": [
12,
13
],
"postgresql": [
13,
14
],
"mariadb": [
13,
14
],
"mysql": [
14,
15
]
},
"procedure": [
"Admit real signal/update receipts through baseline public APIs; record exact keys, command/run/update identities and rows/history. Use the baseline advertised worker protocol; seed generation is not a current-SDK acceptance claim.",
"Stop all baseline Servers, keep the database and backup, and record the baseline image/source and database fingerprints.",
"Run the exact candidate image schema-upgrade once; require the declared schema transition and an empty dw_command_intents table.",
"Prove original raw request_id values and command/history rows were preserved, then start the exact candidate image.",
"Perform legacy readbacks and name/target conflicts; compare full rows/history and original identities. Keep synthetic ambiguity cells separate unless their seed was genuinely created through the baseline API."
],
"no_inference": "The 3.9.1 source tests prove all-backend row preservation separately. They do not prove this installed upgrade cell for an SDK."
},
"postcommit_response_loss": {
"scope": "Separate real admission cell. A proxy must establish that the Server committed before dropping the response; then retry the original key. Synthetic legacy setup does not establish response-loss evidence."
},
"receipt_requirements": [
"Exact fixture Git commit/blob and digest",
"Actual SDK/CLI package and Server source/index/platform identities",
"Synthetic-shape versus physical-upgrade versus postcommit-loss cell label",
"Original and returned command/run/update identities and complete zero-write snapshots where required",
"Nonpass and cleanup outcomes; no inferred PASS from source lineage"
]
}
66 changes: 66 additions & 0 deletions scripts/ci/test_command_key_legacy_fixture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Check the shared producer seed, not native catalog/upgrade acceptance."""
import hashlib
import json
from pathlib import Path
import sqlite3
import unittest

ROOT = Path(__file__).resolve().parents[2]
FIXTURE = json.loads((ROOT / "resources/platform-protocol-specs/command-key-legacy-v1-vectors.json").read_text())


class LegacyFixtureTest(unittest.TestCase):
def test_tokens_are_the_reference_producer_utf8_sha256_tokens(self):
for kind in ("signal", "update"):
self.assertEqual(FIXTURE["keys"]["tokens"][kind],
kind + "-v1:" + hashlib.sha256(FIXTURE["keys"]["original"].encode()).hexdigest())

def test_seed_mutations_preserve_every_other_row_and_column(self):
for kind in ("signal", "update"):
with self.subTest(kind=kind), sqlite3.connect(":memory:") as db:
# Only the fixture's two statement effects are under test.
# This minimal table is not a native database seed/catalog.
db.executescript("""
CREATE TABLE workflow_commands(id TEXT PRIMARY KEY, request_id TEXT UNIQUE, payload BLOB, workflow_run_id TEXT);
CREATE TABLE dw_command_intents(command_id TEXT PRIMARY KEY, intent_digest TEXT);
CREATE TABLE workflow_history_events(id TEXT PRIMARY KEY, payload BLOB);
""")
db.execute("INSERT INTO workflow_commands VALUES (?,?,?,?)",
("selected", FIXTURE["keys"]["tokens"][kind], b"original", "original-run"))
db.execute("INSERT INTO workflow_commands VALUES ('other','other-key',X'00ff','other-run')")
db.executemany("INSERT INTO dw_command_intents VALUES (?,?)", [("selected", "digest"), ("other", "kept")])
db.execute("INSERT INTO workflow_history_events VALUES ('history',X'ff00')")
db.commit()
before_other = db.execute("SELECT * FROM workflow_commands WHERE id='other'").fetchall()
before_history = db.execute("SELECT * FROM workflow_history_events").fetchall()
with db:
for statement in FIXTURE["synthetic_legacy_shape"]["statements"]:
self.assertEqual(db.execute(statement, {"command_id": "selected", "legacy_request_id": "legacy-key"}).rowcount, 1)
self.assertEqual(db.execute("SELECT * FROM workflow_commands WHERE id='selected'").fetchone(),
("selected", "legacy-key", b"original", "original-run"))
self.assertEqual(db.execute("SELECT * FROM dw_command_intents").fetchall(), [("other", "kept")])
self.assertEqual(db.execute("SELECT * FROM workflow_commands WHERE id='other'").fetchall(), before_other)
self.assertEqual(db.execute("SELECT * FROM workflow_history_events").fetchall(), before_history)
# A failed second statement must roll back the intent deletion.
db.execute("INSERT INTO dw_command_intents VALUES ('selected','restored')")
db.commit()
with self.assertRaises(sqlite3.IntegrityError):
with db:
for statement in FIXTURE["synthetic_legacy_shape"]["statements"]:
db.execute(statement, {"command_id": "selected", "legacy_request_id": "other-key"})
self.assertEqual(db.execute("SELECT intent_digest FROM dw_command_intents WHERE command_id='selected'").fetchone(), ("restored",))
self.assertEqual(db.execute("SELECT request_id FROM workflow_commands WHERE id='selected'").fetchone(), ("legacy-key",))

def test_reference_cases_keep_distinct_authority(self):
self.assertEqual(FIXTURE["schema"], "durable-workflow.command-key-legacy-fixture.v1")
self.assertEqual({c["shape"] for c in FIXTURE["ambiguity"]}, {"two-rows", "legacy-token", "modern-raw"})
self.assertEqual(FIXTURE["ambiguity_expected"]["retryable"], False)

Check warning on line 57 in scripts/ci/test_command_key_legacy_fixture.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using "assertFalse" instead.

See more on https://sonarcloud.io/project/issues?id=durable-workflow_server&issues=AaHVJt4GbHm0dhBzK61P&open=AaHVJt4GbHm0dhBzK61P&pullRequest=1208
self.assertEqual(FIXTURE["legacy_readback"]["expected"]["intent_verified"], False)

Check warning on line 58 in scripts/ci/test_command_key_legacy_fixture.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using "assertFalse" instead.

See more on https://sonarcloud.io/project/issues?id=durable-workflow_server&issues=AaHVJt4GbHm0dhBzK61Q&open=AaHVJt4GbHm0dhBzK61Q&pullRequest=1208
self.assertEqual(FIXTURE["physical_upgrade"]["schemas"],
{"sqlite": [12, 13], "postgresql": [13, 14], "mariadb": [13, 14], "mysql": [14, 15]})
self.assertEqual(FIXTURE["case_only"]["mysql_mariadb_default_ci"]["status"], 409)
self.assertEqual(FIXTURE["case_only"]["sqlite_postgresql_binary"]["zero_write_claim"], False)

Check warning on line 62 in scripts/ci/test_command_key_legacy_fixture.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Consider using "assertFalse" instead.

See more on https://sonarcloud.io/project/issues?id=durable-workflow_server&issues=AaHVJt4GbHm0dhBzK61R&open=AaHVJt4GbHm0dhBzK61R&pullRequest=1208


if __name__ == "__main__":
unittest.main()
1 change: 1 addition & 0 deletions tests/Unit/ServerParityAggregateTest.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ test('pull requests run without a path filter and push keeps the one path list',
for (const input of [
'tests/Fixtures/ActivityResultInspection/v1.json',
'resources/platform-protocol-specs/workflow-task-completion-limits-v1-vectors.json',
'resources/platform-protocol-specs/command-key-legacy-v1-vectors.json',
'tests/Fixtures/ServerParityReaders/sdk-signals.mjs',
'tests/Conformance/ServerParityDeadlinePollTest.mjs',
'benchmarks/capacity/v1/bindings/php/capacity_adapter.php',
Expand Down
Loading