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
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: fruwehq/determa-state-conformance
ref: 86cb08a98267371b96b8f4908409aee022e4b4fe
ref: 263644f951f342b0eeaa3aceef4877293d2d7c67
path: .pinned/determa-state-conformance
- name: Check out pinned specification
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: fruwehq/determa-state-spec
ref: 318ef1f16ae024770090bd338c8b70056df2855b
ref: cc4b0d734aa1c5953de75fb53b63e390a3b72761
path: .pinned/determa-state-spec
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
Expand Down
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ This is the Python implementation of Determa State. The distribution is
package so it can coexist with the umbrella `determa` launcher.

The implementation is conformant only when it passes the language-neutral suite.
The synchronized 0.1.0 release uses these immutable inputs:
The current implementation uses these immutable inputs:

- specification: `318ef1f16ae024770090bd338c8b70056df2855b`;
- conformance: `86cb08a98267371b96b8f4908409aee022e4b4fe` (110 core cases,
- specification: `cc4b0d734aa1c5953de75fb53b63e390a3b72761`;
- conformance: `263644f951f342b0eeaa3aceef4877293d2d7c67` (111 core cases,
persistence profiles, and the 85-vector execution-checkpoint profile).

Version `0.1.0` is the published synchronized release. The specification, conformance
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
Python implementation of [Determa State](https://github.com/fruwehq/determa-state-spec),
a language-agnostic statechart engine with a shared normative conformance suite.

This release implements Determa State `format: 1` at the synchronized specification
commit `318ef1f16ae024770090bd338c8b70056df2855b`. Correctness is determined by the
110-case core suite, persistence profiles, and 85-vector execution-checkpoint profile
at conformance commit `86cb08a98267371b96b8f4908409aee022e4b4fe`.
This implementation supports Determa State `format: 1` at specification
commit `cc4b0d734aa1c5953de75fb53b63e390a3b72761`. Correctness is determined by the
111-case core suite, persistence profiles, and 85-vector execution-checkpoint profile
at conformance commit `263644f951f342b0eeaa3aceef4877293d2d7c67`.

Version `0.1.0` is the published synchronized release of the specification, conformance
suite, Python engine, and Rust engine.
Expand Down
17 changes: 17 additions & 0 deletions conformance/persistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
)
from determa.state.wire import (
canonical_bytes,
load_json_artifact,
migration_descriptor_digest,
strict_json,
)
Expand Down Expand Up @@ -107,6 +108,14 @@ def _limits(path: Path, vector: dict[str, Any]) -> MigrationLimits | None:
def _run_vector(path: Path, vector: dict[str, Any]) -> None:
operation = vector["operation"]
expected = vector["expect"]
descriptor_source = (
(path / vector["migration_descriptor"]).read_bytes()
if operation == "decode_selected_migration_descriptor"
else None
)
descriptor_snapshot = (
bytes(descriptor_source) if descriptor_source is not None else None
)
source_key = (
"aggregate_state_package"
if operation.startswith("restore_package")
Expand All @@ -116,6 +125,12 @@ def _run_vector(path: Path, vector: dict[str, Any]) -> None:
source_snapshot = bytes(source) if source is not None else None
resolver = _resolver(path, vector)
try:
if operation == "decode_selected_migration_descriptor":
assert descriptor_source is not None
load_json_artifact(descriptor_source, "migration_descriptor")
assert expected == {"result": "success"}, vector["name"]
assert descriptor_source == descriptor_snapshot
return
if operation == "serialize_created_aggregate":
bundle = load_bundle((path / vector["source_bundle"]).read_text(encoding="utf-8"))
result = create(bundle, **vector["creation"])
Expand Down Expand Up @@ -233,6 +248,8 @@ def _run_vector(path: Path, vector: dict[str, Any]) -> None:
except ArtifactError as error:
assert expected["result"] == "failure", (vector["name"], error.code)
assert error.code == expected["code"], (vector["name"], error.code)
if descriptor_source is not None:
assert descriptor_source == descriptor_snapshot
if expected.get("caller_still_owns_aggregate"):
assert source == source_snapshot
if vector.get("repeat_count", 1) > 1:
Expand Down
4 changes: 2 additions & 2 deletions conformance/pins.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

from pathlib import Path

CONFORMANCE_COMMIT = "86cb08a98267371b96b8f4908409aee022e4b4fe"
SPEC_COMMIT = "318ef1f16ae024770090bd338c8b70056df2855b"
CONFORMANCE_COMMIT = "263644f951f342b0eeaa3aceef4877293d2d7c67"
SPEC_COMMIT = "cc4b0d734aa1c5953de75fb53b63e390a3b72761"

ROOT = Path(__file__).resolve().parent.parent
CONFORMANCE_CACHE = ROOT / ".cache" / f"determa-state-conformance-{CONFORMANCE_COMMIT[:12]}"
Expand Down
2 changes: 1 addition & 1 deletion conformance/test_conformance.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def _spec_root() -> Path | None:

def test_suite_present() -> None:
assert CORE_DIR.exists(), "pinned conformance suite is unavailable"
assert len(core_cases()) == 110
assert len(core_cases()) == 111
assert len(execution_checkpoint_vectors()) == 85


Expand Down
2 changes: 1 addition & 1 deletion scripts/sync_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"aggregate-state-package.schema.json",
"execution-checkpoint.schema.json",
)
SPEC_COMMIT = "318ef1f16ae024770090bd338c8b70056df2855b"
SPEC_COMMIT = "cc4b0d734aa1c5953de75fb53b63e390a3b72761"


def _fetch(name: str) -> str:
Expand Down
2 changes: 1 addition & 1 deletion src/determa/state/wire.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ def _format_code(document: Any, kind: str) -> str | None:
format_member, expected_format, version_member, expected_version, format_code, version_code = (
definitions[kind]
)
if format_member in document and document[format_member] != expected_format:
if format_member not in document or document[format_member] != expected_format:
return format_code
if version_member in document and document[version_member] != expected_version:
return version_code
Expand Down
41 changes: 41 additions & 0 deletions tests/test_persistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from determa.state.wire import (
aggregate_state_digest,
canonical_bytes,
load_json_artifact,
migration_descriptor_digest,
strict_json,
)
Expand Down Expand Up @@ -80,6 +81,46 @@ def _redigest(document):
return canonical_bytes(document)


LEGACY_SNAPSHOT = b'''{
"machine": {
"id": "legacy_job",
"version": "0.0.6"
},
"configuration": ["idle"],
"extended_state_variables": {
"attempts": 1
}
}\n'''


def test_selected_legacy_migration_descriptor_without_discriminator_is_unsupported() -> None:
source = bytes(LEGACY_SNAPSHOT)
resolver = MemoryArtifactResolver()

with pytest.raises(ArtifactError) as raised:
resolver.put_migration_descriptor("sha256:" + "0" * 64, source)

assert raised.value.code == "unsupported_migration_descriptor_format"
assert source == LEGACY_SNAPSHOT


@pytest.mark.parametrize(
("kind", "code"),
[
("aggregate_state", "unsupported_aggregate_state_format"),
("aggregate_state_package", "unsupported_aggregate_state_package_format"),
("execution_checkpoint", "unsupported_execution_checkpoint_format"),
],
)
def test_selected_legacy_artifact_decoder_uses_its_exact_format_code(
kind: str, code: str
) -> None:
with pytest.raises(ArtifactError) as raised:
load_json_artifact(LEGACY_SNAPSHOT, kind)

assert raised.value.code == code


def test_aggregate_round_trip_is_canonical_and_does_not_mutate_state() -> None:
bundle = load_bundle(SOURCE)
created = create(bundle, "job", "job-1", "create-1", {})
Expand Down