diff --git a/.github/workflows/crates-io-maintenance.yml b/.github/workflows/crates-io-maintenance.yml index e56ec29..9a72bad 100644 --- a/.github/workflows/crates-io-maintenance.yml +++ b/.github/workflows/crates-io-maintenance.yml @@ -65,7 +65,7 @@ jobs: - name: Install Rust 1.86 uses: dtolnay/rust-toolchain@4d407b29186a635f0cc27475ef0bc0ae605a8866 # 1.86.0 - - name: Audit discovery and exact prerelease installation + - name: Audit discovery and exact release installation run: >- python3 scripts/ci/maintain-crates-io.py audit --evidence crates-io-audit-evidence.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 31c5681..79201c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2.0.0 + +- Publish the stable Rust SDK qualified against the immutable Server `2.0.0` + artifact. + ## 2.0.0-rc.39 - Qualify this prerelease against the immutable Server `2.0.0-rc.68` artifact diff --git a/Cargo.toml b/Cargo.toml index 0865549..5a25d5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "durable-workflow" -version = "2.0.0-rc.39" +version = "2.0.0" edition = "2021" description = "First-party Rust SDK for Durable Workflow workers and clients" license = "MIT" @@ -14,10 +14,10 @@ categories = ["api-bindings", "asynchronous"] include = ["/src/**", "/schema/**", "/examples/**", "/Cargo.toml", "/README.md", "/CHANGELOG.md", "/LICENSE"] [package.metadata.durable-workflow] -product-train = "2.0.0-rc.39" +product-train = "2.0.0" compatibility-authority = "protocol-manifests" -supported-server-versions = "2.0.0-rc.68" -qualified-server-version = "2.0.0-rc.68" +supported-server-versions = "2.0.0" +qualified-server-version = "2.0.0" worker-protocol-version = "1.19" server-worker-protocol-versions = ">=1.19,<2.0" portable-worker-affinity-minimum-worker-protocol-version = "1.18" diff --git a/README.md b/README.md index 3970b79..0f23e19 100644 --- a/README.md +++ b/README.md @@ -951,6 +951,5 @@ the authoritative source for the `durable-workflow` crate and its Rust API documentation. Crate releases follow semantic versioning and are tagged with the exact crate -version. During the 2.0 beta, the crate advances with the synchronized product -train. After stable 2.0, fixes, additive capabilities, and breaking changes use -ordinary patch, minor, and major progression respectively. +version. Starting with stable 2.0, fixes, additive capabilities, and breaking +changes use ordinary patch, minor, and major progression respectively. diff --git a/docs/index.html b/docs/index.html index 69df610..f93f444 100644 --- a/docs/index.html +++ b/docs/index.html @@ -9,7 +9,7 @@ Durable Workflow for Rust - +
@@ -24,11 +24,11 @@
-

Rust SDK · 2.0 prerelease

+

Rust SDK · 2.0 stable

Durable Workflow Rust SDK

Author deterministic workflows and activities, run workers, and control workflow executions with the first-party Rust SDK.

- Supported prerelease channel + Supported 2.0 release Resolved by the public compatibility authority Requires Rust 1.86+

@@ -57,8 +57,8 @@

Confirm the Rust toolchain

  • 2
    -

    Install the prerelease channel

    -

    Let the public compatibility authority select the qualified crate in the 2.0 RC line.

    +

    Install the supported SDK

    +

    Let the public compatibility authority select the supported crate in the 2.0 line.

    curl -fsSL https://durable-workflow.com/install-sdk.sh | sh -s -- rust
  • @@ -104,7 +104,7 @@

    Durable Workflow Cloud

    Durable Workflow Rust SDK - Explicit 2.0 prerelease documentation + Stable 2.0 documentation
    diff --git a/scripts/ci/maintain-crates-io.py b/scripts/ci/maintain-crates-io.py index 2830da9..64ebc72 100644 --- a/scripts/ci/maintain-crates-io.py +++ b/scripts/ci/maintain-crates-io.py @@ -129,6 +129,13 @@ def is_2_0_release_candidate(version: str) -> bool: ) +def is_supported_2_0_release(version: str) -> bool: + major, minor, patch, prerelease = parse_semver(version) + return (major, minor, patch) == (2, 0, 0) and ( + prerelease is None or prerelease.startswith("rc.") + ) + + def load_authority( plan_path: Path, manifest_path: Path ) -> tuple[dict[str, Any], str, str]: @@ -168,12 +175,12 @@ def load_authority( raise MaintenanceError( "authority_mismatch", "manifest and retirement plan name different packages" ) - if not isinstance(current_version, str) or not is_2_0_release_candidate( + if not isinstance(current_version, str) or not is_supported_2_0_release( current_version ): raise MaintenanceError( "stable_release_not_authorized", - "registry maintenance requires the manifest to name an exact 2.0 release candidate", + "registry maintenance requires an exact supported 2.0 release", ) return plan, current_version, hashlib.sha256(plan_bytes).hexdigest() @@ -331,20 +338,20 @@ def registry_violations( exact = observation["current_exact_version"] if exact["state"] != "active": - violations.append(f"current_release_candidate_not_active:{current_version}") + violations.append(f"current_release_not_active:{current_version}") elif not exact.get("registry_response", {}).get("checksum"): - violations.append( - f"current_release_candidate_checksum_missing:{current_version}" - ) + violations.append(f"current_release_checksum_missing:{current_version}") root = observation["crate_root"] if root["newest_version"] != current_version: - violations.append("crate_root_newest_version_is_not_current_release_candidate") + violations.append("crate_root_newest_version_is_not_current_release") if require_retired and root["default_version"] != current_version: - violations.append("crate_root_default_version_is_not_current_release_candidate") + violations.append("crate_root_default_version_is_not_current_release") for candidate in observation["release_candidates"]: if candidate["yanked"] is True: violations.append(f"release_candidate_yanked:{candidate['version']}") - if observation["stable_2_versions"]: + _, _, _, current_prerelease = parse_semver(current_version) + authorized_stable_versions = [current_version] if current_prerelease is None else [] + if observation["stable_2_versions"] != authorized_stable_versions: violations.append("stable_2_release_exists_without_authorization") return violations @@ -360,7 +367,7 @@ def base_evidence( "environment_variable": "CARGO_REGISTRY_TOKEN", "value_recorded": False, }, - "current_release_candidate": current_version, + "current_release": current_version, "generated_at": utc_now(), "operation": operation, "outcome": "pending", @@ -543,13 +550,13 @@ def verify_exact_install( if len(matches) != 1: raise MaintenanceError( "exact_requirement_resolved_incorrectly", - "Cargo did not resolve the exact current release candidate", + "Cargo did not resolve the exact current release", ) resolved = matches[0] if resolved.get("checksum") != expected_checksum: raise MaintenanceError( "registry_checksum_mismatch", - "Cargo.lock and crates.io report different checksums for the current release candidate", + "Cargo.lock and crates.io report different checksums for the current release", ) return { "credential_available": False, @@ -581,7 +588,7 @@ def audit( evidence["violations"] = violations raise MaintenanceError( "live_registry_audit_failed", - "crates.io discovery does not match the supported prerelease policy", + "crates.io discovery does not match the supported release policy", ) checksum = observation["current_exact_version"]["registry_response"]["checksum"] evidence["exact_installation"] = verify_exact_install( diff --git a/scripts/ci/publish-rust-sdk.sh b/scripts/ci/publish-rust-sdk.sh index d0acbb9..c6d71d1 100755 --- a/scripts/ci/publish-rust-sdk.sh +++ b/scripts/ci/publish-rust-sdk.sh @@ -116,7 +116,7 @@ if [[ "$package_version" != "$product_train" ]]; then printf 'Rust SDK package version must match its product train metadata: %s, %s\n' "$package_version" "$product_train" >&2 exit 1 fi -if [[ "$compatibility_authority" != "protocol-manifests" || "$server_compatibility" != "2.0.0-rc.68" || "$qualified_server_version" != "2.0.0-rc.68" || "$server_worker_protocols" != ">=1.19,<2.0" ]]; then +if [[ "$compatibility_authority" != "protocol-manifests" || "$server_compatibility" != "2.0.0" || "$qualified_server_version" != "2.0.0" || "$server_worker_protocols" != ">=1.19,<2.0" ]]; then printf 'unexpected Rust SDK supported Server contract: %s, %s, %s, %s\n' "$compatibility_authority" "$server_compatibility" "$qualified_server_version" "$server_worker_protocols" >&2 exit 1 fi diff --git a/scripts/ci/qualify-docs-landing.py b/scripts/ci/qualify-docs-landing.py index ef87e6a..10182bb 100644 --- a/scripts/ci/qualify-docs-landing.py +++ b/scripts/ci/qualify-docs-landing.py @@ -61,6 +61,9 @@ EXACT_PRERELEASE_VERSION = re.compile( r"\b\d+\.\d+\.\d+-(?:alpha|beta|rc)\.\d+\b" ) +EXACT_RELEASE_VERSION = re.compile( + r"\d+\.\d+\.\d+(?:-(?:alpha|beta|rc)\.\d+)?" +) VISIBLE_CARGO_PATH = re.compile( r"\bcargo\s+add\s+durable-workflow\b|\bdurable-workflow\s*=" ) @@ -433,9 +436,9 @@ def qualified_rust_version(contract_text: str) -> str: artifacts = contract.get("artifacts") rust = artifacts.get("sdk-rust") if isinstance(artifacts, dict) else None version = rust.get("version") if isinstance(rust, dict) else None - if not isinstance(version, str) or EXACT_PRERELEASE_VERSION.fullmatch(version) is None: + if not isinstance(version, str) or EXACT_RELEASE_VERSION.fullmatch(version) is None: raise QualificationError( - "public quickstart authority is missing a qualified Rust prerelease" + "public quickstart authority is missing a qualified Rust release" ) return version diff --git a/scripts/ci/test-maintain-crates-io.py b/scripts/ci/test-maintain-crates-io.py index d0937cf..e2bc377 100644 --- a/scripts/ci/test-maintain-crates-io.py +++ b/scripts/ci/test-maintain-crates-io.py @@ -110,7 +110,7 @@ def test_plan_names_every_historical_final_and_no_prerelease(self) -> None: for version in self.plan["retired_versions"] ) ) - self.assertTrue(maintenance.is_2_0_release_candidate(self.current)) + self.assertTrue(maintenance.is_supported_2_0_release(self.current)) def test_retirement_yanks_only_active_reviewed_versions(self) -> None: registry = FakeRegistry(self.plan, self.current) @@ -192,7 +192,7 @@ def test_live_audit_rejects_stale_default_discovery(self) -> None: install.assert_not_called() evidence = json.loads(evidence_path.read_text(encoding="utf-8")) self.assertIn( - "crate_root_default_version_is_not_current_release_candidate", + "crate_root_default_version_is_not_current_release", evidence["violations"], ) diff --git a/scripts/ci/test-publish-rust-sdk.py b/scripts/ci/test-publish-rust-sdk.py index e549e55..0ecd45c 100644 --- a/scripts/ci/test-publish-rust-sdk.py +++ b/scripts/ci/test-publish-rust-sdk.py @@ -18,10 +18,10 @@ PUBLISH = ROOT / "scripts" / "ci" / "publish-rust-sdk.sh" RELEASE_WORKFLOW = ROOT / ".github" / "workflows" / "release.yml" RELEASE_TOOLING_INSTALLER = ROOT / "scripts" / "ci" / "install-release-tooling.sh" -PACKAGE_VERSION = "2.0.0-rc.39" +PACKAGE_VERSION = "2.0.0" PRODUCT_TRAIN = PACKAGE_VERSION -SERVER_VERSIONS = "2.0.0-rc.68" -QUALIFIED_SERVER_VERSION = "2.0.0-rc.68" +SERVER_VERSIONS = "2.0.0" +QUALIFIED_SERVER_VERSION = "2.0.0" WORKER_PROTOCOL_VERSION = "1.19" SERVER_WORKER_PROTOCOLS = ">=1.19,<2.0" RELEASE_COMMIT = "0123456789abcdef0123456789abcdef01234567" diff --git a/scripts/ci/test-qualify-docs-landing.py b/scripts/ci/test-qualify-docs-landing.py index 7d73bb0..88dcb23 100644 --- a/scripts/ci/test-qualify-docs-landing.py +++ b/scripts/ci/test-qualify-docs-landing.py @@ -105,6 +105,18 @@ def test_source_ahead_of_public_authority_keeps_install_guidance_truthful(self) self.assertEqual(self.source_version, body.attrs.get("data-source-version")) self.assertNotIn("data-crate-version", body.attrs) + def test_stable_public_authority_is_accepted(self) -> None: + public_version = QUALIFIER.qualified_rust_version( + json.dumps( + { + "schema": QUALIFIER.QUICKSTART_CONTRACT_SCHEMA, + "artifacts": {"sdk-rust": {"version": "2.0.0"}}, + } + ) + ) + + self.assertEqual("2.0.0", public_version) + def test_rejects_a_floating_cargo_requirement_in_visible_onboarding(self) -> None: floating_onboarding = self.html.replace( '

    ', @@ -125,7 +137,7 @@ def test_rejects_a_floating_cargo_requirement_in_visible_onboarding(self) -> Non def test_rejects_an_exact_prerelease_version_in_visible_onboarding(self) -> None: pinned_onboarding = self.html.replace( '

    ', - f'

    Qualified crate {self.source_version}', + '

    Qualified crate 2.0.0-rc.99', 1, ) diff --git a/tests/onboarding_version_pins.rs b/tests/onboarding_version_pins.rs index 620e972..5b8d5f8 100644 --- a/tests/onboarding_version_pins.rs +++ b/tests/onboarding_version_pins.rs @@ -40,7 +40,7 @@ fn visible_html_text(content: &str) -> String { } #[test] -fn onboarding_uses_the_prerelease_channel() { +fn onboarding_uses_the_versionless_supported_channel() { for (path, content) in [ ("README.md", include_str!("../README.md")), ("docs/index.html", include_str!("../docs/index.html")),