From e5a704854e3c43689bedf5e26082a8874fd01065 Mon Sep 17 00:00:00 2001 From: Christian-Manuel Butzke Date: Mon, 5 Oct 2026 11:25:47 +0000 Subject: [PATCH 01/10] feat: checkpoint durable public v1 client; local host integration unfinished --- .../archive-export-request-v1.schema.json | 56 + .../archive-import-request-v1.schema.json | 56 + .../data/archive-participant-v1.schema.json | 109 + .../state/data/archive-result-v1.schema.json | 311 ++ src/determa/state/data/archive-v1.schema.json | 503 +++ .../data/public-host-request-v1.schema.json | 767 ++++ .../data/public-host-response-v1.schema.json | 3390 +++++++++++++++++ .../data/recovery-operation-v1.schema.json | 1423 +++++++ .../state/data/recovery-record-v1.schema.json | 314 ++ .../timer-helper-operation-v1.schema.json | 579 +++ src/determa/state/public_client.py | 260 ++ tests/test_public_client.py | 148 + 12 files changed, 7916 insertions(+) create mode 100644 src/determa/state/data/archive-export-request-v1.schema.json create mode 100644 src/determa/state/data/archive-import-request-v1.schema.json create mode 100644 src/determa/state/data/archive-participant-v1.schema.json create mode 100644 src/determa/state/data/archive-result-v1.schema.json create mode 100644 src/determa/state/data/archive-v1.schema.json create mode 100644 src/determa/state/data/public-host-request-v1.schema.json create mode 100644 src/determa/state/data/public-host-response-v1.schema.json create mode 100644 src/determa/state/data/recovery-operation-v1.schema.json create mode 100644 src/determa/state/data/recovery-record-v1.schema.json create mode 100644 src/determa/state/data/timer-helper-operation-v1.schema.json create mode 100644 src/determa/state/public_client.py create mode 100644 tests/test_public_client.py diff --git a/src/determa/state/data/archive-export-request-v1.schema.json b/src/determa/state/data/archive-export-request-v1.schema.json new file mode 100644 index 0000000..a0c2714 --- /dev/null +++ b/src/determa/state/data/archive-export-request-v1.schema.json @@ -0,0 +1,56 @@ +{ + "type": "object", + "required": [ + "request_format", + "request_schema_version", + "source", + "root_instance_ids", + "required_participant_ids", + "optional_participant_ids", + "consistency_token" + ], + "additionalProperties": false, + "properties": { + "request_format": { + "const": "determa.archive_export_request" + }, + "request_schema_version": { + "const": 1 + }, + "source": { + "$ref": "archive-v1.schema.json#/$defs/source" + }, + "root_instance_ids": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + }, + "required_participant_ids": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z][a-z0-9.-]*$" + }, + "uniqueItems": true + }, + "optional_participant_ids": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z][a-z0-9.-]*$" + }, + "uniqueItems": true + }, + "consistency_token": { + "type": "string", + "minLength": 1 + } + }, + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://determa.dev/state/schema/archive-export-request-v1.schema.json", + "title": "Determa archive export request version 1" +} diff --git a/src/determa/state/data/archive-import-request-v1.schema.json b/src/determa/state/data/archive-import-request-v1.schema.json new file mode 100644 index 0000000..b2dff37 --- /dev/null +++ b/src/determa/state/data/archive-import-request-v1.schema.json @@ -0,0 +1,56 @@ +{ + "type": "object", + "required": [ + "request_format", + "request_schema_version", + "archive_digest", + "trusted_source_profile_digest", + "trusted_participant_contract_digest", + "expected_source", + "allow_optional_omission", + "requested_optional_participant_ids", + "staging_identity" + ], + "additionalProperties": false, + "properties": { + "request_format": { + "const": "determa.archive_import_request" + }, + "request_schema_version": { + "const": 1 + }, + "archive_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "trusted_source_profile_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "trusted_participant_contract_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "expected_source": { + "$ref": "archive-v1.schema.json#/$defs/source" + }, + "allow_optional_omission": { + "type": "boolean" + }, + "requested_optional_participant_ids": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z][a-z0-9.-]*$" + }, + "uniqueItems": true + }, + "staging_identity": { + "type": "string", + "minLength": 1 + } + }, + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://determa.dev/state/schema/archive-import-request-v1.schema.json", + "title": "Determa archive import request version 1" +} diff --git a/src/determa/state/data/archive-participant-v1.schema.json b/src/determa/state/data/archive-participant-v1.schema.json new file mode 100644 index 0000000..698db6b --- /dev/null +++ b/src/determa/state/data/archive-participant-v1.schema.json @@ -0,0 +1,109 @@ +{ + "type": "object", + "required": [ + "participant_format", + "participant_schema_version", + "participant_id", + "required", + "provider_reference", + "participant_schema_digest", + "dependencies", + "storage", + "payload_digest", + "payload" + ], + "additionalProperties": false, + "properties": { + "participant_id": { + "type": "string", + "pattern": "^[a-z][a-z0-9.-]*$" + }, + "required": { + "type": "boolean" + }, + "provider_reference": { + "$ref": "provider-reference-v1.schema.json" + }, + "participant_schema_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "dependencies": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z][a-z0-9.-]*$" + }, + "uniqueItems": true + }, + "storage": { + "enum": [ + "embedded", + "external" + ] + }, + "payload_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "payload": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "aggregate-state-v1.schema.json#/$defs/typedValue" + } + ] + }, + "participant_format": { + "const": "determa.archive_participant" + }, + "participant_schema_version": { + "const": 1 + } + }, + "allOf": [ + { + "if": { + "properties": { + "storage": { + "const": "embedded" + } + }, + "required": [ + "storage" + ] + }, + "then": { + "properties": { + "payload": { + "$ref": "aggregate-state-v1.schema.json#/$defs/typedValue" + } + } + } + }, + { + "if": { + "properties": { + "storage": { + "const": "external" + } + }, + "required": [ + "storage" + ] + }, + "then": { + "properties": { + "payload": { + "type": "null" + } + } + } + } + ], + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://determa.dev/state/schema/archive-participant-v1.schema.json", + "title": "Determa archive participant record version 1" +} diff --git a/src/determa/state/data/archive-result-v1.schema.json b/src/determa/state/data/archive-result-v1.schema.json new file mode 100644 index 0000000..edbb2b6 --- /dev/null +++ b/src/determa/state/data/archive-result-v1.schema.json @@ -0,0 +1,311 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://determa.dev/state/schema/archive-result-v1.schema.json", + "title": "Determa archive operation result version 1", + "oneOf": [ + { + "type": "object", + "required": [ + "result_format", + "result_schema_version", + "status", + "archive_digest", + "staged", + "included_participant_ids", + "absent_optional_participants", + "unsupported_optional_participants" + ], + "additionalProperties": false, + "properties": { + "result_format": { + "const": "determa.archive_result" + }, + "result_schema_version": { + "const": 1 + }, + "status": { + "const": "staged" + }, + "archive_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "staged": { + "const": true + }, + "absent_optional_participants": { + "type": "array", + "items": { + "type": "object", + "required": [ + "participant_id", + "reason" + ], + "additionalProperties": false, + "properties": { + "participant_id": { + "type": "string", + "pattern": "^[a-z][a-z0-9.-]*$" + }, + "reason": { + "enum": [ + "not_present", + "provider_unavailable", + "schema_unsupported", + "capability_unsupported", + "payload_unavailable" + ] + } + } + } + }, + "unsupported_optional_participants": { + "type": "array", + "items": { + "type": "object", + "required": [ + "participant_id", + "reason" + ], + "additionalProperties": false, + "properties": { + "participant_id": { + "type": "string", + "pattern": "^[a-z][a-z0-9.-]*$" + }, + "reason": { + "enum": [ + "not_present", + "provider_unavailable", + "schema_unsupported", + "capability_unsupported", + "payload_unavailable" + ] + } + } + } + }, + "included_participant_ids": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[a-z][a-z0-9.-]*$" + } + } + } + }, + { + "type": "object", + "required": [ + "result_format", + "result_schema_version", + "status", + "archive_digest", + "staged", + "included_participant_ids", + "absent_optional_participants", + "unsupported_optional_participants" + ], + "additionalProperties": false, + "properties": { + "result_format": { + "const": "determa.archive_result" + }, + "result_schema_version": { + "const": 1 + }, + "status": { + "const": "exported" + }, + "archive_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "staged": { + "const": false + }, + "absent_optional_participants": { + "type": "array", + "items": { + "type": "object", + "required": [ + "participant_id", + "reason" + ], + "additionalProperties": false, + "properties": { + "participant_id": { + "type": "string", + "pattern": "^[a-z][a-z0-9.-]*$" + }, + "reason": { + "enum": [ + "not_present", + "provider_unavailable", + "schema_unsupported", + "capability_unsupported", + "payload_unavailable" + ] + } + } + } + }, + "unsupported_optional_participants": { + "type": "array", + "items": { + "type": "object", + "required": [ + "participant_id", + "reason" + ], + "additionalProperties": false, + "properties": { + "participant_id": { + "type": "string", + "pattern": "^[a-z][a-z0-9.-]*$" + }, + "reason": { + "enum": [ + "not_present", + "provider_unavailable", + "schema_unsupported", + "capability_unsupported", + "payload_unavailable" + ] + } + } + } + }, + "included_participant_ids": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[a-z][a-z0-9.-]*$" + } + } + } + }, + { + "type": "object", + "required": [ + "result_format", + "result_schema_version", + "status", + "code", + "participant_id", + "staged", + "included_participant_ids", + "absent_optional_participants", + "unsupported_optional_participants" + ], + "additionalProperties": false, + "properties": { + "result_format": { + "const": "determa.archive_result" + }, + "result_schema_version": { + "const": 1 + }, + "status": { + "const": "refused" + }, + "code": { + "enum": [ + "unsupported_archive_format", + "unsupported_archive_schema_version", + "unsupported_archive_participant_format", + "unsupported_archive_participant_schema_version", + "invalid_archive", + "archive_digest_mismatch", + "invalid_archive_checkpoint", + "invalid_archive_attachment", + "invalid_archive_participant", + "archive_payload_digest_mismatch", + "missing_required_artifact", + "missing_required_provider", + "archive_capability_mismatch", + "archive_consistency_unavailable", + "archive_staging_failure", + "archive_source_profile_mismatch", + "archive_participant_contract_mismatch", + "archive_source_provenance_mismatch", + "archive_selection_invalid", + "archive_host_journal_inventory_mismatch", + "archive_manifest_mismatch" + ] + }, + "participant_id": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string", + "pattern": "^[a-z][a-z0-9.-]*$" + } + ] + }, + "staged": { + "const": false + }, + "absent_optional_participants": { + "type": "array", + "items": { + "type": "object", + "required": [ + "participant_id", + "reason" + ], + "additionalProperties": false, + "properties": { + "participant_id": { + "type": "string", + "pattern": "^[a-z][a-z0-9.-]*$" + }, + "reason": { + "enum": [ + "not_present", + "provider_unavailable", + "schema_unsupported", + "capability_unsupported", + "payload_unavailable" + ] + } + } + } + }, + "unsupported_optional_participants": { + "type": "array", + "items": { + "type": "object", + "required": [ + "participant_id", + "reason" + ], + "additionalProperties": false, + "properties": { + "participant_id": { + "type": "string", + "pattern": "^[a-z][a-z0-9.-]*$" + }, + "reason": { + "enum": [ + "not_present", + "provider_unavailable", + "schema_unsupported", + "capability_unsupported", + "payload_unavailable" + ] + } + } + } + }, + "included_participant_ids": { + "const": [] + } + } + } + ] +} diff --git a/src/determa/state/data/archive-v1.schema.json b/src/determa/state/data/archive-v1.schema.json new file mode 100644 index 0000000..2f43f04 --- /dev/null +++ b/src/determa/state/data/archive-v1.schema.json @@ -0,0 +1,503 @@ +{ + "type": "object", + "required": [ + "archive_format", + "archive_schema_version", + "source", + "participant_contract", + "selection", + "checkpoints", + "normalized_definitions", + "migration_descriptors", + "participants", + "archive_digest", + "members", + "required_determa_capabilities", + "optional_participant_references", + "source_fence_reference", + "transfer_reference" + ], + "additionalProperties": false, + "properties": { + "archive_format": { + "const": "determa.scope_archive" + }, + "archive_schema_version": { + "const": 1 + }, + "selection": { + "type": "object", + "required": [ + "root_instance_ids", + "consistency_token" + ], + "additionalProperties": false, + "properties": { + "root_instance_ids": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "consistency_token": { + "type": "string", + "minLength": 1 + } + } + }, + "checkpoints": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "execution-checkpoint-v1.schema.json" + } + }, + "normalized_definitions": { + "type": "array", + "items": { + "type": "object", + "required": [ + "validated_bundle_fingerprint", + "normalized_bundle" + ], + "additionalProperties": false, + "properties": { + "validated_bundle_fingerprint": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "normalized_bundle": { + "$ref": "aggregate-state-v1.schema.json#/$defs/typedValue" + } + } + } + }, + "migration_descriptors": { + "type": "array", + "items": { + "$ref": "migration-descriptor-v1.schema.json" + } + }, + "participants": { + "type": "array", + "items": { + "$ref": "archive-participant-v1.schema.json" + } + }, + "archive_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "source": { + "$ref": "#/$defs/source" + }, + "participant_contract": { + "$ref": "#/$defs/participantContract" + }, + "members": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "identity", + "digest", + "byte_length" + ], + "additionalProperties": false, + "properties": { + "identity": { + "type": "string", + "pattern": "^(checkpoint|definition|migration_descriptor|participant):.+" + }, + "digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "byte_length": { + "type": "string", + "pattern": "^[1-9][0-9]*$" + } + } + } + }, + "required_determa_capabilities": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/$defs/capability" + } + }, + "optional_participant_references": { + "type": "array", + "items": { + "$ref": "#/$defs/optionalParticipantReference" + } + }, + "source_fence_reference": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/sourceFenceReference" + } + ] + }, + "transfer_reference": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/transferReference" + } + ] + } + }, + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://determa.dev/state/schema/archive-v1.schema.json", + "title": "Determa portable archive version 1", + "$defs": { + "source": { + "type": "object", + "required": [ + "profile_kind", + "profile_claims", + "profile_digest", + "logical_scope_identity", + "ownership_binding_digest", + "scope_generation" + ], + "additionalProperties": false, + "properties": { + "profile_kind": { + "enum": [ + "standalone", + "checkpoint_host", + "guarded_scope" + ] + }, + "profile_claims": { + "type": "array", + "items": { + "enum": [ + "durable_embedded_processing", + "exactly_once_committed_processing", + "broker_integrated", + "strict_durable_outbox", + "compact_durable_outbox", + "shared_application_transaction", + "guarded_local_writes", + "worker_fencing", + "complete_scope_inventory", + "safe_relocation", + "durable_native_results", + "lossless_projection" + ] + }, + "uniqueItems": true + }, + "profile_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "logical_scope_identity": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string", + "minLength": 1 + } + ] + }, + "ownership_binding_digest": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + ] + }, + "scope_generation": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "profile_kind": { + "const": "standalone" + } + }, + "required": [ + "profile_kind" + ] + }, + "then": { + "properties": { + "profile_claims": { + "const": [] + }, + "logical_scope_identity": { + "type": "null" + }, + "ownership_binding_digest": { + "type": "null" + }, + "scope_generation": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "profile_kind": { + "const": "guarded_scope" + } + }, + "required": [ + "profile_kind" + ] + }, + "then": { + "properties": { + "logical_scope_identity": { + "type": "string", + "minLength": 1 + }, + "ownership_binding_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "scope_generation": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "profile_claims": { + "type": "array", + "contains": { + "const": "guarded_local_writes" + } + } + } + } + }, + { + "if": { + "properties": { + "profile_kind": { + "const": "checkpoint_host" + } + }, + "required": [ + "profile_kind" + ] + }, + "then": { + "properties": { + "logical_scope_identity": { + "type": "string", + "minLength": 1 + } + } + } + } + ] + }, + "participantContract": { + "type": "object", + "required": [ + "required_participant_ids", + "optional_participant_ids", + "participant_contract_digest" + ], + "additionalProperties": false, + "properties": { + "required_participant_ids": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z][a-z0-9.-]*$" + }, + "uniqueItems": true + }, + "optional_participant_ids": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z][a-z0-9.-]*$" + }, + "uniqueItems": true + }, + "participant_contract_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + } + }, + "capability": { + "enum": [ + "portable_archive", + "portable_checkpoint", + "normalized_definition", + "migration_descriptor", + "archive_participant", + "host_effect_journal" + ] + }, + "optionalParticipantReference": { + "type": "object", + "required": [ + "participant_id", + "provider_reference", + "participant_schema_digest" + ], + "additionalProperties": false, + "properties": { + "participant_id": { + "type": "string", + "pattern": "^[a-z][a-z0-9.-]*$" + }, + "provider_reference": { + "$ref": "provider-reference-v1.schema.json" + }, + "participant_schema_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + } + }, + "sourceFenceReference": { + "type": "object", + "required": [ + "operation_kind", + "operation_id", + "response_digest" + ], + "additionalProperties": false, + "properties": { + "operation_kind": { + "enum": [ + "freeze_scope", + "prove_retirement" + ] + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "response_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + } + }, + "transferReference": { + "type": "object", + "required": [ + "operation_kind", + "operation_id", + "response_digest", + "destination_binding_digest" + ], + "additionalProperties": false, + "properties": { + "operation_kind": { + "const": "prove_retirement" + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "response_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "destination_binding_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "source": { + "properties": { + "profile_kind": { + "const": "standalone" + } + }, + "required": [ + "profile_kind" + ] + } + }, + "required": [ + "source" + ] + }, + "then": { + "properties": { + "source_fence_reference": { + "type": "null" + }, + "transfer_reference": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "transfer_reference": { + "type": "object" + } + }, + "required": [ + "transfer_reference" + ] + }, + "then": { + "properties": { + "source_fence_reference": { + "$ref": "#/$defs/sourceFenceReference" + }, + "source": { + "properties": { + "profile_claims": { + "contains": { + "const": "safe_relocation" + } + } + } + } + } + } + } + ] +} diff --git a/src/determa/state/data/public-host-request-v1.schema.json b/src/determa/state/data/public-host-request-v1.schema.json new file mode 100644 index 0000000..38e9e14 --- /dev/null +++ b/src/determa/state/data/public-host-request-v1.schema.json @@ -0,0 +1,767 @@ +{ + "type": "object", + "required": [ + "protocol", + "protocol_version", + "operation_id", + "scope_binding_identity", + "operation", + "target", + "precondition", + "arguments" + ], + "additionalProperties": false, + "properties": { + "protocol": { + "const": "determa.execution_host" + }, + "protocol_version": { + "const": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "scope_binding_identity": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string", + "minLength": 1 + } + ] + }, + "operation": { + "enum": [ + "capabilities", + "create", + "admit", + "process", + "read", + "inspect", + "receipt", + "effect_result", + "cancel_effect", + "timer_command", + "scope_operation" + ] + }, + "target": { + "type": "object", + "required": [ + "root_instance_id", + "runtime_id", + "runtime_incarnation" + ], + "additionalProperties": false, + "properties": { + "root_instance_id": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string", + "minLength": 1 + } + ] + }, + "runtime_id": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string", + "minLength": 1 + } + ] + }, + "runtime_incarnation": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "aggregate-state-v1.schema.json#/$defs/identityOrigin" + } + ] + } + } + }, + "precondition": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "required": [ + "revision", + "checkpoint_digest" + ], + "additionalProperties": false, + "properties": { + "revision": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "checkpoint_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + } + } + ] + }, + "arguments": { + "anyOf": [ + { + "$ref": "#/$defs/capabilitiesArguments" + }, + { + "$ref": "#/$defs/createArguments" + }, + { + "$ref": "#/$defs/admitArguments" + }, + { + "$ref": "#/$defs/processArguments" + }, + { + "$ref": "#/$defs/readArguments" + }, + { + "$ref": "#/$defs/inspectArguments" + }, + { + "$ref": "#/$defs/receiptArguments" + }, + { + "$ref": "#/$defs/effect_resultArguments" + }, + { + "$ref": "#/$defs/cancel_effectArguments" + }, + { + "$ref": "#/$defs/timer_commandArguments" + }, + { + "$ref": "#/$defs/scope_operationArguments" + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "operation": { + "const": "capabilities" + } + } + }, + "then": { + "properties": { + "arguments": { + "$ref": "#/$defs/capabilitiesArguments" + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "create" + } + } + }, + "then": { + "properties": { + "arguments": { + "$ref": "#/$defs/createArguments" + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "admit" + } + } + }, + "then": { + "properties": { + "arguments": { + "$ref": "#/$defs/admitArguments" + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "process" + } + } + }, + "then": { + "properties": { + "arguments": { + "$ref": "#/$defs/processArguments" + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "read" + } + } + }, + "then": { + "properties": { + "arguments": { + "$ref": "#/$defs/readArguments" + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "inspect" + } + } + }, + "then": { + "properties": { + "arguments": { + "$ref": "#/$defs/inspectArguments" + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "receipt" + } + } + }, + "then": { + "properties": { + "arguments": { + "$ref": "#/$defs/receiptArguments" + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "effect_result" + } + } + }, + "then": { + "properties": { + "arguments": { + "$ref": "#/$defs/effect_resultArguments" + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "cancel_effect" + } + } + }, + "then": { + "properties": { + "arguments": { + "$ref": "#/$defs/cancel_effectArguments" + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "timer_command" + } + } + }, + "then": { + "properties": { + "arguments": { + "$ref": "#/$defs/timer_commandArguments" + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "scope_operation" + } + } + }, + "then": { + "properties": { + "arguments": { + "$ref": "#/$defs/scope_operationArguments" + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "enum": [ + "capabilities", + "scope_operation" + ] + } + } + }, + "then": { + "properties": { + "target": { + "properties": { + "root_instance_id": { + "type": "null" + }, + "runtime_id": { + "type": "null" + }, + "runtime_incarnation": { + "type": "null" + } + } + }, + "precondition": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "create" + } + } + }, + "then": { + "properties": { + "target": { + "properties": { + "root_instance_id": { + "type": "string", + "minLength": 1 + }, + "runtime_id": { + "type": "null" + }, + "runtime_incarnation": { + "type": "null" + } + } + }, + "precondition": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "enum": [ + "admit", + "process", + "effect_result", + "cancel_effect" + ] + } + } + }, + "then": { + "properties": { + "target": { + "properties": { + "root_instance_id": { + "type": "string", + "minLength": 1 + } + } + }, + "precondition": { + "type": "object", + "required": [ + "revision", + "checkpoint_digest" + ], + "additionalProperties": false, + "properties": { + "revision": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "checkpoint_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + } + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "process" + } + } + }, + "then": { + "properties": { + "target": { + "properties": { + "runtime_id": { + "type": "string", + "minLength": 1 + }, + "runtime_incarnation": { + "$ref": "aggregate-state-v1.schema.json#/$defs/identityOrigin" + } + } + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "receipt" + } + } + }, + "then": { + "properties": { + "precondition": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "capabilities" + } + } + }, + "then": {}, + "else": { + "properties": { + "scope_binding_identity": { + "type": "string", + "minLength": 1 + } + } + } + } + ], + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://determa.dev/state/schema/public-host-request-v1.schema.json", + "title": "Determa State execution host request version 1", + "$defs": { + "capabilitiesArguments": { + "type": "object", + "required": [ + "scope_alias" + ], + "additionalProperties": false, + "properties": { + "scope_alias": { + "type": "string", + "minLength": 1 + } + } + }, + "createArguments": { + "type": "object", + "required": [ + "validated_bundle_fingerprint", + "namespace", + "machine_id", + "machine_version", + "root_instance_id", + "creation_id", + "bindings" + ], + "additionalProperties": false, + "properties": { + "validated_bundle_fingerprint": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "namespace": { + "type": "string", + "minLength": 1 + }, + "machine_id": { + "type": "string", + "minLength": 1 + }, + "machine_version": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "root_instance_id": { + "type": "string", + "minLength": 1 + }, + "creation_id": { + "type": "string", + "minLength": 1 + }, + "bindings": { + "$ref": "aggregate-state-v1.schema.json#/$defs/typedMap" + } + } + }, + "admitArguments": { + "type": "object", + "required": [ + "ordered_deliveries" + ], + "additionalProperties": false, + "properties": { + "ordered_deliveries": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "delivery_mode", + "envelope", + "envelope_digest" + ], + "additionalProperties": false, + "properties": { + "delivery_mode": { + "enum": [ + "input", + "internal" + ] + }, + "envelope": { + "$ref": "aggregate-state-v1.schema.json#/$defs/envelope" + }, + "envelope_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + } + } + } + } + }, + "processArguments": { + "type": "object", + "required": [], + "additionalProperties": false, + "properties": {} + }, + "readArguments": { + "type": "object", + "required": [], + "additionalProperties": false, + "properties": {} + }, + "inspectArguments": { + "type": "object", + "required": [ + "candidate" + ], + "additionalProperties": false, + "properties": { + "candidate": { + "$ref": "inspection-v1.schema.json#/$defs/request" + } + } + }, + "receiptArguments": { + "type": "object", + "required": [ + "request_digest", + "queried_operation_id" + ], + "additionalProperties": false, + "properties": { + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "queried_operation_id": { + "type": "string", + "minLength": 1 + } + } + }, + "effect_resultArguments": { + "type": "object", + "required": [ + "result", + "expected_journal_revision" + ], + "additionalProperties": false, + "properties": { + "result": { + "$ref": "effect-result-request-v1.schema.json" + }, + "expected_journal_revision": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + } + } + }, + "cancel_effectArguments": { + "type": "object", + "required": [ + "request", + "expected_journal_revision" + ], + "additionalProperties": false, + "properties": { + "request": { + "$ref": "effect-cancellation-request-v1.schema.json" + }, + "expected_journal_revision": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + } + } + }, + "timer_commandArguments": { + "type": "object", + "required": [ + "timer_request" + ], + "additionalProperties": false, + "properties": { + "timer_request": { + "$ref": "timer-helper-operation-v1.schema.json#/$defs/request" + } + } + }, + "scope_operationArguments": { + "oneOf": [ + { + "type": "object", + "required": [ + "action", + "authority_request" + ], + "additionalProperties": false, + "properties": { + "action": { + "const": "authority" + }, + "authority_request": { + "$ref": "host-authority-operation-v1.schema.json#/$defs/request" + } + } + }, + { + "type": "object", + "required": [ + "action", + "archive_request" + ], + "additionalProperties": false, + "properties": { + "action": { + "const": "archive_export" + }, + "archive_request": { + "$ref": "archive-export-request-v1.schema.json" + } + } + }, + { + "type": "object", + "required": [ + "action", + "archive_request", + "archive" + ], + "additionalProperties": false, + "properties": { + "action": { + "const": "archive_import" + }, + "archive_request": { + "$ref": "archive-import-request-v1.schema.json" + }, + "archive": { + "$ref": "archive-v1.schema.json" + } + } + }, + { + "type": "object", + "required": [ + "action", + "recovery_request" + ], + "additionalProperties": false, + "properties": { + "action": { + "const": "recovery" + }, + "recovery_request": { + "allOf": [ + { + "$ref": "recovery-operation-v1.schema.json" + }, + { + "properties": { + "operation_format": { + "const": "determa.recovery_operation" + } + } + } + ] + } + } + } + ] + } + } +} diff --git a/src/determa/state/data/public-host-response-v1.schema.json b/src/determa/state/data/public-host-response-v1.schema.json new file mode 100644 index 0000000..d933412 --- /dev/null +++ b/src/determa/state/data/public-host-response-v1.schema.json @@ -0,0 +1,3390 @@ +{ + "type": "object", + "required": [ + "protocol", + "protocol_version", + "operation_id", + "status", + "receipt", + "value", + "error" + ], + "additionalProperties": false, + "properties": { + "protocol": { + "const": "determa.execution_host" + }, + "protocol_version": { + "const": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "status": { + "enum": [ + "committed", + "pending", + "rejected" + ] + }, + "receipt": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "required": [ + "scope_binding_identity", + "operation_id", + "request_digest", + "receipt_kind", + "evidence_digest", + "acceptance_receipt" + ], + "additionalProperties": false, + "properties": { + "scope_binding_identity": { + "type": "string", + "minLength": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "receipt_kind": { + "enum": [ + "committed", + "accepted" + ] + }, + "evidence_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "acceptance_receipt": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/acceptanceReceipt" + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "receipt_kind": { + "const": "accepted" + } + } + }, + "then": { + "properties": { + "acceptance_receipt": { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/acceptanceReceipt" + } + } + }, + "else": { + "properties": { + "acceptance_receipt": { + "type": "null" + } + } + } + } + ] + } + ] + }, + "value": { + "oneOf": [ + { + "type": "null" + }, + { + "oneOf": [ + { + "$ref": "#/$defs/capabilitiesValue" + }, + { + "$ref": "#/$defs/createValue" + }, + { + "$ref": "#/$defs/admitValue" + }, + { + "$ref": "#/$defs/processValue" + }, + { + "$ref": "#/$defs/readValue" + }, + { + "$ref": "#/$defs/inspectValue" + }, + { + "$ref": "#/$defs/receiptValue" + }, + { + "$ref": "#/$defs/effect_resultValue" + }, + { + "$ref": "#/$defs/cancel_effectValue" + }, + { + "$ref": "#/$defs/timer_commandValue" + }, + { + "$ref": "#/$defs/scope_operationValue" + } + ] + } + ] + }, + "error": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "object", + "required": [ + "operation", + "code" + ], + "additionalProperties": false, + "properties": { + "operation": { + "enum": [ + "capabilities", + "create", + "admit", + "process", + "read", + "inspect", + "receipt", + "effect_result", + "cancel_effect", + "timer_command", + "scope_operation" + ] + }, + "code": { + "enum": [ + "archive_capability_mismatch", + "archive_consistency_unavailable", + "archive_digest_mismatch", + "archive_host_journal_inventory_mismatch", + "archive_manifest_mismatch", + "archive_participant_contract_mismatch", + "archive_payload_digest_mismatch", + "archive_selection_invalid", + "archive_source_profile_mismatch", + "archive_source_provenance_mismatch", + "archive_staging_failure", + "binding_unavailable", + "checkpoint_revision_conflict", + "clone_has_unresolved_work", + "clone_isolation_unproven", + "creation_id_conflict", + "delivery_ambiguous", + "delivery_digest_mismatch", + "duplicate_event_id_in_batch", + "effect_not_outstanding", + "effect_outcome_ambiguous", + "effect_result_conflict", + "event_disposition_missing", + "event_id_conflict", + "host_capability_mismatch", + "inactive_component_target", + "inspection_capability_unavailable", + "inspection_guard_failure", + "inspection_limit_exceeded", + "invalid_archive", + "invalid_archive_attachment", + "invalid_archive_checkpoint", + "invalid_archive_participant", + "invalid_correlation", + "invalid_delivery_mode", + "invalid_delivery_source", + "invalid_event", + "invalid_host_request", + "invalid_inspection_request", + "invalid_instance_target", + "invalid_payload", + "invalid_recovery_request", + "invalid_timer_request", + "invalid_timer_time", + "language_provider_unavailable", + "malformed_delivery", + "missing_required_artifact", + "missing_required_provider", + "operation_id_conflict", + "outcome_unknown", + "portable_value_required", + "projection_not_lossless", + "replay_evidence_expired", + "runtime_provider_output_invalid", + "runtime_provider_unavailable", + "scope_archive_digest_mismatch", + "scope_archive_incomplete", + "scope_destination_not_empty", + "scope_fence_unproven", + "scope_generation_conflict", + "scope_not_active", + "scope_operation_conflict", + "scope_restore_requires_quarantine", + "scope_transaction_in_doubt", + "scope_transfer_consumed", + "scope_transfer_target_mismatch", + "stale_attempt_fence", + "stale_scope_authority", + "standalone_takeover_ambiguous_work", + "standalone_takeover_requires_fresh_scope", + "terminal_root", + "timer_admission_rejected", + "timer_already_fired", + "timer_cancelled", + "timer_capability_mismatch", + "timer_clock_unavailable", + "timer_deadline_overflow", + "timer_event_conflict", + "timer_fire_in_progress", + "timer_id_conflict", + "timer_not_due", + "timer_not_found", + "timer_operation_conflict", + "timer_revision_conflict", + "timer_stale_fence", + "timer_worker_mismatch", + "tombstoned_root", + "unauthorized_scope", + "unauthorized_timer_scope", + "unsupported_archive_format", + "unsupported_archive_participant_format", + "unsupported_archive_participant_schema_version", + "unsupported_archive_schema_version", + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "unsupported_recovery_format", + "unsupported_recovery_version", + "unsupported_timer_protocol", + "unsupported_timer_protocol_version", + "wrong_root" + ] + }, + "timer_result": { + "allOf": [ + { + "$ref": "timer-helper-operation-v1.schema.json#/$defs/result" + }, + { + "properties": { + "status": { + "const": "rejected" + } + } + } + ] + }, + "authority_result": { + "allOf": [ + { + "$ref": "host-authority-operation-v1.schema.json#/$defs/result" + }, + { + "properties": { + "status": { + "const": "rejected" + } + } + } + ] + }, + "effect_result": { + "allOf": [ + { + "$ref": "effect-result-response-v1.schema.json" + }, + { + "properties": { + "status": { + "const": "rejected" + } + } + } + ] + }, + "cancellation_result": { + "allOf": [ + { + "$ref": "effect-cancellation-response-v1.schema.json" + }, + { + "properties": { + "status": { + "const": "rejected" + } + } + } + ] + }, + "recovery_result": { + "allOf": [ + { + "$ref": "recovery-operation-v1.schema.json" + }, + { + "properties": { + "result_format": { + "const": "determa.recovery_result" + }, + "status": { + "const": "refused" + } + } + } + ] + }, + "archive_result": { + "allOf": [ + { + "$ref": "archive-result-v1.schema.json" + }, + { + "properties": { + "status": { + "const": "refused" + } + } + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "operation": { + "const": "capabilities" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired" + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "create" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired", + "creation_id_conflict", + "projection_not_lossless", + "checkpoint_revision_conflict", + "runtime_provider_unavailable", + "language_provider_unavailable" + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "admit" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired", + "event_id_conflict", + "checkpoint_revision_conflict", + "event_disposition_missing", + "portable_value_required", + "malformed_delivery", + "wrong_root", + "duplicate_event_id_in_batch", + "terminal_root", + "tombstoned_root", + "invalid_delivery_mode", + "invalid_delivery_source", + "invalid_instance_target", + "inactive_component_target", + "invalid_event", + "invalid_payload", + "invalid_correlation", + "delivery_digest_mismatch" + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "process" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired", + "checkpoint_revision_conflict", + "runtime_provider_output_invalid", + "event_disposition_missing" + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "read" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired", + "checkpoint_revision_conflict" + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "inspect" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired", + "checkpoint_revision_conflict", + "invalid_inspection_request", + "inspection_capability_unavailable", + "inspection_guard_failure", + "inspection_limit_exceeded" + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "receipt" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired" + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "effect_result" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired", + "checkpoint_revision_conflict", + "scope_not_active", + "stale_scope_authority", + "effect_not_outstanding", + "effect_result_conflict", + "stale_attempt_fence", + "runtime_provider_output_invalid", + "portable_value_required" + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "cancel_effect" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired", + "checkpoint_revision_conflict", + "scope_not_active", + "stale_scope_authority", + "effect_not_outstanding", + "effect_result_conflict" + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "timer_command" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired", + "delivery_ambiguous", + "invalid_timer_request", + "invalid_timer_time", + "timer_admission_rejected", + "timer_already_fired", + "timer_cancelled", + "timer_capability_mismatch", + "timer_clock_unavailable", + "timer_deadline_overflow", + "timer_event_conflict", + "timer_fire_in_progress", + "timer_id_conflict", + "timer_not_due", + "timer_not_found", + "timer_operation_conflict", + "timer_revision_conflict", + "timer_stale_fence", + "timer_worker_mismatch", + "unauthorized_timer_scope", + "unsupported_timer_protocol", + "unsupported_timer_protocol_version" + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "scope_operation" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired", + "archive_capability_mismatch", + "archive_consistency_unavailable", + "archive_digest_mismatch", + "archive_host_journal_inventory_mismatch", + "archive_manifest_mismatch", + "archive_participant_contract_mismatch", + "archive_payload_digest_mismatch", + "archive_selection_invalid", + "archive_source_profile_mismatch", + "archive_source_provenance_mismatch", + "archive_staging_failure", + "clone_has_unresolved_work", + "clone_isolation_unproven", + "effect_not_outstanding", + "effect_outcome_ambiguous", + "host_capability_mismatch", + "invalid_archive", + "invalid_archive_attachment", + "invalid_archive_checkpoint", + "invalid_archive_participant", + "invalid_recovery_request", + "missing_required_artifact", + "missing_required_provider", + "scope_archive_digest_mismatch", + "scope_archive_incomplete", + "scope_destination_not_empty", + "scope_fence_unproven", + "scope_generation_conflict", + "scope_not_active", + "scope_operation_conflict", + "scope_restore_requires_quarantine", + "scope_transaction_in_doubt", + "scope_transfer_consumed", + "scope_transfer_target_mismatch", + "stale_attempt_fence", + "stale_scope_authority", + "standalone_takeover_ambiguous_work", + "standalone_takeover_requires_fresh_scope", + "unauthorized_scope", + "unsupported_archive_format", + "unsupported_archive_participant_format", + "unsupported_archive_participant_schema_version", + "unsupported_archive_schema_version", + "unsupported_recovery_format", + "unsupported_recovery_version" + ] + } + } + } + }, + { + "if": { + "properties": { + "code": { + "enum": [ + "unsupported_archive_format", + "unsupported_archive_schema_version", + "unsupported_archive_participant_format", + "unsupported_archive_participant_schema_version", + "invalid_archive", + "archive_digest_mismatch", + "invalid_archive_checkpoint", + "invalid_archive_attachment", + "invalid_archive_participant", + "archive_payload_digest_mismatch", + "missing_required_artifact", + "missing_required_provider", + "archive_capability_mismatch", + "archive_consistency_unavailable", + "archive_staging_failure", + "archive_source_profile_mismatch", + "archive_participant_contract_mismatch", + "archive_source_provenance_mismatch", + "archive_selection_invalid", + "archive_host_journal_inventory_mismatch", + "archive_manifest_mismatch" + ] + } + } + }, + "then": { + "required": [ + "archive_result" + ] + } + }, + { + "if": { + "required": [ + "archive_result" + ] + }, + "then": { + "properties": { + "operation": { + "const": "scope_operation" + }, + "code": { + "enum": [ + "unsupported_archive_format", + "unsupported_archive_schema_version", + "unsupported_archive_participant_format", + "unsupported_archive_participant_schema_version", + "invalid_archive", + "archive_digest_mismatch", + "invalid_archive_checkpoint", + "invalid_archive_attachment", + "invalid_archive_participant", + "archive_payload_digest_mismatch", + "missing_required_artifact", + "missing_required_provider", + "archive_capability_mismatch", + "archive_consistency_unavailable", + "archive_staging_failure", + "archive_source_profile_mismatch", + "archive_participant_contract_mismatch", + "archive_source_provenance_mismatch", + "archive_selection_invalid", + "archive_host_journal_inventory_mismatch", + "archive_manifest_mismatch" + ] + } + } + } + }, + { + "if": { + "required": [ + "recovery_result" + ] + }, + "then": { + "properties": { + "operation": { + "const": "scope_operation" + }, + "code": { + "enum": [ + "clone_has_unresolved_work", + "clone_isolation_unproven", + "effect_not_outstanding", + "effect_outcome_ambiguous", + "host_capability_mismatch", + "invalid_recovery_request", + "scope_archive_digest_mismatch", + "scope_archive_incomplete", + "scope_destination_not_empty", + "scope_fence_unproven", + "scope_generation_conflict", + "scope_not_active", + "scope_operation_conflict", + "scope_restore_requires_quarantine", + "scope_transaction_in_doubt", + "scope_transfer_consumed", + "scope_transfer_target_mismatch", + "stale_attempt_fence", + "stale_scope_authority", + "standalone_takeover_ambiguous_work", + "standalone_takeover_requires_fresh_scope", + "unauthorized_scope", + "unsupported_recovery_format", + "unsupported_recovery_version" + ] + } + } + } + }, + { + "if": { + "required": [ + "timer_result" + ] + }, + "then": { + "properties": { + "operation": { + "const": "timer_command" + }, + "code": { + "enum": [ + "invalid_timer_request", + "unsupported_timer_protocol", + "unsupported_timer_protocol_version", + "unauthorized_timer_scope", + "invalid_timer_time", + "timer_deadline_overflow", + "timer_clock_unavailable", + "timer_operation_conflict", + "timer_id_conflict", + "timer_not_found", + "timer_revision_conflict", + "timer_not_due", + "timer_fire_in_progress", + "timer_already_fired", + "timer_cancelled", + "timer_stale_fence", + "timer_worker_mismatch", + "timer_event_conflict", + "timer_admission_rejected", + "delivery_ambiguous", + "timer_capability_mismatch" + ] + } + } + } + }, + { + "if": { + "required": [ + "authority_result" + ] + }, + "then": { + "properties": { + "operation": { + "const": "scope_operation" + } + } + } + }, + { + "if": { + "required": [ + "effect_result" + ] + }, + "then": { + "properties": { + "operation": { + "const": "effect_result" + } + } + } + }, + { + "if": { + "required": [ + "cancellation_result" + ] + }, + "then": { + "properties": { + "operation": { + "const": "cancel_effect" + } + } + } + } + ] + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "status": { + "const": "committed" + } + } + }, + "then": { + "properties": { + "value": { + "oneOf": [ + { + "$ref": "#/$defs/capabilitiesValue" + }, + { + "$ref": "#/$defs/createValue" + }, + { + "$ref": "#/$defs/admitValue" + }, + { + "$ref": "#/$defs/processValue" + }, + { + "$ref": "#/$defs/readValue" + }, + { + "$ref": "#/$defs/inspectValue" + }, + { + "$ref": "#/$defs/receiptValue" + }, + { + "$ref": "#/$defs/effect_resultValue" + }, + { + "$ref": "#/$defs/cancel_effectValue" + }, + { + "$ref": "#/$defs/timer_commandValue" + }, + { + "$ref": "#/$defs/scope_operationValue" + } + ] + }, + "error": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "status": { + "const": "pending" + } + } + }, + "then": { + "properties": { + "receipt": { + "allOf": [ + { + "type": "object", + "required": [ + "scope_binding_identity", + "operation_id", + "request_digest", + "receipt_kind", + "evidence_digest", + "acceptance_receipt" + ], + "additionalProperties": false, + "properties": { + "scope_binding_identity": { + "type": "string", + "minLength": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "receipt_kind": { + "enum": [ + "committed", + "accepted" + ] + }, + "evidence_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "acceptance_receipt": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/acceptanceReceipt" + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "receipt_kind": { + "const": "accepted" + } + } + }, + "then": { + "properties": { + "acceptance_receipt": { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/acceptanceReceipt" + } + } + }, + "else": { + "properties": { + "acceptance_receipt": { + "type": "null" + } + } + } + } + ] + }, + { + "properties": { + "receipt_kind": { + "const": "accepted" + } + } + } + ] + }, + "value": { + "type": "null" + }, + "error": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "status": { + "const": "rejected" + } + } + }, + "then": { + "properties": { + "receipt": { + "type": "null" + }, + "value": { + "type": "null" + }, + "error": { + "type": "object", + "required": [ + "operation", + "code" + ], + "additionalProperties": false, + "properties": { + "operation": { + "enum": [ + "capabilities", + "create", + "admit", + "process", + "read", + "inspect", + "receipt", + "effect_result", + "cancel_effect", + "timer_command", + "scope_operation" + ] + }, + "code": { + "enum": [ + "archive_capability_mismatch", + "archive_consistency_unavailable", + "archive_digest_mismatch", + "archive_host_journal_inventory_mismatch", + "archive_manifest_mismatch", + "archive_participant_contract_mismatch", + "archive_payload_digest_mismatch", + "archive_selection_invalid", + "archive_source_profile_mismatch", + "archive_source_provenance_mismatch", + "archive_staging_failure", + "binding_unavailable", + "checkpoint_revision_conflict", + "clone_has_unresolved_work", + "clone_isolation_unproven", + "creation_id_conflict", + "delivery_ambiguous", + "delivery_digest_mismatch", + "duplicate_event_id_in_batch", + "effect_not_outstanding", + "effect_outcome_ambiguous", + "effect_result_conflict", + "event_disposition_missing", + "event_id_conflict", + "host_capability_mismatch", + "inactive_component_target", + "inspection_capability_unavailable", + "inspection_guard_failure", + "inspection_limit_exceeded", + "invalid_archive", + "invalid_archive_attachment", + "invalid_archive_checkpoint", + "invalid_archive_participant", + "invalid_correlation", + "invalid_delivery_mode", + "invalid_delivery_source", + "invalid_event", + "invalid_host_request", + "invalid_inspection_request", + "invalid_instance_target", + "invalid_payload", + "invalid_recovery_request", + "invalid_timer_request", + "invalid_timer_time", + "language_provider_unavailable", + "malformed_delivery", + "missing_required_artifact", + "missing_required_provider", + "operation_id_conflict", + "outcome_unknown", + "portable_value_required", + "projection_not_lossless", + "replay_evidence_expired", + "runtime_provider_output_invalid", + "runtime_provider_unavailable", + "scope_archive_digest_mismatch", + "scope_archive_incomplete", + "scope_destination_not_empty", + "scope_fence_unproven", + "scope_generation_conflict", + "scope_not_active", + "scope_operation_conflict", + "scope_restore_requires_quarantine", + "scope_transaction_in_doubt", + "scope_transfer_consumed", + "scope_transfer_target_mismatch", + "stale_attempt_fence", + "stale_scope_authority", + "standalone_takeover_ambiguous_work", + "standalone_takeover_requires_fresh_scope", + "terminal_root", + "timer_admission_rejected", + "timer_already_fired", + "timer_cancelled", + "timer_capability_mismatch", + "timer_clock_unavailable", + "timer_deadline_overflow", + "timer_event_conflict", + "timer_fire_in_progress", + "timer_id_conflict", + "timer_not_due", + "timer_not_found", + "timer_operation_conflict", + "timer_revision_conflict", + "timer_stale_fence", + "timer_worker_mismatch", + "tombstoned_root", + "unauthorized_scope", + "unauthorized_timer_scope", + "unsupported_archive_format", + "unsupported_archive_participant_format", + "unsupported_archive_participant_schema_version", + "unsupported_archive_schema_version", + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "unsupported_recovery_format", + "unsupported_recovery_version", + "unsupported_timer_protocol", + "unsupported_timer_protocol_version", + "wrong_root" + ] + }, + "timer_result": { + "allOf": [ + { + "$ref": "timer-helper-operation-v1.schema.json#/$defs/result" + }, + { + "properties": { + "status": { + "const": "rejected" + } + } + } + ] + }, + "authority_result": { + "allOf": [ + { + "$ref": "host-authority-operation-v1.schema.json#/$defs/result" + }, + { + "properties": { + "status": { + "const": "rejected" + } + } + } + ] + }, + "effect_result": { + "allOf": [ + { + "$ref": "effect-result-response-v1.schema.json" + }, + { + "properties": { + "status": { + "const": "rejected" + } + } + } + ] + }, + "cancellation_result": { + "allOf": [ + { + "$ref": "effect-cancellation-response-v1.schema.json" + }, + { + "properties": { + "status": { + "const": "rejected" + } + } + } + ] + }, + "recovery_result": { + "allOf": [ + { + "$ref": "recovery-operation-v1.schema.json" + }, + { + "properties": { + "result_format": { + "const": "determa.recovery_result" + }, + "status": { + "const": "refused" + } + } + } + ] + }, + "archive_result": { + "allOf": [ + { + "$ref": "archive-result-v1.schema.json" + }, + { + "properties": { + "status": { + "const": "refused" + } + } + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "operation": { + "const": "capabilities" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired" + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "create" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired", + "creation_id_conflict", + "projection_not_lossless", + "checkpoint_revision_conflict", + "runtime_provider_unavailable", + "language_provider_unavailable" + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "admit" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired", + "event_id_conflict", + "checkpoint_revision_conflict", + "event_disposition_missing", + "portable_value_required", + "malformed_delivery", + "wrong_root", + "duplicate_event_id_in_batch", + "terminal_root", + "tombstoned_root", + "invalid_delivery_mode", + "invalid_delivery_source", + "invalid_instance_target", + "inactive_component_target", + "invalid_event", + "invalid_payload", + "invalid_correlation", + "delivery_digest_mismatch" + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "process" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired", + "checkpoint_revision_conflict", + "runtime_provider_output_invalid", + "event_disposition_missing" + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "read" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired", + "checkpoint_revision_conflict" + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "inspect" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired", + "checkpoint_revision_conflict", + "invalid_inspection_request", + "inspection_capability_unavailable", + "inspection_guard_failure", + "inspection_limit_exceeded" + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "receipt" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired" + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "effect_result" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired", + "checkpoint_revision_conflict", + "scope_not_active", + "stale_scope_authority", + "effect_not_outstanding", + "effect_result_conflict", + "stale_attempt_fence", + "runtime_provider_output_invalid", + "portable_value_required" + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "cancel_effect" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired", + "checkpoint_revision_conflict", + "scope_not_active", + "stale_scope_authority", + "effect_not_outstanding", + "effect_result_conflict" + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "timer_command" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired", + "delivery_ambiguous", + "invalid_timer_request", + "invalid_timer_time", + "timer_admission_rejected", + "timer_already_fired", + "timer_cancelled", + "timer_capability_mismatch", + "timer_clock_unavailable", + "timer_deadline_overflow", + "timer_event_conflict", + "timer_fire_in_progress", + "timer_id_conflict", + "timer_not_due", + "timer_not_found", + "timer_operation_conflict", + "timer_revision_conflict", + "timer_stale_fence", + "timer_worker_mismatch", + "unauthorized_timer_scope", + "unsupported_timer_protocol", + "unsupported_timer_protocol_version" + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "scope_operation" + } + } + }, + "then": { + "properties": { + "code": { + "enum": [ + "unsupported_host_protocol", + "unsupported_host_protocol_version", + "invalid_host_request", + "host_capability_mismatch", + "unauthorized_scope", + "operation_id_conflict", + "binding_unavailable", + "outcome_unknown", + "replay_evidence_expired", + "archive_capability_mismatch", + "archive_consistency_unavailable", + "archive_digest_mismatch", + "archive_host_journal_inventory_mismatch", + "archive_manifest_mismatch", + "archive_participant_contract_mismatch", + "archive_payload_digest_mismatch", + "archive_selection_invalid", + "archive_source_profile_mismatch", + "archive_source_provenance_mismatch", + "archive_staging_failure", + "clone_has_unresolved_work", + "clone_isolation_unproven", + "effect_not_outstanding", + "effect_outcome_ambiguous", + "host_capability_mismatch", + "invalid_archive", + "invalid_archive_attachment", + "invalid_archive_checkpoint", + "invalid_archive_participant", + "invalid_recovery_request", + "missing_required_artifact", + "missing_required_provider", + "scope_archive_digest_mismatch", + "scope_archive_incomplete", + "scope_destination_not_empty", + "scope_fence_unproven", + "scope_generation_conflict", + "scope_not_active", + "scope_operation_conflict", + "scope_restore_requires_quarantine", + "scope_transaction_in_doubt", + "scope_transfer_consumed", + "scope_transfer_target_mismatch", + "stale_attempt_fence", + "stale_scope_authority", + "standalone_takeover_ambiguous_work", + "standalone_takeover_requires_fresh_scope", + "unauthorized_scope", + "unsupported_archive_format", + "unsupported_archive_participant_format", + "unsupported_archive_participant_schema_version", + "unsupported_archive_schema_version", + "unsupported_recovery_format", + "unsupported_recovery_version" + ] + } + } + } + }, + { + "if": { + "properties": { + "code": { + "enum": [ + "unsupported_archive_format", + "unsupported_archive_schema_version", + "unsupported_archive_participant_format", + "unsupported_archive_participant_schema_version", + "invalid_archive", + "archive_digest_mismatch", + "invalid_archive_checkpoint", + "invalid_archive_attachment", + "invalid_archive_participant", + "archive_payload_digest_mismatch", + "missing_required_artifact", + "missing_required_provider", + "archive_capability_mismatch", + "archive_consistency_unavailable", + "archive_staging_failure", + "archive_source_profile_mismatch", + "archive_participant_contract_mismatch", + "archive_source_provenance_mismatch", + "archive_selection_invalid", + "archive_host_journal_inventory_mismatch", + "archive_manifest_mismatch" + ] + } + } + }, + "then": { + "required": [ + "archive_result" + ] + } + }, + { + "if": { + "required": [ + "archive_result" + ] + }, + "then": { + "properties": { + "operation": { + "const": "scope_operation" + }, + "code": { + "enum": [ + "unsupported_archive_format", + "unsupported_archive_schema_version", + "unsupported_archive_participant_format", + "unsupported_archive_participant_schema_version", + "invalid_archive", + "archive_digest_mismatch", + "invalid_archive_checkpoint", + "invalid_archive_attachment", + "invalid_archive_participant", + "archive_payload_digest_mismatch", + "missing_required_artifact", + "missing_required_provider", + "archive_capability_mismatch", + "archive_consistency_unavailable", + "archive_staging_failure", + "archive_source_profile_mismatch", + "archive_participant_contract_mismatch", + "archive_source_provenance_mismatch", + "archive_selection_invalid", + "archive_host_journal_inventory_mismatch", + "archive_manifest_mismatch" + ] + } + } + } + }, + { + "if": { + "required": [ + "recovery_result" + ] + }, + "then": { + "properties": { + "operation": { + "const": "scope_operation" + }, + "code": { + "enum": [ + "clone_has_unresolved_work", + "clone_isolation_unproven", + "effect_not_outstanding", + "effect_outcome_ambiguous", + "host_capability_mismatch", + "invalid_recovery_request", + "scope_archive_digest_mismatch", + "scope_archive_incomplete", + "scope_destination_not_empty", + "scope_fence_unproven", + "scope_generation_conflict", + "scope_not_active", + "scope_operation_conflict", + "scope_restore_requires_quarantine", + "scope_transaction_in_doubt", + "scope_transfer_consumed", + "scope_transfer_target_mismatch", + "stale_attempt_fence", + "stale_scope_authority", + "standalone_takeover_ambiguous_work", + "standalone_takeover_requires_fresh_scope", + "unauthorized_scope", + "unsupported_recovery_format", + "unsupported_recovery_version" + ] + } + } + } + }, + { + "if": { + "required": [ + "timer_result" + ] + }, + "then": { + "properties": { + "operation": { + "const": "timer_command" + }, + "code": { + "enum": [ + "invalid_timer_request", + "unsupported_timer_protocol", + "unsupported_timer_protocol_version", + "unauthorized_timer_scope", + "invalid_timer_time", + "timer_deadline_overflow", + "timer_clock_unavailable", + "timer_operation_conflict", + "timer_id_conflict", + "timer_not_found", + "timer_revision_conflict", + "timer_not_due", + "timer_fire_in_progress", + "timer_already_fired", + "timer_cancelled", + "timer_stale_fence", + "timer_worker_mismatch", + "timer_event_conflict", + "timer_admission_rejected", + "delivery_ambiguous", + "timer_capability_mismatch" + ] + } + } + } + }, + { + "if": { + "required": [ + "authority_result" + ] + }, + "then": { + "properties": { + "operation": { + "const": "scope_operation" + } + } + } + }, + { + "if": { + "required": [ + "effect_result" + ] + }, + "then": { + "properties": { + "operation": { + "const": "effect_result" + } + } + } + }, + { + "if": { + "required": [ + "cancellation_result" + ] + }, + "then": { + "properties": { + "operation": { + "const": "cancel_effect" + } + } + } + } + ] + } + } + } + }, + { + "if": { + "properties": { + "status": { + "const": "committed" + } + } + }, + "then": { + "properties": { + "receipt": { + "oneOf": [ + { + "type": "null" + }, + { + "allOf": [ + { + "type": "object", + "required": [ + "scope_binding_identity", + "operation_id", + "request_digest", + "receipt_kind", + "evidence_digest", + "acceptance_receipt" + ], + "additionalProperties": false, + "properties": { + "scope_binding_identity": { + "type": "string", + "minLength": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "receipt_kind": { + "enum": [ + "committed", + "accepted" + ] + }, + "evidence_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "acceptance_receipt": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/acceptanceReceipt" + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "receipt_kind": { + "const": "accepted" + } + } + }, + "then": { + "properties": { + "acceptance_receipt": { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/acceptanceReceipt" + } + } + }, + "else": { + "properties": { + "acceptance_receipt": { + "type": "null" + } + } + } + } + ] + }, + { + "properties": { + "receipt_kind": { + "const": "committed" + } + } + } + ] + } + ] + } + } + } + }, + { + "if": { + "properties": { + "status": { + "const": "committed" + }, + "value": { + "properties": { + "operation": { + "enum": [ + "create", + "admit", + "effect_result", + "cancel_effect" + ] + } + } + } + } + }, + "then": { + "properties": { + "receipt": { + "type": "object", + "required": [ + "scope_binding_identity", + "operation_id", + "request_digest", + "receipt_kind", + "evidence_digest", + "acceptance_receipt" + ], + "additionalProperties": false, + "properties": { + "scope_binding_identity": { + "type": "string", + "minLength": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "receipt_kind": { + "enum": [ + "committed", + "accepted" + ] + }, + "evidence_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "acceptance_receipt": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/acceptanceReceipt" + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "receipt_kind": { + "const": "accepted" + } + } + }, + "then": { + "properties": { + "acceptance_receipt": { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/acceptanceReceipt" + } + } + }, + "else": { + "properties": { + "acceptance_receipt": { + "type": "null" + } + } + } + } + ] + } + } + } + }, + { + "if": { + "properties": { + "status": { + "const": "committed" + }, + "value": { + "properties": { + "operation": { + "const": "scope_operation" + }, + "result": { + "properties": { + "action": { + "enum": [ + "archive_export", + "archive_import" + ] + } + } + } + } + } + } + }, + "then": { + "properties": { + "receipt": { + "type": "object", + "required": [ + "scope_binding_identity", + "operation_id", + "request_digest", + "receipt_kind", + "evidence_digest", + "acceptance_receipt" + ], + "additionalProperties": false, + "properties": { + "scope_binding_identity": { + "type": "string", + "minLength": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "receipt_kind": { + "enum": [ + "committed", + "accepted" + ] + }, + "evidence_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "acceptance_receipt": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/acceptanceReceipt" + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "receipt_kind": { + "const": "accepted" + } + } + }, + "then": { + "properties": { + "acceptance_receipt": { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/acceptanceReceipt" + } + } + }, + "else": { + "properties": { + "acceptance_receipt": { + "type": "null" + } + } + } + } + ] + } + } + } + }, + { + "if": { + "properties": { + "status": { + "const": "committed" + }, + "value": { + "properties": { + "operation": { + "const": "scope_operation" + }, + "result": { + "properties": { + "action": { + "const": "recovery" + }, + "recovery_result": { + "properties": { + "operation": { + "enum": [ + "strict_restore", + "standalone_takeover", + "resume_takeover", + "reconcile_work", + "clone_scope", + "activate_clone", + "prepare_transfer", + "stage_transfer", + "commit_transfer", + "activate_import" + ] + } + } + } + } + } + } + } + } + }, + "then": { + "properties": { + "receipt": { + "type": "object", + "required": [ + "scope_binding_identity", + "operation_id", + "request_digest", + "receipt_kind", + "evidence_digest", + "acceptance_receipt" + ], + "additionalProperties": false, + "properties": { + "scope_binding_identity": { + "type": "string", + "minLength": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "receipt_kind": { + "enum": [ + "committed", + "accepted" + ] + }, + "evidence_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "acceptance_receipt": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/acceptanceReceipt" + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "receipt_kind": { + "const": "accepted" + } + } + }, + "then": { + "properties": { + "acceptance_receipt": { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/acceptanceReceipt" + } + } + }, + "else": { + "properties": { + "acceptance_receipt": { + "type": "null" + } + } + } + } + ] + } + } + } + }, + { + "if": { + "properties": { + "status": { + "const": "committed" + }, + "value": { + "properties": { + "operation": { + "const": "timer_command" + }, + "result": { + "properties": { + "timer_result": { + "properties": { + "operation": { + "enum": [ + "schedule", + "cancel", + "claim_fire", + "complete_fire" + ] + } + } + } + } + } + } + } + } + }, + "then": { + "properties": { + "receipt": { + "type": "object", + "required": [ + "scope_binding_identity", + "operation_id", + "request_digest", + "receipt_kind", + "evidence_digest", + "acceptance_receipt" + ], + "additionalProperties": false, + "properties": { + "scope_binding_identity": { + "type": "string", + "minLength": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "receipt_kind": { + "enum": [ + "committed", + "accepted" + ] + }, + "evidence_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "acceptance_receipt": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/acceptanceReceipt" + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "receipt_kind": { + "const": "accepted" + } + } + }, + "then": { + "properties": { + "acceptance_receipt": { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/acceptanceReceipt" + } + } + }, + "else": { + "properties": { + "acceptance_receipt": { + "type": "null" + } + } + } + } + ] + } + } + } + }, + { + "if": { + "properties": { + "status": { + "const": "committed" + }, + "value": { + "properties": { + "operation": { + "const": "process" + }, + "result": { + "properties": { + "core_result": { + "properties": { + "disposition": { + "enum": [ + "handled", + "deferred", + "unhandled", + "faulted" + ] + } + } + } + } + } + } + } + } + }, + "then": { + "properties": { + "receipt": { + "type": "object", + "required": [ + "scope_binding_identity", + "operation_id", + "request_digest", + "receipt_kind", + "evidence_digest", + "acceptance_receipt" + ], + "additionalProperties": false, + "properties": { + "scope_binding_identity": { + "type": "string", + "minLength": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "receipt_kind": { + "enum": [ + "committed", + "accepted" + ] + }, + "evidence_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "acceptance_receipt": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/acceptanceReceipt" + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "receipt_kind": { + "const": "accepted" + } + } + }, + "then": { + "properties": { + "acceptance_receipt": { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/acceptanceReceipt" + } + } + }, + "else": { + "properties": { + "acceptance_receipt": { + "type": "null" + } + } + } + } + ] + } + } + } + }, + { + "if": { + "properties": { + "status": { + "const": "committed" + }, + "value": { + "properties": { + "operation": { + "const": "scope_operation" + }, + "result": { + "properties": { + "action": { + "const": "authority" + }, + "authority_result": { + "properties": { + "operation": { + "enum": [ + "guarded_commit", + "freeze_scope", + "fence_worker", + "prove_retirement" + ] + } + } + } + } + } + } + } + } + }, + "then": { + "properties": { + "receipt": { + "type": "object", + "required": [ + "scope_binding_identity", + "operation_id", + "request_digest", + "receipt_kind", + "evidence_digest", + "acceptance_receipt" + ], + "additionalProperties": false, + "properties": { + "scope_binding_identity": { + "type": "string", + "minLength": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "receipt_kind": { + "enum": [ + "committed", + "accepted" + ] + }, + "evidence_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "acceptance_receipt": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/acceptanceReceipt" + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "receipt_kind": { + "const": "accepted" + } + } + }, + "then": { + "properties": { + "acceptance_receipt": { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/acceptanceReceipt" + } + } + }, + "else": { + "properties": { + "acceptance_receipt": { + "type": "null" + } + } + } + } + ] + } + } + } + } + ], + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://determa.dev/state/schema/public-host-response-v1.schema.json", + "title": "Determa State execution host response version 1", + "$defs": { + "capabilitiesValue": { + "type": "object", + "required": [ + "operation", + "result" + ], + "additionalProperties": false, + "properties": { + "operation": { + "const": "capabilities" + }, + "result": { + "type": "object", + "required": [ + "scope_binding_identity", + "supported_operations", + "supported_scope_actions", + "supported_determa_capabilities", + "supported_timer_commands", + "profile_digest", + "extension_reports", + "authority_profile", + "guarantees" + ], + "additionalProperties": false, + "properties": { + "scope_binding_identity": { + "type": "string", + "minLength": 1 + }, + "supported_operations": { + "type": "array", + "uniqueItems": true, + "items": { + "enum": [ + "capabilities", + "create", + "admit", + "process", + "read", + "inspect", + "receipt", + "effect_result", + "cancel_effect", + "timer_command", + "scope_operation" + ] + } + }, + "supported_scope_actions": { + "type": "array", + "uniqueItems": true, + "items": { + "enum": [ + "authority", + "archive_export", + "archive_import", + "recovery" + ] + } + }, + "supported_determa_capabilities": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "archive-v1.schema.json#/$defs/capability" + } + }, + "supported_timer_commands": { + "type": "array", + "uniqueItems": true, + "items": { + "enum": [ + "schedule", + "cancel", + "claim_fire", + "complete_fire", + "read_timer" + ] + } + }, + "profile_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "extension_reports": { + "type": "array", + "items": { + "$ref": "extension-capability-report-v1.schema.json" + } + }, + "authority_profile": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "host-authority-profile-report-v1.schema.json" + } + ] + }, + "guarantees": { + "type": "object", + "required": [ + "inspection_structural", + "inspection_semantic", + "retained_history", + "saved_response_replay", + "deterministic_reexecution" + ], + "additionalProperties": false, + "properties": { + "inspection_structural": { + "type": "boolean" + }, + "inspection_semantic": { + "type": "boolean" + }, + "retained_history": { + "type": "boolean" + }, + "saved_response_replay": { + "type": "boolean" + }, + "deterministic_reexecution": { + "type": "boolean" + } + } + } + } + } + } + }, + "createValue": { + "type": "object", + "required": [ + "operation", + "result" + ], + "additionalProperties": false, + "properties": { + "operation": { + "const": "create" + }, + "result": { + "type": "object", + "required": [ + "checkpoint", + "creation_receipt", + "status", + "emissions", + "lifecycle_dispositions", + "fault" + ], + "additionalProperties": false, + "properties": { + "checkpoint": { + "$ref": "execution-checkpoint-v1.schema.json" + }, + "creation_receipt": { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/creationReceipt" + }, + "status": { + "enum": [ + "running", + "completed", + "faulted" + ] + }, + "emissions": { + "type": "array", + "items": { + "$ref": "core-step-result-v1.schema.json#/$defs/emission" + } + }, + "lifecycle_dispositions": { + "type": "array", + "items": { + "$ref": "core-step-result-v1.schema.json#/$defs/lifecycleDisposition" + } + }, + "fault": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "aggregate-state-v1.schema.json#/$defs/fault" + } + ] + } + } + } + } + }, + "admitValue": { + "type": "object", + "required": [ + "operation", + "result" + ], + "additionalProperties": false, + "properties": { + "operation": { + "const": "admit" + }, + "result": { + "type": "object", + "required": [ + "checkpoint", + "acceptance_receipts", + "status", + "accepted" + ], + "additionalProperties": false, + "properties": { + "checkpoint": { + "$ref": "execution-checkpoint-v1.schema.json" + }, + "acceptance_receipts": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/acceptanceReceipt" + } + }, + "status": { + "enum": [ + "running", + "completed", + "faulted" + ] + }, + "accepted": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "aggregate-state-v1.schema.json#/$defs/envelope" + } + } + } + } + } + }, + "processValue": { + "type": "object", + "required": [ + "operation", + "result" + ], + "additionalProperties": false, + "properties": { + "operation": { + "const": "process" + }, + "result": { + "type": "object", + "required": [ + "checkpoint", + "core_result", + "terminal_receipt" + ], + "additionalProperties": false, + "properties": { + "checkpoint": { + "$ref": "execution-checkpoint-v1.schema.json" + }, + "core_result": { + "$ref": "core-step-result-v1.schema.json" + }, + "terminal_receipt": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "execution-checkpoint-v1.schema.json#/$defs/terminalEventReceipt" + } + ] + } + } + } + } + }, + "readValue": { + "type": "object", + "required": [ + "operation", + "result" + ], + "additionalProperties": false, + "properties": { + "operation": { + "const": "read" + }, + "result": { + "type": "object", + "required": [ + "checkpoint", + "observed_checkpoint_digest" + ], + "additionalProperties": false, + "properties": { + "checkpoint": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "execution-checkpoint-v1.schema.json" + } + ] + }, + "observed_checkpoint_digest": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + ] + } + } + } + } + }, + "inspectValue": { + "type": "object", + "required": [ + "operation", + "result" + ], + "additionalProperties": false, + "properties": { + "operation": { + "const": "inspect" + }, + "result": { + "type": "object", + "required": [ + "outcome", + "observed_aggregate_state_digest" + ], + "additionalProperties": false, + "properties": { + "outcome": { + "$ref": "inspection-v1.schema.json#/$defs/outcome" + }, + "observed_aggregate_state_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + } + } + } + }, + "receiptValue": { + "type": "object", + "required": [ + "operation", + "result" + ], + "additionalProperties": false, + "properties": { + "operation": { + "const": "receipt" + }, + "result": { + "type": "object", + "required": [ + "saved_response", + "retention" + ], + "additionalProperties": false, + "properties": { + "saved_response": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "public-host-response-v1.schema.json" + } + ] + }, + "retention": { + "enum": [ + "retained", + "expired", + "never_recorded", + "unknown" + ] + } + } + } + } + }, + "effect_resultValue": { + "type": "object", + "required": [ + "operation", + "result" + ], + "additionalProperties": false, + "properties": { + "operation": { + "const": "effect_result" + }, + "result": { + "type": "object", + "required": [ + "result" + ], + "additionalProperties": false, + "properties": { + "result": { + "allOf": [ + { + "$ref": "effect-result-response-v1.schema.json" + }, + { + "properties": { + "status": { + "enum": [ + "committed", + "report_recorded" + ] + } + } + } + ] + } + } + } + } + }, + "cancel_effectValue": { + "type": "object", + "required": [ + "operation", + "result" + ], + "additionalProperties": false, + "properties": { + "operation": { + "const": "cancel_effect" + }, + "result": { + "type": "object", + "required": [ + "result" + ], + "additionalProperties": false, + "properties": { + "result": { + "allOf": [ + { + "$ref": "effect-cancellation-response-v1.schema.json" + }, + { + "properties": { + "status": { + "enum": [ + "committed", + "reconciliation_required" + ] + } + } + } + ] + } + } + } + } + }, + "timer_commandValue": { + "type": "object", + "required": [ + "operation", + "result" + ], + "additionalProperties": false, + "properties": { + "operation": { + "const": "timer_command" + }, + "result": { + "type": "object", + "required": [ + "timer_result" + ], + "additionalProperties": false, + "properties": { + "timer_result": { + "allOf": [ + { + "$ref": "timer-helper-operation-v1.schema.json#/$defs/result" + }, + { + "properties": { + "status": { + "const": "accepted" + } + } + } + ] + } + } + } + } + }, + "scope_operationValue": { + "type": "object", + "required": [ + "operation", + "result" + ], + "additionalProperties": false, + "properties": { + "operation": { + "const": "scope_operation" + }, + "result": { + "oneOf": [ + { + "type": "object", + "required": [ + "action", + "authority_result" + ], + "additionalProperties": false, + "properties": { + "action": { + "const": "authority" + }, + "authority_result": { + "allOf": [ + { + "$ref": "host-authority-operation-v1.schema.json#/$defs/result" + }, + { + "properties": { + "status": { + "const": "accepted" + } + } + } + ] + } + } + }, + { + "type": "object", + "required": [ + "action", + "archive_result", + "archive" + ], + "additionalProperties": false, + "properties": { + "action": { + "const": "archive_export" + }, + "archive_result": { + "allOf": [ + { + "$ref": "archive-result-v1.schema.json" + }, + { + "properties": { + "status": { + "const": "exported" + } + } + } + ] + }, + "archive": { + "$ref": "archive-v1.schema.json" + } + } + }, + { + "type": "object", + "required": [ + "action", + "archive_result" + ], + "additionalProperties": false, + "properties": { + "action": { + "const": "archive_import" + }, + "archive_result": { + "allOf": [ + { + "$ref": "archive-result-v1.schema.json" + }, + { + "properties": { + "status": { + "const": "staged" + } + } + } + ] + } + } + }, + { + "type": "object", + "required": [ + "action", + "recovery_result", + "recovery_record" + ], + "additionalProperties": false, + "properties": { + "action": { + "const": "recovery" + }, + "recovery_result": { + "allOf": [ + { + "$ref": "recovery-operation-v1.schema.json" + }, + { + "properties": { + "result_format": { + "const": "determa.recovery_result" + }, + "status": { + "const": "succeeded" + } + } + } + ] + }, + "recovery_record": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "recovery-record-v1.schema.json" + } + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "recovery_result": { + "properties": { + "record_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + } + } + } + }, + "then": { + "properties": { + "recovery_record": { + "$ref": "recovery-record-v1.schema.json" + } + } + }, + "else": { + "properties": { + "recovery_record": { + "type": "null" + } + } + } + } + ] + } + ] + } + } + } + } +} diff --git a/src/determa/state/data/recovery-operation-v1.schema.json b/src/determa/state/data/recovery-operation-v1.schema.json new file mode 100644 index 0000000..dfc1c3c --- /dev/null +++ b/src/determa/state/data/recovery-operation-v1.schema.json @@ -0,0 +1,1423 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://determa.dev/state/schema/recovery-operation-v1.schema.json", + "title": "Recovery operation request or complete result version 1", + "oneOf": [ + { + "type": "object", + "required": [ + "operation_format", + "operation_schema_version", + "operation", + "operation_id", + "request_digest", + "destination_scope_identity", + "expected_destination_generation", + "arguments" + ], + "properties": { + "operation_format": { + "const": "determa.recovery_operation" + }, + "operation_schema_version": { + "const": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "destination_scope_identity": { + "type": "string", + "minLength": 1 + }, + "expected_destination_generation": { + "oneOf": [ + { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + { + "type": "null" + } + ] + }, + "operation": { + "const": "strict_restore" + }, + "arguments": { + "type": "object", + "required": [ + "staging_identity", + "archive_digest", + "source", + "activate" + ], + "properties": { + "staging_identity": { + "type": "string", + "minLength": 1 + }, + "archive_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "source": { + "type": "object", + "required": [ + "logical_scope_identity", + "ownership_binding_digest", + "profile_digest", + "participant_contract_digest", + "archive_digest" + ], + "properties": { + "logical_scope_identity": { + "type": "string", + "minLength": 1 + }, + "ownership_binding_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "profile_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "participant_contract_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "archive_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + }, + "additionalProperties": false + }, + "activate": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "operation_format", + "operation_schema_version", + "operation", + "operation_id", + "request_digest", + "destination_scope_identity", + "expected_destination_generation", + "arguments" + ], + "properties": { + "operation_format": { + "const": "determa.recovery_operation" + }, + "operation_schema_version": { + "const": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "destination_scope_identity": { + "type": "string", + "minLength": 1 + }, + "expected_destination_generation": { + "oneOf": [ + { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + { + "type": "null" + } + ] + }, + "operation": { + "const": "standalone_takeover" + }, + "arguments": { + "type": "object", + "required": [ + "staging_identity", + "archive_digest", + "source", + "external_idempotency_namespace", + "acknowledge_old_owner_risk" + ], + "properties": { + "staging_identity": { + "type": "string", + "minLength": 1 + }, + "archive_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "source": { + "type": "object", + "required": [ + "logical_scope_identity", + "ownership_binding_digest", + "profile_digest", + "participant_contract_digest", + "archive_digest" + ], + "properties": { + "logical_scope_identity": { + "type": "string", + "minLength": 1 + }, + "ownership_binding_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "profile_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "participant_contract_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "archive_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + }, + "additionalProperties": false + }, + "external_idempotency_namespace": { + "type": "string", + "minLength": 1 + }, + "acknowledge_old_owner_risk": { + "const": true + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "operation_format", + "operation_schema_version", + "operation", + "operation_id", + "request_digest", + "destination_scope_identity", + "expected_destination_generation", + "arguments" + ], + "properties": { + "operation_format": { + "const": "determa.recovery_operation" + }, + "operation_schema_version": { + "const": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "destination_scope_identity": { + "type": "string", + "minLength": 1 + }, + "expected_destination_generation": { + "oneOf": [ + { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + { + "type": "null" + } + ] + }, + "operation": { + "const": "resume_takeover" + }, + "arguments": { + "type": "object", + "required": [ + "acknowledge_old_owner_risk", + "record_digest" + ], + "properties": { + "acknowledge_old_owner_risk": { + "const": true + }, + "record_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "operation_format", + "operation_schema_version", + "operation", + "operation_id", + "request_digest", + "destination_scope_identity", + "expected_destination_generation", + "arguments" + ], + "properties": { + "operation_format": { + "const": "determa.recovery_operation" + }, + "operation_schema_version": { + "const": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "destination_scope_identity": { + "type": "string", + "minLength": 1 + }, + "expected_destination_generation": { + "oneOf": [ + { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + { + "type": "null" + } + ] + }, + "operation": { + "const": "reconcile_work" + }, + "arguments": { + "type": "object", + "required": [ + "work_kind", + "work_identity", + "resolution", + "evidence_digest", + "operator_principal" + ], + "properties": { + "work_kind": { + "enum": [ + "effect", + "delivery", + "helper" + ] + }, + "work_identity": { + "type": "string", + "minLength": 1 + }, + "resolution": { + "enum": [ + "reconciled", + "abandoned", + "cancelled" + ] + }, + "evidence_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "operator_principal": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "operation_format", + "operation_schema_version", + "operation", + "operation_id", + "request_digest", + "destination_scope_identity", + "expected_destination_generation", + "arguments" + ], + "properties": { + "operation_format": { + "const": "determa.recovery_operation" + }, + "operation_schema_version": { + "const": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "destination_scope_identity": { + "type": "string", + "minLength": 1 + }, + "expected_destination_generation": { + "oneOf": [ + { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + { + "type": "null" + } + ] + }, + "operation": { + "const": "clone_scope" + }, + "arguments": { + "type": "object", + "required": [ + "staging_identity", + "archive_digest", + "source", + "external_idempotency_namespace" + ], + "properties": { + "staging_identity": { + "type": "string", + "minLength": 1 + }, + "archive_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "source": { + "type": "object", + "required": [ + "logical_scope_identity", + "ownership_binding_digest", + "profile_digest", + "participant_contract_digest", + "archive_digest" + ], + "properties": { + "logical_scope_identity": { + "type": "string", + "minLength": 1 + }, + "ownership_binding_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "profile_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "participant_contract_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "archive_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + }, + "additionalProperties": false + }, + "external_idempotency_namespace": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "operation_format", + "operation_schema_version", + "operation", + "operation_id", + "request_digest", + "destination_scope_identity", + "expected_destination_generation", + "arguments" + ], + "properties": { + "operation_format": { + "const": "determa.recovery_operation" + }, + "operation_schema_version": { + "const": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "destination_scope_identity": { + "type": "string", + "minLength": 1 + }, + "expected_destination_generation": { + "oneOf": [ + { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + { + "type": "null" + } + ] + }, + "operation": { + "const": "activate_clone" + }, + "arguments": { + "type": "object", + "required": [ + "independent_execution", + "isolation_evidence_digest", + "resolved_work_digest" + ], + "properties": { + "independent_execution": { + "const": true + }, + "isolation_evidence_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "resolved_work_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "operation_format", + "operation_schema_version", + "operation", + "operation_id", + "request_digest", + "destination_scope_identity", + "expected_destination_generation", + "arguments" + ], + "properties": { + "operation_format": { + "const": "determa.recovery_operation" + }, + "operation_schema_version": { + "const": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "destination_scope_identity": { + "type": "string", + "minLength": 1 + }, + "expected_destination_generation": { + "oneOf": [ + { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + { + "type": "null" + } + ] + }, + "operation": { + "const": "prepare_transfer" + }, + "arguments": { + "type": "object", + "required": [ + "staging_identity", + "archive_digest", + "source", + "freeze_evidence_digest", + "destination_binding_digest", + "expected_source_epoch", + "expected_source_generation" + ], + "properties": { + "staging_identity": { + "type": "string", + "minLength": 1 + }, + "archive_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "source": { + "type": "object", + "required": [ + "logical_scope_identity", + "ownership_binding_digest", + "profile_digest", + "participant_contract_digest", + "archive_digest" + ], + "properties": { + "logical_scope_identity": { + "type": "string", + "minLength": 1 + }, + "ownership_binding_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "profile_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "participant_contract_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "archive_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + }, + "additionalProperties": false + }, + "freeze_evidence_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "destination_binding_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "expected_source_epoch": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "expected_source_generation": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "operation_format", + "operation_schema_version", + "operation", + "operation_id", + "request_digest", + "destination_scope_identity", + "expected_destination_generation", + "arguments" + ], + "properties": { + "operation_format": { + "const": "determa.recovery_operation" + }, + "operation_schema_version": { + "const": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "destination_scope_identity": { + "type": "string", + "minLength": 1 + }, + "expected_destination_generation": { + "oneOf": [ + { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + { + "type": "null" + } + ] + }, + "operation": { + "const": "stage_transfer" + }, + "arguments": { + "type": "object", + "required": [ + "staging_identity", + "archive_digest", + "source", + "transfer_id", + "destination_binding_digest", + "prepared_proof_digest" + ], + "properties": { + "staging_identity": { + "type": "string", + "minLength": 1 + }, + "archive_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "source": { + "type": "object", + "required": [ + "logical_scope_identity", + "ownership_binding_digest", + "profile_digest", + "participant_contract_digest", + "archive_digest" + ], + "properties": { + "logical_scope_identity": { + "type": "string", + "minLength": 1 + }, + "ownership_binding_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "profile_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "participant_contract_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "archive_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + }, + "additionalProperties": false + }, + "transfer_id": { + "type": "string", + "minLength": 1 + }, + "destination_binding_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "prepared_proof_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "operation_format", + "operation_schema_version", + "operation", + "operation_id", + "request_digest", + "destination_scope_identity", + "expected_destination_generation", + "arguments" + ], + "properties": { + "operation_format": { + "const": "determa.recovery_operation" + }, + "operation_schema_version": { + "const": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "destination_scope_identity": { + "type": "string", + "minLength": 1 + }, + "expected_destination_generation": { + "oneOf": [ + { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + { + "type": "null" + } + ] + }, + "operation": { + "const": "commit_transfer" + }, + "arguments": { + "type": "object", + "required": [ + "transfer_id", + "prepared_proof_digest", + "staged_record_digest", + "destination_binding_digest" + ], + "properties": { + "transfer_id": { + "type": "string", + "minLength": 1 + }, + "prepared_proof_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "staged_record_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "destination_binding_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "operation_format", + "operation_schema_version", + "operation", + "operation_id", + "request_digest", + "destination_scope_identity", + "expected_destination_generation", + "arguments" + ], + "properties": { + "operation_format": { + "const": "determa.recovery_operation" + }, + "operation_schema_version": { + "const": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "destination_scope_identity": { + "type": "string", + "minLength": 1 + }, + "expected_destination_generation": { + "oneOf": [ + { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + { + "type": "null" + } + ] + }, + "operation": { + "const": "guarded_action" + }, + "arguments": { + "type": "object", + "required": [ + "action", + "work_kind", + "work_identity", + "worker_scope_identity", + "worker_authority_epoch", + "attempt_fence" + ], + "properties": { + "action": { + "enum": [ + "admit_event", + "process_event", + "dispatch_effect", + "submit_effect_result", + "fire_helper", + "ack_ingress", + "evaluate_impure_provider", + "promote", + "clock_timeout", + "cancel_helper" + ] + }, + "work_kind": { + "oneOf": [ + { + "enum": [ + "effect", + "delivery", + "helper" + ] + }, + { + "type": "null" + } + ] + }, + "work_identity": { + "oneOf": [ + { + "type": "string", + "minLength": 1 + }, + { + "type": "null" + } + ] + }, + "worker_scope_identity": { + "oneOf": [ + { + "type": "string", + "minLength": 1 + }, + { + "type": "null" + } + ] + }, + "worker_authority_epoch": { + "oneOf": [ + { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + { + "type": "null" + } + ] + }, + "attempt_fence": { + "oneOf": [ + { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "operation_format", + "operation_schema_version", + "operation", + "operation_id", + "request_digest", + "destination_scope_identity", + "expected_destination_generation", + "arguments" + ], + "properties": { + "operation_format": { + "const": "determa.recovery_operation" + }, + "operation_schema_version": { + "const": 1 + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "destination_scope_identity": { + "type": "string", + "minLength": 1 + }, + "expected_destination_generation": { + "oneOf": [ + { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + { + "type": "null" + } + ] + }, + "operation": { + "const": "activate_import" + }, + "arguments": { + "type": "object", + "required": [ + "transfer_id", + "committed_proof_digest", + "record_digest", + "expected_authority_epoch", + "expected_scope_generation" + ], + "properties": { + "transfer_id": { + "type": "string", + "minLength": 1 + }, + "committed_proof_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "record_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "expected_authority_epoch": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "expected_scope_generation": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "result_format", + "result_schema_version", + "operation", + "operation_id", + "request_digest", + "status", + "code", + "destination_scope_identity", + "state", + "record_digest", + "safe_relocation", + "no_duplicate_external_work", + "warning", + "source", + "authority_epoch", + "scope_generation", + "transfer_proof_digest" + ], + "properties": { + "result_format": { + "const": "determa.recovery_result" + }, + "result_schema_version": { + "const": 1 + }, + "operation": { + "enum": [ + "strict_restore", + "standalone_takeover", + "resume_takeover", + "reconcile_work", + "clone_scope", + "activate_clone", + "prepare_transfer", + "stage_transfer", + "commit_transfer", + "guarded_action", + "activate_import" + ] + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "status": { + "enum": [ + "succeeded", + "refused" + ] + }, + "code": { + "oneOf": [ + { + "enum": [ + "unsupported_recovery_format", + "unsupported_recovery_version", + "invalid_recovery_request", + "unauthorized_scope", + "scope_operation_conflict", + "scope_archive_incomplete", + "scope_archive_digest_mismatch", + "scope_destination_not_empty", + "scope_restore_requires_quarantine", + "standalone_takeover_requires_fresh_scope", + "standalone_takeover_ambiguous_work", + "clone_has_unresolved_work", + "clone_isolation_unproven", + "host_capability_mismatch", + "scope_fence_unproven", + "scope_generation_conflict", + "scope_transaction_in_doubt", + "scope_transfer_consumed", + "scope_transfer_target_mismatch", + "stale_scope_authority", + "scope_not_active", + "stale_attempt_fence", + "effect_outcome_ambiguous", + "effect_not_outstanding" + ] + }, + { + "type": "null" + } + ] + }, + "destination_scope_identity": { + "type": "string", + "minLength": 1 + }, + "state": { + "enum": [ + "quarantined", + "inactive", + "active", + "retired", + "unchanged" + ] + }, + "record_digest": { + "oneOf": [ + { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + { + "type": "null" + } + ] + }, + "safe_relocation": { + "type": "boolean" + }, + "no_duplicate_external_work": { + "type": "boolean" + }, + "warning": { + "oneOf": [ + { + "enum": [ + "old_owner_may_still_act", + "independent_business_execution", + "unproved_old_owner_retirement", + "external_outcome_requires_reconciliation" + ] + }, + { + "type": "null" + } + ] + }, + "source": { + "oneOf": [ + { + "type": "object", + "required": [ + "logical_scope_identity", + "ownership_binding_digest", + "profile_digest", + "participant_contract_digest", + "archive_digest" + ], + "properties": { + "logical_scope_identity": { + "type": "string", + "minLength": 1 + }, + "ownership_binding_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "profile_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "participant_contract_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "archive_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + }, + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "authority_epoch": { + "oneOf": [ + { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + { + "type": "null" + } + ] + }, + "scope_generation": { + "oneOf": [ + { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + { + "type": "null" + } + ] + }, + "transfer_proof_digest": { + "oneOf": [ + { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "allOf": [ + { + "if": { + "properties": { + "status": { + "const": "succeeded" + } + } + }, + "then": { + "properties": { + "code": { + "type": "null" + } + } + }, + "else": { + "properties": { + "code": { + "type": "string" + }, + "state": { + "const": "unchanged" + }, + "record_digest": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "enum": [ + "strict_restore", + "standalone_takeover", + "resume_takeover", + "reconcile_work", + "clone_scope", + "activate_clone" + ] + } + } + }, + "then": { + "properties": { + "safe_relocation": { + "const": false + }, + "no_duplicate_external_work": { + "const": false + } + } + } + } + ] + }, + { + "type": "object", + "required": [ + "result_format", + "result_schema_version", + "operation", + "operation_id", + "request_digest", + "status", + "code", + "destination_scope_identity", + "state", + "record_digest", + "safe_relocation", + "no_duplicate_external_work", + "warning", + "source", + "authority_epoch", + "scope_generation", + "transfer_proof_digest" + ], + "properties": { + "result_format": { + "const": "determa.recovery_result" + }, + "result_schema_version": { + "const": 1 + }, + "operation": { + "type": "null" + }, + "operation_id": { + "type": "null" + }, + "request_digest": { + "type": "null" + }, + "status": { + "const": "refused" + }, + "code": { + "enum": [ + "unsupported_recovery_format", + "unsupported_recovery_version", + "invalid_recovery_request" + ] + }, + "destination_scope_identity": { + "type": "null" + }, + "state": { + "const": "unchanged" + }, + "record_digest": { + "type": "null" + }, + "safe_relocation": { + "const": false + }, + "no_duplicate_external_work": { + "const": false + }, + "warning": { + "type": "null" + }, + "source": { + "type": "null" + }, + "authority_epoch": { + "type": "null" + }, + "scope_generation": { + "type": "null" + }, + "transfer_proof_digest": { + "type": "null" + } + }, + "additionalProperties": false + } + ] +} diff --git a/src/determa/state/data/recovery-record-v1.schema.json b/src/determa/state/data/recovery-record-v1.schema.json new file mode 100644 index 0000000..1549fc6 --- /dev/null +++ b/src/determa/state/data/recovery-record-v1.schema.json @@ -0,0 +1,314 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://determa.dev/state/schema/recovery-record-v1.schema.json", + "title": "Recovery state record version 1", + "type": "object", + "required": [ + "record_format", + "record_schema_version", + "destination_scope_identity", + "external_idempotency_namespace", + "mode", + "state", + "source", + "work", + "retained_checkpoint_digests", + "risk_acknowledged", + "isolation_evidence_digest", + "decisions", + "destination_binding_digest", + "authority_epoch", + "operation_ledger_identity", + "active_claims" + ], + "properties": { + "record_format": { + "const": "determa.recovery_record" + }, + "record_schema_version": { + "const": 1 + }, + "destination_scope_identity": { + "type": "string", + "minLength": 1 + }, + "external_idempotency_namespace": { + "oneOf": [ + { + "type": "string", + "minLength": 1 + }, + { + "type": "null" + } + ] + }, + "mode": { + "enum": [ + "strict", + "standalone", + "clone", + "relocation" + ] + }, + "state": { + "enum": [ + "quarantined", + "inactive", + "active", + "retired" + ] + }, + "source": { + "type": "object", + "required": [ + "logical_scope_identity", + "ownership_binding_digest", + "profile_digest", + "participant_contract_digest", + "archive_digest" + ], + "properties": { + "logical_scope_identity": { + "type": "string", + "minLength": 1 + }, + "ownership_binding_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "profile_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "participant_contract_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "archive_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + }, + "additionalProperties": false + }, + "work": { + "type": "array", + "items": { + "type": "object", + "required": [ + "work_kind", + "work_identity", + "source_attempt", + "disposition", + "evidence_digest" + ], + "properties": { + "work_kind": { + "enum": [ + "effect", + "delivery", + "helper" + ] + }, + "work_identity": { + "type": "string", + "minLength": 1 + }, + "source_attempt": { + "enum": [ + "unattempted", + "submitted", + "accepted", + "pending", + "unknown", + "terminal" + ] + }, + "disposition": { + "enum": [ + "unattempted", + "ambiguous", + "terminal", + "reconciled", + "abandoned", + "cancelled" + ] + }, + "evidence_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + }, + "additionalProperties": false + } + }, + "retained_checkpoint_digests": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + }, + "risk_acknowledged": { + "type": "boolean" + }, + "isolation_evidence_digest": { + "oneOf": [ + { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + { + "type": "null" + } + ] + }, + "destination_binding_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "authority_epoch": { + "oneOf": [ + { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + { + "type": "null" + } + ] + }, + "operation_ledger_identity": { + "oneOf": [ + { + "type": "string", + "minLength": 1 + }, + { + "type": "null" + } + ] + }, + "active_claims": { + "type": "array", + "maxItems": 0 + }, + "decisions": { + "type": "array", + "items": { + "type": "object", + "required": [ + "work_kind", + "work_identity", + "resolution", + "evidence_digest", + "operator_principal" + ], + "properties": { + "work_kind": { + "enum": [ + "effect", + "delivery", + "helper" + ] + }, + "work_identity": { + "type": "string", + "minLength": 1 + }, + "resolution": { + "enum": [ + "reconciled", + "abandoned", + "cancelled" + ] + }, + "evidence_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "operator_principal": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false, + "allOf": [ + { + "if": { + "properties": { + "mode": { + "const": "strict" + } + } + }, + "then": { + "properties": { + "state": { + "const": "quarantined" + }, + "external_idempotency_namespace": { + "type": "null" + }, + "operation_ledger_identity": { + "type": "null" + } + } + } + }, + { + "if": { + "properties": { + "mode": { + "const": "standalone" + } + } + }, + "then": { + "properties": { + "risk_acknowledged": { + "const": true + }, + "external_idempotency_namespace": { + "type": "string", + "minLength": 1 + }, + "operation_ledger_identity": { + "type": "string", + "minLength": 1 + } + } + } + }, + { + "if": { + "properties": { + "mode": { + "enum": [ + "clone", + "relocation" + ] + } + } + }, + "then": { + "properties": { + "external_idempotency_namespace": { + "type": "string", + "minLength": 1 + }, + "operation_ledger_identity": { + "type": "string", + "minLength": 1 + } + } + } + } + ] +} diff --git a/src/determa/state/data/timer-helper-operation-v1.schema.json b/src/determa/state/data/timer-helper-operation-v1.schema.json new file mode 100644 index 0000000..d327851 --- /dev/null +++ b/src/determa/state/data/timer-helper-operation-v1.schema.json @@ -0,0 +1,579 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://determa.dev/state/schema/timer-helper-operation-v1.schema.json", + "title": "Optional external timer helper operation version 1", + "oneOf": [ + { + "$ref": "#/$defs/request" + }, + { + "$ref": "#/$defs/result" + }, + { + "$ref": "#/$defs/earlyError" + } + ], + "$defs": { + "request": { + "type": "object", + "properties": { + "interface": { + "const": "determa.timer_helper" + }, + "interface_version": { + "const": 1 + }, + "operation": { + "enum": [ + "schedule", + "cancel", + "claim_fire", + "complete_fire", + "read_timer" + ] + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "scope_identity": { + "type": "string", + "minLength": 1 + }, + "root_instance_id": { + "type": "string", + "minLength": 1 + }, + "root_runtime_id": { + "type": "string", + "minLength": 1 + }, + "timer_id": { + "type": "string", + "minLength": 1 + }, + "request_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "arguments": { + "type": "object" + } + }, + "required": [ + "interface", + "interface_version", + "operation", + "operation_id", + "scope_identity", + "root_instance_id", + "root_runtime_id", + "timer_id", + "request_digest", + "arguments" + ], + "additionalProperties": false, + "allOf": [ + { + "if": { + "properties": { + "operation": { + "const": "schedule" + } + }, + "required": [ + "operation" + ] + }, + "then": { + "properties": { + "arguments": { + "type": "object", + "properties": { + "clock_basis": { + "const": "unix_nanoseconds" + }, + "deadline_at": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*|-[1-9][0-9]*)$" + }, + "delay_nanoseconds": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "event_name": { + "type": "string", + "minLength": 1 + }, + "payload": { + "$ref": "aggregate-state-v1.schema.json#/$defs/typedMap" + }, + "correlation_id": { + "oneOf": [ + { + "type": "string", + "minLength": 1 + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "clock_basis", + "event_name", + "payload", + "correlation_id" + ], + "additionalProperties": false, + "oneOf": [ + { + "required": [ + "deadline_at" + ], + "not": { + "required": [ + "delay_nanoseconds" + ] + } + }, + { + "required": [ + "delay_nanoseconds" + ], + "not": { + "required": [ + "deadline_at" + ] + } + } + ] + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "cancel" + } + }, + "required": [ + "operation" + ] + }, + "then": { + "properties": { + "arguments": { + "type": "object", + "properties": { + "expected_revision": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + } + }, + "required": [ + "expected_revision" + ], + "additionalProperties": false + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "claim_fire" + } + }, + "required": [ + "operation" + ] + }, + "then": { + "properties": { + "arguments": { + "type": "object", + "properties": { + "expected_revision": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "clock_basis": { + "const": "unix_nanoseconds" + }, + "worker_principal": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "expected_revision", + "clock_basis", + "worker_principal" + ], + "additionalProperties": false + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "complete_fire" + } + }, + "required": [ + "operation" + ] + }, + "then": { + "properties": { + "arguments": { + "type": "object", + "properties": { + "expected_revision": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "attempt_fence": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "worker_principal": { + "type": "string", + "minLength": 1 + }, + "event_id": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "admission_receipt_digest": { + "oneOf": [ + { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "expected_revision", + "attempt_fence", + "worker_principal", + "event_id", + "admission_receipt_digest" + ], + "additionalProperties": false + } + } + } + }, + { + "if": { + "properties": { + "operation": { + "const": "read_timer" + } + }, + "required": [ + "operation" + ] + }, + "then": { + "properties": { + "arguments": { + "type": "object", + "properties": {}, + "required": [], + "additionalProperties": false + } + } + } + } + ] + }, + "result": { + "type": "object", + "properties": { + "interface": { + "const": "determa.timer_helper" + }, + "interface_version": { + "const": 1 + }, + "operation": { + "enum": [ + "schedule", + "cancel", + "claim_fire", + "complete_fire", + "read_timer" + ] + }, + "operation_id": { + "type": "string", + "minLength": 1 + }, + "scope_identity": { + "type": "string", + "minLength": 1 + }, + "root_instance_id": { + "type": "string", + "minLength": 1 + }, + "root_runtime_id": { + "type": "string", + "minLength": 1 + }, + "timer_id": { + "type": "string", + "minLength": 1 + }, + "status": { + "enum": [ + "accepted", + "rejected" + ] + }, + "record_revision": { + "oneOf": [ + { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + { + "type": "null" + } + ] + }, + "attempt_fence": { + "oneOf": [ + { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + { + "type": "null" + } + ] + }, + "clock_basis": { + "const": "unix_nanoseconds" + }, + "expires_at": { + "oneOf": [ + { + "type": "string", + "pattern": "^(0|[1-9][0-9]*|-[1-9][0-9]*)$" + }, + { + "type": "null" + } + ] + }, + "event_id": { + "oneOf": [ + { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + { + "type": "null" + } + ] + }, + "delivery_state": { + "enum": [ + "none", + "pending", + "admitted", + "ambiguous" + ] + }, + "error_code": { + "enum": [ + null, + "invalid_timer_request", + "unsupported_timer_protocol", + "unsupported_timer_protocol_version", + "unauthorized_timer_scope", + "invalid_timer_time", + "timer_deadline_overflow", + "timer_clock_unavailable", + "timer_operation_conflict", + "timer_id_conflict", + "timer_not_found", + "timer_revision_conflict", + "timer_not_due", + "timer_fire_in_progress", + "timer_already_fired", + "timer_cancelled", + "timer_stale_fence", + "timer_worker_mismatch", + "timer_event_conflict", + "timer_admission_rejected", + "delivery_ambiguous", + "timer_capability_mismatch" + ] + }, + "result_digest": { + "oneOf": [ + { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "interface", + "interface_version", + "operation", + "operation_id", + "scope_identity", + "root_instance_id", + "root_runtime_id", + "timer_id", + "status", + "record_revision", + "attempt_fence", + "clock_basis", + "expires_at", + "event_id", + "delivery_state", + "error_code", + "result_digest" + ], + "additionalProperties": false, + "allOf": [ + { + "if": { + "properties": { + "status": { + "const": "accepted" + } + }, + "required": [ + "status" + ] + }, + "then": { + "properties": { + "error_code": { + "type": "null" + }, + "result_digest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + } + }, + "else": { + "properties": { + "error_code": { + "not": { + "type": "null" + } + }, + "result_digest": { + "type": "null" + } + } + } + } + ] + }, + "earlyError": { + "type": "object", + "properties": { + "operation": { + "type": "null" + }, + "operation_id": { + "type": "null" + }, + "scope_identity": { + "type": "null" + }, + "root_instance_id": { + "type": "null" + }, + "root_runtime_id": { + "type": "null" + }, + "timer_id": { + "type": "null" + }, + "interface": { + "const": "determa.timer_helper" + }, + "interface_version": { + "const": 1 + }, + "status": { + "const": "rejected" + }, + "record_revision": { + "type": "null" + }, + "attempt_fence": { + "type": "null" + }, + "clock_basis": { + "const": "unix_nanoseconds" + }, + "expires_at": { + "type": "null" + }, + "event_id": { + "type": "null" + }, + "delivery_state": { + "const": "none" + }, + "error_code": { + "enum": [ + "invalid_timer_request", + "unsupported_timer_protocol", + "unsupported_timer_protocol_version" + ] + }, + "result_digest": { + "type": "null" + } + }, + "required": [ + "operation", + "operation_id", + "scope_identity", + "root_instance_id", + "root_runtime_id", + "timer_id", + "interface", + "interface_version", + "status", + "record_revision", + "attempt_fence", + "clock_basis", + "expires_at", + "event_id", + "delivery_state", + "error_code", + "result_digest" + ], + "additionalProperties": false + } + } +} diff --git a/src/determa/state/public_client.py b/src/determa/state/public_client.py new file mode 100644 index 0000000..102fa23 --- /dev/null +++ b/src/determa/state/public_client.py @@ -0,0 +1,260 @@ +"""Named-binding public v1 client with durable, pinned mutation retry. + +Transport authentication stays outside these messages. A transport exception means +unknown fate; the saved request remains available for receipt lookup or exact retry. +""" + +from __future__ import annotations + +import copy +import json +import sqlite3 +import uuid +from collections.abc import Callable, Mapping +from dataclasses import dataclass +from functools import cache +from pathlib import Path +from typing import Any, cast + +from .errors import ArtifactError +from .wire import canonical_bytes, hash_value + +_DATA = Path(__file__).with_name("data") +_PROTOCOL = "determa.execution_host" +_READS = {"capabilities", "read", "inspect", "receipt"} + + +class PublicHostError(ArtifactError): + """A closed protocol or pinned-binding failure.""" + + +@cache +def _validator(filename: str) -> Any: + import jsonschema + from referencing import Registry, Resource + from referencing.jsonschema import DRAFT202012 + + registry = Registry() + for path in _DATA.glob("*.schema.json"): + document = json.loads(path.read_text()) + resource = Resource.from_contents(document, default_specification=DRAFT202012) + registry = registry.with_resource(path.name, resource) + registry = registry.with_resource(f"https://determa.dev/state/schema/{path.name}", resource) + if "$id" in document: + registry = registry.with_resource(document["$id"], resource) + document = json.loads((_DATA / filename).read_text()) + return jsonschema.Draft202012Validator(document, registry=registry) + + +def validate_public_message(document: Mapping[str, Any], *, response: bool = False) -> None: + """Validate a closed public wire message against the pinned v1 schema.""" + filename = f"public-host-{'response' if response else 'request'}-v1.schema.json" + if next(_validator(filename).iter_errors(document), None) is not None: + raise PublicHostError("invalid_host_request") + canonical_bytes(document) + + +def public_request_digest(request: Mapping[str, Any]) -> str: + validate_public_message(request) + return hash_value(["determa-public-host-request-digest-1", "1", request]) + + +@dataclass(frozen=True, slots=True) +class EndpointBinding: + """A deployment endpoint and scope alias; never part of machine definitions.""" + + endpoint: str + scope_alias: str + + def __post_init__(self) -> None: + if not self.endpoint or not self.scope_alias: + raise PublicHostError("binding_unavailable") + + +class PublicHostClient: + """Persist complete requests before sending any mutation. + + ``transport`` receives the saved endpoint and closed request. It supplies its + own authenticated transport context and returns a closed protocol response. + Connections and credential objects never enter request bytes or this journal. + """ + + def __init__( + self, + path: str | Path, + bindings: Mapping[str, EndpointBinding], + transport: Callable[[str, dict[str, Any]], dict[str, Any]], + ) -> None: + if str(path) == ":memory:": + raise PublicHostError("binding_unavailable") + self.path = str(path) + self.bindings = dict(bindings) + self.transport = transport + + def setup_schema(self) -> None: + """Explicitly create a durable client request journal.""" + with self._connect() as db: + db.execute( + "CREATE TABLE IF NOT EXISTS determa_public_client_requests (" + "operation_id TEXT PRIMARY KEY, binding_name TEXT NOT NULL, " + "endpoint TEXT NOT NULL, request BLOB NOT NULL, " + "request_digest TEXT NOT NULL, response BLOB)" + ) + + def _connect(self) -> sqlite3.Connection: + db = sqlite3.connect(self.path) + db.execute("PRAGMA journal_mode=WAL") + db.execute("PRAGMA synchronous=FULL") + return db + + def _send(self, endpoint: str, request: dict[str, Any]) -> dict[str, Any]: + response = self.transport(endpoint, copy.deepcopy(request)) + validate_public_message(response, response=True) + if response["operation_id"] != request["operation_id"]: + raise PublicHostError("invalid_host_request") + receipt = response["receipt"] + if receipt is not None: + if ( + receipt["scope_binding_identity"] != request["scope_binding_identity"] + or receipt["operation_id"] != request["operation_id"] + or receipt["request_digest"] != public_request_digest(request) + ): + raise PublicHostError("invalid_host_request") + if receipt["receipt_kind"] == "committed": + digest = hash_value(["determa-public-host-evidence-1", "1", response["value"]]) + else: + digest = hash_value( + [ + "determa-public-host-acceptance-evidence-1", + "1", + request["scope_binding_identity"], + request["operation_id"], + receipt["request_digest"], + receipt["acceptance_receipt"], + ] + ) + if receipt["evidence_digest"] != digest: + raise PublicHostError("invalid_host_request") + if response["value"] is not None and response["value"]["operation"] != request["operation"]: + raise PublicHostError("invalid_host_request") + if response["error"] is not None and response["error"]["operation"] != request["operation"]: + raise PublicHostError("invalid_host_request") + return copy.deepcopy(response) + + def discover(self, name: str) -> dict[str, Any]: + binding = self.bindings.get(name) + if binding is None: + raise PublicHostError("binding_unavailable") + request = { + "protocol": _PROTOCOL, + "protocol_version": 1, + "operation_id": str(uuid.uuid4()), + "scope_binding_identity": None, + "operation": "capabilities", + "target": {"root_instance_id": None, "runtime_id": None, "runtime_incarnation": None}, + "precondition": None, + "arguments": {"scope_alias": binding.scope_alias}, + } + validate_public_message(request) + return self._send(binding.endpoint, request) + + def submit(self, name: str, request: Mapping[str, Any]) -> dict[str, Any]: + candidate = copy.deepcopy(dict(request)) + operation_id = candidate.get("operation_id") + if not isinstance(operation_id, str) or not operation_id: + raise PublicHostError("invalid_host_request") + with self._connect() as db: + previous = db.execute( + "SELECT binding_name, endpoint, request, response FROM " + "determa_public_client_requests WHERE operation_id=?", + (operation_id,), + ).fetchone() + if previous is not None: + saved = json.loads(previous[2]) + if candidate.get("scope_binding_identity") is None: + candidate["scope_binding_identity"] = saved["scope_binding_identity"] + validate_public_message(candidate) + if name != previous[0] or canonical_bytes(candidate) != previous[2]: + raise PublicHostError("operation_id_conflict") + return self.retry(operation_id) + binding = self.bindings.get(name) + if binding is None: + raise PublicHostError("binding_unavailable") + if candidate.get("scope_binding_identity") is None: + discovery = self.discover(name) + if discovery["status"] != "committed": + raise PublicHostError("binding_unavailable") + candidate["scope_binding_identity"] = discovery["value"]["result"][ + "scope_binding_identity" + ] + validate_public_message(candidate) + if candidate["operation"] in _READS: + return self._send(binding.endpoint, candidate) + encoded = canonical_bytes(candidate) + with self._connect() as db: + db.execute("BEGIN IMMEDIATE") + try: + db.execute( + "INSERT INTO determa_public_client_requests VALUES (?,?,?,?,?,NULL)", + ( + operation_id, + name, + binding.endpoint, + encoded, + public_request_digest(candidate), + ), + ) + except sqlite3.IntegrityError as error: + raise PublicHostError("operation_id_conflict") from error + return self.retry(operation_id) + + def retry(self, operation_id: str) -> dict[str, Any]: + """Send only saved bytes to the saved endpoint; never rediscover an alias.""" + with self._connect() as db: + row = db.execute( + "SELECT endpoint, request, request_digest, response FROM " + "determa_public_client_requests WHERE operation_id=?", + (operation_id,), + ).fetchone() + if row is None: + raise PublicHostError("outcome_unknown") + request = json.loads(row[1]) + if canonical_bytes(request) != row[1] or public_request_digest(request) != row[2]: + raise PublicHostError("invalid_host_request") + if row[3] is not None: + response = json.loads(row[3]) + validate_public_message(response, response=True) + return cast(dict[str, Any], response) + response = self._send(row[0], request) + if response["status"] != "pending": + with self._connect() as db: + db.execute( + "UPDATE determa_public_client_requests SET response=? " + "WHERE operation_id=? AND response IS NULL", + (canonical_bytes(response), operation_id), + ) + return response + + def receipt(self, operation_id: str) -> dict[str, Any]: + """Query retained host evidence at the original pinned binding.""" + with self._connect() as db: + row = db.execute( + "SELECT endpoint, request, request_digest FROM " + "determa_public_client_requests WHERE operation_id=?", + (operation_id,), + ).fetchone() + if row is None: + raise PublicHostError("outcome_unknown") + saved = json.loads(row[1]) + request = { + "protocol": _PROTOCOL, + "protocol_version": 1, + "operation_id": str(uuid.uuid4()), + "scope_binding_identity": saved["scope_binding_identity"], + "operation": "receipt", + "target": {"root_instance_id": None, "runtime_id": None, "runtime_incarnation": None}, + "precondition": None, + "arguments": {"queried_operation_id": operation_id, "request_digest": row[2]}, + } + validate_public_message(request) + return self._send(row[0], request) diff --git a/tests/test_public_client.py b/tests/test_public_client.py new file mode 100644 index 0000000..9e553f1 --- /dev/null +++ b/tests/test_public_client.py @@ -0,0 +1,148 @@ +from __future__ import annotations + +import copy + +import pytest + +from determa.state.public_client import ( + EndpointBinding, + PublicHostClient, + PublicHostError, + public_request_digest, +) +from determa.state.wire import hash_value + + +def request(operation_id="create-1"): + return { + "protocol": "determa.execution_host", + "protocol_version": 1, + "operation_id": operation_id, + "scope_binding_identity": None, + "operation": "create", + "target": {"root_instance_id": "root", "runtime_id": None, "runtime_incarnation": None}, + "precondition": None, + "arguments": { + "validated_bundle_fingerprint": "sha256:" + "a" * 64, + "namespace": "example", + "machine_id": "counter", + "machine_version": "1", + "root_instance_id": "root", + "creation_id": "root:create", + "bindings": ["map", []], + }, + } + + +def capabilities(candidate): + result = { + "scope_binding_identity": "immutable-binding-1", + "supported_operations": ["capabilities", "create", "receipt"], + "supported_scope_actions": [], + "supported_determa_capabilities": [], + "supported_timer_commands": [], + "extension_reports": [], + "authority_profile": None, + "guarantees": { + "inspection_structural": False, + "inspection_semantic": False, + "retained_history": False, + "saved_response_replay": False, + "deterministic_reexecution": False, + }, + } + result["profile_digest"] = hash_value( + ["determa-public-host-profile-1", "1", "immutable-binding-1", result] + ) + return { + "protocol": "determa.execution_host", + "protocol_version": 1, + "operation_id": candidate["operation_id"], + "status": "committed", + "receipt": None, + "value": {"operation": "capabilities", "result": result}, + "error": None, + } + + +def rejected(candidate): + return { + "protocol": "determa.execution_host", + "protocol_version": 1, + "operation_id": candidate["operation_id"], + "status": "rejected", + "receipt": None, + "value": None, + "error": {"operation": candidate["operation"], "code": "host_capability_mismatch"}, + } + + +def test_lost_response_pins_request_and_endpoint_across_restart_and_alias_change(tmp_path): + calls = [] + + def transport(endpoint, candidate): + calls.append((endpoint, copy.deepcopy(candidate))) + if candidate["operation"] == "capabilities": + return capabilities(candidate) + raise TimeoutError("response lost") + + path = tmp_path / "client.db" + client = PublicHostClient( + path, {"one": EndpointBinding("old-endpoint", "old-scope")}, transport + ) + client.setup_schema() + with pytest.raises(TimeoutError): + client.submit("one", request()) + first = calls[-1] + assert first[1]["scope_binding_identity"] == "immutable-binding-1" + assert public_request_digest(first[1]).startswith("sha256:") + assert len(calls) == 2 + + def restarted_transport(endpoint, candidate): + calls.append((endpoint, copy.deepcopy(candidate))) + return rejected(candidate) + + restarted = PublicHostClient( + path, {"one": EndpointBinding("new-endpoint", "new-scope")}, restarted_transport + ) + response = restarted.submit("one", request()) + assert response["status"] == "rejected" + assert calls[-1] == first + assert len(calls) == 3 # No new discovery after the lost response. + assert restarted.retry("create-1") == response + assert len(calls) == 3 # The saved complete response is returned without transport. + + +def test_unequal_operation_reuse_fails_without_transport(tmp_path): + calls = [] + + def transport(endpoint, candidate): + calls.append(candidate) + return ( + capabilities(candidate) + if candidate["operation"] == "capabilities" + else rejected(candidate) + ) + + client = PublicHostClient( + tmp_path / "client.db", {"one": EndpointBinding("endpoint", "scope")}, transport + ) + client.setup_schema() + client.submit("one", request()) + changed = request() + changed["arguments"]["creation_id"] = "another-creation" + with pytest.raises(PublicHostError, match="operation_id_conflict"): + client.submit("one", changed) + assert len(calls) == 2 + + +def test_unknown_operation_is_not_rollback_or_automatic_replacement(tmp_path): + def forbidden_transport(*_args): + raise AssertionError("unknown local work cannot be sent") + + client = PublicHostClient(tmp_path / "client.db", {}, forbidden_transport) + client.setup_schema() + with pytest.raises(PublicHostError, match="outcome_unknown"): + client.retry("missing") + with pytest.raises(PublicHostError, match="outcome_unknown"): + client.receipt("missing") From ec2aa327c871ac7ce4c96b45c6e6db7c5126b101 Mon Sep 17 00:00:00 2001 From: Christian-Manuel Butzke Date: Mon, 5 Oct 2026 11:29:14 +0000 Subject: [PATCH 02/10] feat: checkpoint public SQLite host core operations; validation unfinished --- src/determa/state/public_client.py | 24 +- src/determa/state/public_host.py | 382 +++++++++++++++++++++++++++++ tests/test_public_host.py | 128 ++++++++++ 3 files changed, 527 insertions(+), 7 deletions(-) create mode 100644 src/determa/state/public_host.py create mode 100644 tests/test_public_host.py diff --git a/src/determa/state/public_client.py b/src/determa/state/public_client.py index 102fa23..ecc70fa 100644 --- a/src/determa/state/public_client.py +++ b/src/determa/state/public_client.py @@ -139,6 +139,13 @@ def _send(self, endpoint: str, request: dict[str, Any]) -> dict[str, Any]: raise PublicHostError("invalid_host_request") if response["error"] is not None and response["error"]["operation"] != request["operation"]: raise PublicHostError("invalid_host_request") + if request["operation"] == "capabilities" and response["status"] == "committed": + profile = copy.deepcopy(response["value"]["result"]) + digest = profile.pop("profile_digest") + if digest != hash_value( + ["determa-public-host-profile-1", "1", profile["scope_binding_identity"], profile] + ): + raise PublicHostError("invalid_host_request") return copy.deepcopy(response) def discover(self, name: str) -> dict[str, Any]: @@ -180,13 +187,16 @@ def submit(self, name: str, request: Mapping[str, Any]) -> dict[str, Any]: binding = self.bindings.get(name) if binding is None: raise PublicHostError("binding_unavailable") - if candidate.get("scope_binding_identity") is None: - discovery = self.discover(name) - if discovery["status"] != "committed": - raise PublicHostError("binding_unavailable") - candidate["scope_binding_identity"] = discovery["value"]["result"][ - "scope_binding_identity" - ] + discovery = self.discover(name) + if discovery["status"] != "committed": + raise PublicHostError("binding_unavailable") + profile = discovery["value"]["result"] + resolved = profile["scope_binding_identity"] + if candidate.get("scope_binding_identity") not in {None, resolved}: + raise PublicHostError("binding_unavailable") + candidate["scope_binding_identity"] = resolved + if candidate.get("operation") not in profile["supported_operations"]: + raise PublicHostError("host_capability_mismatch") validate_public_message(candidate) if candidate["operation"] in _READS: return self._send(binding.endpoint, candidate) diff --git a/src/determa/state/public_host.py b/src/determa/state/public_host.py new file mode 100644 index 0000000..49c300e --- /dev/null +++ b/src/determa/state/public_host.py @@ -0,0 +1,382 @@ +"""Minimal public v1 local SQLite host; no authority or helper claims.""" + +from __future__ import annotations + +import copy +import json +import sqlite3 +from collections.abc import Mapping +from pathlib import Path +from typing import Any, cast + +from .checkpoint_v1 import ( + admit_checkpoint_v1, + create_checkpoint_v1, + restore_execution_checkpoint_v1, + step_checkpoint_v1, +) +from .definition import Bundle, load_bundle +from .errors import ArtifactError +from .inspection import inspect_candidate +from .public_client import PublicHostError, public_request_digest, validate_public_message +from .wire import DefinitionResolver, canonical_bytes, decoded_typed_value, hash_value + +_OPERATIONS = ["capabilities", "create", "admit", "process", "read", "inspect", "receipt"] +_MUTATIONS = {"create", "admit", "process"} + + +class SQLitePublicExecutionHost: + """One authenticated logical scope in one explicitly initialized SQLite file. + + Transport adapters authenticate the principal before calling ``handle``. This + host authorizes that identity before any scope, root or receipt lookup. Native + effects, authority, timers, archive staging and recovery are not advertised. + """ + + def __init__( + self, + path: str | Path, + *, + scope_alias: str, + scope_binding_identity: str, + authorized_principals: frozenset[str], + resolver: DefinitionResolver, + ) -> None: + if ( + str(path) == ":memory:" + or not scope_alias + or not scope_binding_identity + or not authorized_principals + ): + raise PublicHostError("host_capability_mismatch") + self.path = str(path) + self.scope_alias = scope_alias + self.scope_binding_identity = scope_binding_identity + self.authorized_principals = frozenset(authorized_principals) + self.resolver = resolver + + def _connect(self) -> sqlite3.Connection: + db = sqlite3.connect(self.path) + db.execute("PRAGMA journal_mode=WAL") + db.execute("PRAGMA synchronous=FULL") + return db + + def setup_schema(self) -> None: + with self._connect() as db: + db.execute("BEGIN IMMEDIATE") + db.execute( + "CREATE TABLE IF NOT EXISTS determa_public_host_binding (" + "singleton INTEGER PRIMARY KEY CHECK(singleton=1), " + "schema_version INTEGER NOT NULL CHECK(schema_version=1), " + "scope_binding_identity TEXT NOT NULL)" + ) + db.execute( + "CREATE TABLE IF NOT EXISTS determa_public_host_checkpoints (" + "root_instance_id TEXT PRIMARY KEY, checkpoint BLOB NOT NULL)" + ) + db.execute( + "CREATE TABLE IF NOT EXISTS determa_public_host_responses (" + "operation_id TEXT PRIMARY KEY, request BLOB NOT NULL, " + "request_digest TEXT NOT NULL, response BLOB NOT NULL)" + ) + db.execute( + "INSERT OR IGNORE INTO determa_public_host_binding VALUES (1,1,?)", + (self.scope_binding_identity,), + ) + self._check_binding(db) + + def _check_binding(self, db: sqlite3.Connection) -> None: + rows = db.execute( + "SELECT singleton,schema_version,scope_binding_identity " + "FROM determa_public_host_binding" + ).fetchall() + if rows != [(1, 1, self.scope_binding_identity)]: + raise PublicHostError("binding_unavailable") + + @staticmethod + def _response( + request: Mapping[str, Any], *, result: Any = None, code: str | None = None + ) -> dict[str, Any]: + return { + "protocol": "determa.execution_host", + "protocol_version": 1, + "operation_id": request["operation_id"], + "status": "rejected" if code else "committed", + "receipt": None, + "value": None if code else {"operation": request["operation"], "result": result}, + "error": {"operation": request["operation"], "code": code} if code else None, + } + + def _capabilities(self) -> dict[str, Any]: + result = { + "scope_binding_identity": self.scope_binding_identity, + "supported_operations": _OPERATIONS, + "supported_scope_actions": [], + "supported_determa_capabilities": [], + "supported_timer_commands": [], + "extension_reports": [], + "authority_profile": None, + "guarantees": { + "inspection_structural": True, + "inspection_semantic": False, + "retained_history": True, + "saved_response_replay": True, + "deterministic_reexecution": False, + }, + } + result["profile_digest"] = hash_value( + ["determa-public-host-profile-1", "1", self.scope_binding_identity, result] + ) + return result + + def handle(self, request: Mapping[str, Any], *, principal: str) -> dict[str, Any]: + """Return a response only after its native transaction has committed.""" + candidate = copy.deepcopy(dict(request)) + validate_public_message(candidate) + if principal not in self.authorized_principals: + return self._response(candidate, code="unauthorized_scope") + operation = candidate["operation"] + if operation == "capabilities": + if candidate["arguments"]["scope_alias"] != self.scope_alias: + return self._response(candidate, code="unauthorized_scope") + if candidate["scope_binding_identity"] not in {None, self.scope_binding_identity}: + return self._response(candidate, code="binding_unavailable") + elif candidate["scope_binding_identity"] != self.scope_binding_identity: + return self._response(candidate, code="binding_unavailable") + if operation not in _OPERATIONS: + return self._response(candidate, code="host_capability_mismatch") + digest = public_request_digest(candidate) + try: + with self._connect() as db: + db.execute("BEGIN IMMEDIATE") + self._check_binding(db) + if operation in _MUTATIONS: + saved = db.execute( + "SELECT request, request_digest, response " + "FROM determa_public_host_responses " + "WHERE operation_id=?", + (candidate["operation_id"],), + ).fetchone() + if saved is not None: + if saved[0] != canonical_bytes(candidate) or saved[1] != digest: + return self._response(candidate, code="operation_id_conflict") + response = json.loads(saved[2]) + validate_public_message(response, response=True) + return cast(dict[str, Any], response) + result, checkpoint, changed = self._execute(db, candidate) + response = self._response(candidate, result=result) + if operation in _MUTATIONS and changed: + response["receipt"] = { + "scope_binding_identity": self.scope_binding_identity, + "operation_id": candidate["operation_id"], + "request_digest": digest, + "receipt_kind": "committed", + "acceptance_receipt": None, + "evidence_digest": hash_value( + ["determa-public-host-evidence-1", "1", response["value"]] + ), + } + validate_public_message(response, response=True) + assert checkpoint is not None + db.execute( + "INSERT INTO determa_public_host_checkpoints VALUES (?,?) " + "ON CONFLICT(root_instance_id) DO UPDATE " + "SET checkpoint=excluded.checkpoint", + (checkpoint["root_instance_id"], canonical_bytes(checkpoint)), + ) + db.execute( + "INSERT INTO determa_public_host_responses VALUES (?,?,?,?)", + ( + candidate["operation_id"], + canonical_bytes(candidate), + digest, + canonical_bytes(response), + ), + ) + else: + validate_public_message(response, response=True) + return response + except ArtifactError as error: + # Pure refusals roll back the transaction; native transaction failures + # escape with no protocol response and must be reconciled by the client. + response = self._response(candidate, code=error.code) + validate_public_message(response, response=True) + return response + + def _execute(self, db: sqlite3.Connection, request: dict[str, Any]) -> tuple[Any, Any, bool]: + operation = request["operation"] + arguments = request["arguments"] + if operation == "capabilities": + return self._capabilities(), None, False + if operation == "receipt": + row = db.execute( + "SELECT request_digest,response FROM determa_public_host_responses " + "WHERE operation_id=?", + (arguments["queried_operation_id"],), + ).fetchone() + if row is not None and row[0] != arguments["request_digest"]: + raise PublicHostError("operation_id_conflict") + return ( + { + "saved_response": None if row is None else json.loads(row[1]), + "retention": "unknown" if row is None else "retained", + }, + None, + False, + ) + target = request["target"] + root_id = target["root_instance_id"] + if not root_id: + raise PublicHostError("invalid_host_request") + row = db.execute( + "SELECT checkpoint FROM determa_public_host_checkpoints WHERE root_instance_id=?", + (root_id,), + ).fetchone() + if operation == "read": + checkpoint = ( + None + if row is None + else restore_execution_checkpoint_v1(row[0], self.resolver).document + ) + return ( + { + "checkpoint": checkpoint, + "observed_checkpoint_digest": None + if checkpoint is None + else checkpoint["execution_checkpoint_digest"], + }, + None, + False, + ) + if operation == "create": + if arguments["root_instance_id"] != root_id: + raise PublicHostError("invalid_host_request") + source = self.resolver.resolve_definition(arguments["validated_bundle_fingerprint"]) + if source is None or not self.resolver.definition_is_trusted( + arguments["validated_bundle_fingerprint"] + ): + raise PublicHostError("missing_required_artifact") + bundle = source if isinstance(source, Bundle) else load_bundle(source) + if bundle.fingerprint != arguments["validated_bundle_fingerprint"]: + raise PublicHostError("invalid_host_request") + selected = next( + (m for m in bundle.raw["machines"] if m["machine_id"] == arguments["machine_id"]), + None, + ) + if ( + selected is None + or bundle.raw["namespace"] != arguments["namespace"] + or str(selected.get("version", 1)) != arguments["machine_version"] + ): + raise PublicHostError("invalid_host_request") + result = create_checkpoint_v1( + bundle, + arguments["machine_id"], + root_id, + arguments["creation_id"], + decoded_typed_value(arguments["bindings"]), + _include_projection_result=True, + ) + checkpoint = result["checkpoint"] + if row is not None: + existing = restore_execution_checkpoint_v1(row[0], self.resolver).document + if existing["operation_receipts"][0] != checkpoint["operation_receipts"][0]: + raise PublicHostError("creation_id_conflict") + checkpoint = existing + core = result["core_result"] + return ( + { + "checkpoint": checkpoint, + "creation_receipt": checkpoint["operation_receipts"][0], + **{ + k: core[k] + for k in ("status", "emissions", "lifecycle_dispositions", "fault") + }, + }, + checkpoint, + True, + ) + if row is None: + raise PublicHostError("invalid_instance_target") + checkpoint = restore_execution_checkpoint_v1(row[0], self.resolver).document + aggregate = checkpoint["root_record"].get("aggregate_state") + if aggregate is None: + raise PublicHostError("terminal_root") + if operation == "inspect": + inspection = arguments["candidate"] + if ( + target["runtime_id"] != inspection["runtime_id"] + or target["runtime_incarnation"] != inspection["runtime_incarnation"] + ): + raise PublicHostError("invalid_host_request") + outcome = inspect_candidate( + aggregate, inspection, self.resolver, semantic_enabled=False + ) + if outcome.get("result") == "failure": + raise PublicHostError(outcome["code"]) + return ( + { + "outcome": outcome, + "observed_aggregate_state_digest": aggregate["aggregate_state_digest"], + }, + None, + False, + ) + precondition = request["precondition"] + if precondition is None: + raise PublicHostError("invalid_host_request") + guard = { + "expected_revision": precondition["revision"], + "expected_checkpoint_digest": precondition["checkpoint_digest"], + } + if operation == "admit": + deliveries = arguments["ordered_deliveries"] + for delivery in deliveries: + destination = delivery["envelope"]["target"] + if next(iter(destination.values()))["root_instance_id"] != root_id: + raise PublicHostError("invalid_host_request") + result = admit_checkpoint_v1( + checkpoint, deliveries, self.resolver, **guard, _include_projection_result=True + ) + updated = result.get("checkpoint", result) + if "execution_checkpoint_digest" not in updated: + updated = checkpoint + receipts = [ + next( + r + for r in updated["operation_receipts"] + if r["operation_kind"] == "acceptance" + and r["event_id"] == d["envelope"]["event_id"] + ) + for d in deliveries + ] + root = next( + r + for r in updated["root_record"]["aggregate_state"]["runtimes"] + if r["runtime_id"] == aggregate["root_runtime_id"] + ) + return ( + { + "checkpoint": updated, + "acceptance_receipts": receipts, + "status": root["status"], + "accepted": [d["envelope"] for d in deliveries], + }, + updated, + True, + ) + runtime = next( + (r for r in aggregate["runtimes"] if r["runtime_id"] == target["runtime_id"]), None + ) + if runtime is None or runtime["identity_origin"] != target["runtime_incarnation"]: + raise PublicHostError("invalid_instance_target") + result = step_checkpoint_v1( + checkpoint, target["runtime_id"], self.resolver, **guard, _include_host_response=True + ) + updated = result["checkpoint"] + core = result.get("core_result", result.get("step_result")) + return ( + {"checkpoint": updated, "core_result": core, "terminal_receipt": result.get("receipt")}, + updated, + (updated["execution_checkpoint_digest"] != checkpoint["execution_checkpoint_digest"]), + ) diff --git a/tests/test_public_host.py b/tests/test_public_host.py new file mode 100644 index 0000000..04fbd3e --- /dev/null +++ b/tests/test_public_host.py @@ -0,0 +1,128 @@ +from __future__ import annotations + +import copy + +import determa.state as ds +from determa.state.public_client import validate_public_message +from determa.state.public_host import SQLitePublicExecutionHost + +MACHINE = """ +format: 1 +namespace: example +events: + increment: + direction: input + payload: + amount: {type: int, required: true} +machines: + - machine_id: counter + version: 1 + root: + variables: + count: {type: int, init: 0} + on_events: + increment: + action: + - assign: {count: "count + event.payload.amount"} +""" + + +def base(operation, operation_id, root="root"): + return { + "protocol": "determa.execution_host", + "protocol_version": 1, + "scope_binding_identity": "binding-1", + "operation": operation, + "operation_id": operation_id, + "target": {"root_instance_id": root, "runtime_id": None, "runtime_incarnation": None}, + "precondition": None, + "arguments": {}, + } + + +def open_host(tmp_path): + bundle = ds.load_bundle(MACHINE) + host = SQLitePublicExecutionHost( + tmp_path / "host.db", + scope_alias="scope", + scope_binding_identity="binding-1", + authorized_principals=frozenset({"alice"}), + resolver=ds.MemoryArtifactResolver(definitions={bundle.fingerprint: bundle}), + ) + host.setup_schema() + return host, bundle + + +def creation(bundle): + request = base("create", "operation-create") + request["arguments"] = { + "validated_bundle_fingerprint": bundle.fingerprint, + "namespace": "example", + "machine_id": "counter", + "machine_version": "1", + "root_instance_id": "root", + "creation_id": "root:create", + "bindings": ["map", []], + } + return request + + +def test_public_host_commits_mailbox_processing_and_exact_saved_response(tmp_path): + host, bundle = open_host(tmp_path) + request = creation(bundle) + response = host.handle(request, principal="alice") + validate_public_message(response, response=True) + assert response["status"] == "committed" + assert response["receipt"] is not None + checkpoint = response["value"]["result"]["checkpoint"] + aggregate = checkpoint["root_record"]["aggregate_state"] + root = aggregate["runtimes"][0] + envelope = ds.portable_envelope("increment", "event-1", root["target_identity"], {"amount": 4}) + admit = base("admit", "operation-admit") + admit["arguments"] = { + "ordered_deliveries": [ + { + "delivery_mode": "input", + "envelope": envelope, + "envelope_digest": ds.delivery_request_digest("root", "input", envelope), + } + ] + } + admit["precondition"] = { + "revision": checkpoint["revision"], + "checkpoint_digest": checkpoint["execution_checkpoint_digest"], + } + admitted = host.handle(admit, principal="alice") + assert admitted["status"] == "committed" + checkpoint = admitted["value"]["result"]["checkpoint"] + process = base("process", "operation-process") + process["target"].update( + runtime_id=root["runtime_id"], runtime_incarnation=root["identity_origin"] + ) + process["precondition"] = { + "revision": checkpoint["revision"], + "checkpoint_digest": checkpoint["execution_checkpoint_digest"], + } + completed = host.handle(process, principal="alice") + assert completed["status"] == "committed" + assert completed["value"]["result"]["core_result"]["disposition"] == "handled" + reopened, _ = open_host(tmp_path) + assert reopened.handle(request, principal="alice") == response + assert reopened.handle(process, principal="alice") == completed + changed = copy.deepcopy(process) + changed["precondition"]["revision"] = "99" + assert reopened.handle(changed, principal="alice")["error"]["code"] == "operation_id_conflict" + observed = reopened.handle(base("read", "read-1"), principal="alice") + checkpoint = observed["value"]["result"]["checkpoint"] + count = checkpoint["root_record"]["aggregate_state"]["runtimes"][0]["variables"][0]["value"] + assert count == ["integer", "4"] + assert checkpoint["revision"] == "2" + + +def test_unauthorized_existing_and_absent_roots_have_same_denial(tmp_path): + host, bundle = open_host(tmp_path) + host.handle(creation(bundle), principal="alice") + existing = host.handle(base("read", "read-denied", "root"), principal="mallory") + absent = host.handle(base("read", "read-denied", "absent"), principal="mallory") + assert existing == absent + assert existing["error"]["code"] == "unauthorized_scope" From e690c7c9b9419ccb327237ee34d1d2f51d819357 Mon Sep 17 00:00:00 2001 From: Christian-Manuel Butzke Date: Mon, 5 Oct 2026 11:34:43 +0000 Subject: [PATCH 03/10] fix: retain idle public responses and replay creation without core calls --- conformance/test_public_host.py | 80 +++++++++++++++++++++++ src/determa/state/public_client.py | 21 ++++++ src/determa/state/public_host.py | 77 +++++++++++++++++----- tests/test_public_host.py | 100 +++++++++++++++++++++++++++++ 4 files changed, 262 insertions(+), 16 deletions(-) create mode 100644 conformance/test_public_host.py diff --git a/conformance/test_public_host.py b/conformance/test_public_host.py new file mode 100644 index 0000000..721c5e6 --- /dev/null +++ b/conformance/test_public_host.py @@ -0,0 +1,80 @@ +"""Pinned public wire goldens and executable minimal local-host profile.""" + +from __future__ import annotations + +import json +import os +from pathlib import Path + +import pytest + +from determa.state import MemoryArtifactResolver, load_bundle +from determa.state.public_client import ( + PublicHostError, + public_request_digest, + validate_public_message, +) +from determa.state.public_host import SQLitePublicExecutionHost +from determa.state.wire import canonical_bytes, hash_value + +from .harness import conformance_root + + +def goldens(name): + root = Path(os.environ["DETERMA_SPEC_DIR"]) + return json.loads((root / "examples/public-host" / name).read_text()) + + +def test_complete_public_golden_wire_and_hash_matrix(): + positive = goldens("positive-v1.json") + negative = goldens("negative-v1.json") + for case in positive["cases"]: + validate_public_message(case["request"]) + validate_public_message(case["response"], response=True) + assert public_request_digest(case["request"]) == case["request_digest"] + assert ( + canonical_bytes(["determa-public-host-request-digest-1", "1", case["request"]]).decode() + == case["request_hash_operand_jcs"] + ) + for case in negative["cases"]: + if "request_hash_operand_jcs" in case: + assert ( + hash_value(["determa-public-host-request-digest-1", "1", case["request"]]) + == case["request_digest"] + ) + if "response" in case: + validate_public_message(case["response"], response=True) + for case in negative["invalid_responses"]: + if case["expected_error"] == "schema_validation": + with pytest.raises(PublicHostError): + validate_public_message(case["response"], response=True) + + +def test_local_host_exact_public_core_goldens(tmp_path): + bundle = load_bundle( + ( + conformance_root() + / "conformance/core/119-native-v1-aggregate-integrity/root-machine.yaml" + ).read_text() + ) + host = SQLitePublicExecutionHost( + tmp_path / "host.db", + scope_alias="scope", + scope_binding_identity="binding-local-1", + authorized_principals=frozenset({"alice"}), + resolver=MemoryArtifactResolver(definitions={bundle.fingerprint: bundle}), + ) + host.setup_schema() + names = { + "create_committed", + "read_existing_checkpoint", + "inspect_absent_target_in_checkpoint", + "process_empty_mailbox", + "retained_operation_receipt", + } + observed = set() + for case in goldens("positive-v1.json")["cases"]: + if case["name"] in names: + assert host.handle(case["request"], principal="alice") == case["response"], case["name"] + observed.add(case["name"]) + assert observed == names diff --git a/src/determa/state/public_client.py b/src/determa/state/public_client.py index ecc70fa..e7e0c70 100644 --- a/src/determa/state/public_client.py +++ b/src/determa/state/public_client.py @@ -112,6 +112,15 @@ def _send(self, endpoint: str, request: dict[str, Any]) -> dict[str, Any]: validate_public_message(response, response=True) if response["operation_id"] != request["operation_id"]: raise PublicHostError("invalid_host_request") + if response["status"] == "committed": + operation = request["operation"] + required = operation in {"create", "admit", "effect_result", "cancel_effect"} + if operation == "process": + required = ( + response["value"]["result"]["core_result"]["disposition"] != "not_runnable" + ) + if required and response["receipt"] is None: + raise PublicHostError("invalid_host_request") receipt = response["receipt"] if receipt is not None: if ( @@ -146,6 +155,18 @@ def _send(self, endpoint: str, request: dict[str, Any]) -> dict[str, Any]: ["determa-public-host-profile-1", "1", profile["scope_binding_identity"], profile] ): raise PublicHostError("invalid_host_request") + if request["operation"] == "receipt" and response["status"] == "committed": + saved = response["value"]["result"]["saved_response"] + if saved is not None: + if saved["operation_id"] != request["arguments"]["queried_operation_id"]: + raise PublicHostError("invalid_host_request") + evidence = saved["receipt"] + if evidence is not None and ( + evidence["scope_binding_identity"] != request["scope_binding_identity"] + or evidence["request_digest"] != request["arguments"]["request_digest"] + or evidence["operation_id"] != saved["operation_id"] + ): + raise PublicHostError("invalid_host_request") return copy.deepcopy(response) def discover(self, name: str) -> dict[str, Any]: diff --git a/src/determa/state/public_host.py b/src/determa/state/public_host.py index 49c300e..879e802 100644 --- a/src/determa/state/public_host.py +++ b/src/determa/state/public_host.py @@ -145,6 +145,24 @@ def handle(self, request: Mapping[str, Any], *, principal: str) -> dict[str, Any return self._response(candidate, code="binding_unavailable") if operation not in _OPERATIONS: return self._response(candidate, code="host_capability_mismatch") + target = candidate["target"] + if operation == "capabilities": + applicable = all(value is None for value in target.values()) + elif operation == "receipt": + applicable = target["runtime_id"] is None and target["runtime_incarnation"] is None + elif operation in {"create", "admit", "read"}: + applicable = ( + target["root_instance_id"] is not None + and target["runtime_id"] is None + and target["runtime_incarnation"] is None + ) + else: + applicable = all(value is not None for value in target.values()) + if not applicable or ( + operation in {"capabilities", "receipt", "create"} + and candidate["precondition"] is not None + ): + return self._response(candidate, code="invalid_host_request") digest = public_request_digest(candidate) try: with self._connect() as db: @@ -176,14 +194,16 @@ def handle(self, request: Mapping[str, Any], *, principal: str) -> dict[str, Any ["determa-public-host-evidence-1", "1", response["value"]] ), } - validate_public_message(response, response=True) + validate_public_message(response, response=True) + if operation in _MUTATIONS: assert checkpoint is not None - db.execute( - "INSERT INTO determa_public_host_checkpoints VALUES (?,?) " - "ON CONFLICT(root_instance_id) DO UPDATE " - "SET checkpoint=excluded.checkpoint", - (checkpoint["root_instance_id"], canonical_bytes(checkpoint)), - ) + if changed: + db.execute( + "INSERT INTO determa_public_host_checkpoints VALUES (?,?) " + "ON CONFLICT(root_instance_id) DO UPDATE " + "SET checkpoint=excluded.checkpoint", + (checkpoint["root_instance_id"], canonical_bytes(checkpoint)), + ) db.execute( "INSERT INTO determa_public_host_responses VALUES (?,?,?,?)", ( @@ -193,8 +213,6 @@ def handle(self, request: Mapping[str, Any], *, principal: str) -> dict[str, Any canonical_bytes(response), ), ) - else: - validate_public_message(response, response=True) return response except ArtifactError as error: # Pure refusals roll back the transaction; native transaction failures @@ -203,6 +221,14 @@ def handle(self, request: Mapping[str, Any], *, principal: str) -> dict[str, Any validate_public_message(response, response=True) return response + @staticmethod + def _check_precondition(checkpoint: Mapping[str, Any], precondition: Mapping[str, Any]) -> None: + if ( + checkpoint["revision"] != precondition["revision"] + or checkpoint["execution_checkpoint_digest"] != precondition["checkpoint_digest"] + ): + raise PublicHostError("checkpoint_revision_conflict") + def _execute(self, db: sqlite3.Connection, request: dict[str, Any]) -> tuple[Any, Any, bool]: operation = request["operation"] arguments = request["arguments"] @@ -210,12 +236,18 @@ def _execute(self, db: sqlite3.Connection, request: dict[str, Any]) -> tuple[Any return self._capabilities(), None, False if operation == "receipt": row = db.execute( - "SELECT request_digest,response FROM determa_public_host_responses " + "SELECT request_digest,response,request FROM determa_public_host_responses " "WHERE operation_id=?", (arguments["queried_operation_id"],), ).fetchone() if row is not None and row[0] != arguments["request_digest"]: raise PublicHostError("operation_id_conflict") + if row is not None and request["target"]["root_instance_id"] is not None: + if ( + json.loads(row[2])["target"]["root_instance_id"] + != request["target"]["root_instance_id"] + ): + raise PublicHostError("invalid_host_request") return ( { "saved_response": None if row is None else json.loads(row[1]), @@ -238,6 +270,8 @@ def _execute(self, db: sqlite3.Connection, request: dict[str, Any]) -> tuple[Any if row is None else restore_execution_checkpoint_v1(row[0], self.resolver).document ) + if request["precondition"] is not None and checkpoint is not None: + self._check_precondition(checkpoint, request["precondition"]) return ( { "checkpoint": checkpoint, @@ -251,6 +285,20 @@ def _execute(self, db: sqlite3.Connection, request: dict[str, Any]) -> tuple[Any if operation == "create": if arguments["root_instance_id"] != root_id: raise PublicHostError("invalid_host_request") + if row is not None: + for saved_request, saved_response in db.execute( + "SELECT request,response FROM determa_public_host_responses" + ): + previous = json.loads(saved_request) + if ( + previous["operation"] == "create" + and previous["target"]["root_instance_id"] == root_id + ): + if previous["arguments"] != arguments: + raise PublicHostError("creation_id_conflict") + result = json.loads(saved_response)["value"]["result"] + return result, json.loads(row[0]), True + raise PublicHostError("replay_evidence_expired") source = self.resolver.resolve_definition(arguments["validated_bundle_fingerprint"]) if source is None or not self.resolver.definition_is_trusted( arguments["validated_bundle_fingerprint"] @@ -278,11 +326,6 @@ def _execute(self, db: sqlite3.Connection, request: dict[str, Any]) -> tuple[Any _include_projection_result=True, ) checkpoint = result["checkpoint"] - if row is not None: - existing = restore_execution_checkpoint_v1(row[0], self.resolver).document - if existing["operation_receipts"][0] != checkpoint["operation_receipts"][0]: - raise PublicHostError("creation_id_conflict") - checkpoint = existing core = result["core_result"] return ( { @@ -302,6 +345,8 @@ def _execute(self, db: sqlite3.Connection, request: dict[str, Any]) -> tuple[Any aggregate = checkpoint["root_record"].get("aggregate_state") if aggregate is None: raise PublicHostError("terminal_root") + if request["precondition"] is not None and operation == "inspect": + self._check_precondition(checkpoint, request["precondition"]) if operation == "inspect": inspection = arguments["candidate"] if ( @@ -312,7 +357,7 @@ def _execute(self, db: sqlite3.Connection, request: dict[str, Any]) -> tuple[Any outcome = inspect_candidate( aggregate, inspection, self.resolver, semantic_enabled=False ) - if outcome.get("result") == "failure": + if "code" in outcome: raise PublicHostError(outcome["code"]) return ( { diff --git a/tests/test_public_host.py b/tests/test_public_host.py index 04fbd3e..4bb3614 100644 --- a/tests/test_public_host.py +++ b/tests/test_public_host.py @@ -126,3 +126,103 @@ def test_unauthorized_existing_and_absent_roots_have_same_denial(tmp_path): absent = host.handle(base("read", "read-denied", "absent"), principal="mallory") assert existing == absent assert existing["error"]["code"] == "unauthorized_scope" + + +def test_empty_mailbox_process_replays_after_later_admission(tmp_path, monkeypatch): + host, bundle = open_host(tmp_path) + checkpoint = host.handle(creation(bundle), principal="alice")["value"]["result"]["checkpoint"] + aggregate = checkpoint["root_record"]["aggregate_state"] + root = aggregate["runtimes"][0] + idle = base("process", "idle-process") + idle["target"].update( + runtime_id=root["runtime_id"], runtime_incarnation=root["identity_origin"] + ) + idle["precondition"] = { + "revision": checkpoint["revision"], + "checkpoint_digest": checkpoint["execution_checkpoint_digest"], + } + original = host.handle(idle, principal="alice") + assert original["value"]["result"]["core_result"]["disposition"] == "not_runnable" + envelope = ds.portable_envelope("increment", "later", root["target_identity"], {"amount": 4}) + admission = base("admit", "later-admission") + admission["precondition"] = idle["precondition"] + admission["arguments"] = { + "ordered_deliveries": [ + { + "delivery_mode": "input", + "envelope": envelope, + "envelope_digest": ds.delivery_request_digest("root", "input", envelope), + } + ] + } + assert host.handle(admission, principal="alice")["status"] == "committed" + + def forbidden_core(*_args, **_kwargs): + raise AssertionError("saved process must not call core") + + monkeypatch.setattr("determa.state.public_host.step_checkpoint_v1", forbidden_core) + assert host.handle(idle, principal="alice") == original + + +def test_creation_identity_replay_under_new_operation_never_reinitializes(tmp_path, monkeypatch): + host, bundle = open_host(tmp_path) + request = creation(bundle) + first = host.handle(request, principal="alice") + + def forbidden_core(*_args, **_kwargs): + raise AssertionError("retained creation must not call core") + + monkeypatch.setattr("determa.state.public_host.create_checkpoint_v1", forbidden_core) + repeated = copy.deepcopy(request) + repeated["operation_id"] = "new-public-operation" + replay = host.handle(repeated, principal="alice") + assert replay["value"] == first["value"] + assert replay["receipt"]["operation_id"] == "new-public-operation" + + +def test_malformed_read_target_rejected_before_root_lookup(tmp_path): + host, bundle = open_host(tmp_path) + host.handle(creation(bundle), principal="alice") + request = base("read", "malformed-target") + request["target"]["runtime_id"] = "sha256:" + "f" * 64 + request["target"]["runtime_incarnation"] = { + "kind": "root", + "root_instance_id": "root", + "definition": { + "validated_bundle_fingerprint": bundle.fingerprint, + "machine": { + "namespace": "example", + "machine_id": "counter", + "machine_version": "1", + "root_definition_pointer": "/machines/0/root", + }, + }, + } + assert host.handle(request, principal="alice")["error"]["code"] == "invalid_host_request" + + +def test_client_rejects_mismatched_nested_receipt_evidence(tmp_path): + import pytest + + from determa.state.public_client import EndpointBinding, PublicHostClient, PublicHostError + + host, bundle = open_host(tmp_path) + + def transport(_endpoint, request): + response = host.handle(request, principal="alice") + if request["operation"] == "create": + raise TimeoutError("committed response lost") + if request["operation"] == "receipt": + response["value"]["result"]["saved_response"]["receipt"]["request_digest"] = ( + "sha256:" + "f" * 64 + ) + return response + + client = PublicHostClient( + tmp_path / "client.db", {"one": EndpointBinding("host", "scope")}, transport + ) + client.setup_schema() + with pytest.raises(TimeoutError): + client.submit("one", creation(bundle)) + with pytest.raises(PublicHostError, match="invalid_host_request"): + client.receipt("operation-create") From 8b57e3439ab6f0bcfe5fc371c5e08c6d01f52154 Mon Sep 17 00:00:00 2001 From: Christian-Manuel Butzke Date: Mon, 5 Oct 2026 11:36:41 +0000 Subject: [PATCH 04/10] fix: verify native public-host retention schema and commit fate --- src/determa/state/public_client.py | 42 ++++++++++-- src/determa/state/public_host.py | 101 ++++++++++++++++++++++------- tests/test_public_host.py | 74 +++++++++++++++++++++ 3 files changed, 187 insertions(+), 30 deletions(-) diff --git a/src/determa/state/public_client.py b/src/determa/state/public_client.py index e7e0c70..658d82b 100644 --- a/src/determa/state/public_client.py +++ b/src/determa/state/public_client.py @@ -10,7 +10,8 @@ import json import sqlite3 import uuid -from collections.abc import Callable, Mapping +from collections.abc import Callable, Iterator, Mapping +from contextlib import contextmanager from dataclasses import dataclass from functools import cache from pathlib import Path @@ -101,14 +102,24 @@ def setup_schema(self) -> None: "request_digest TEXT NOT NULL, response BLOB)" ) - def _connect(self) -> sqlite3.Connection: + @contextmanager + def _connect(self) -> Iterator[sqlite3.Connection]: db = sqlite3.connect(self.path) - db.execute("PRAGMA journal_mode=WAL") - db.execute("PRAGMA synchronous=FULL") - return db + try: + db.execute("PRAGMA journal_mode=WAL") + db.execute("PRAGMA synchronous=FULL") + with db: + yield db + finally: + db.close() def _send(self, endpoint: str, request: dict[str, Any]) -> dict[str, Any]: response = self.transport(endpoint, copy.deepcopy(request)) + return self._checked_response(request, response) + + def _checked_response( + self, request: dict[str, Any], response: dict[str, Any] + ) -> dict[str, Any]: validate_public_message(response, response=True) if response["operation_id"] != request["operation_id"]: raise PublicHostError("invalid_host_request") @@ -160,6 +171,21 @@ def _send(self, endpoint: str, request: dict[str, Any]) -> dict[str, Any]: if saved is not None: if saved["operation_id"] != request["arguments"]["queried_operation_id"]: raise PublicHostError("invalid_host_request") + with self._connect() as db: + local = db.execute( + "SELECT request,request_digest FROM determa_public_client_requests " + "WHERE operation_id=?", + (saved["operation_id"],), + ).fetchone() + if local is not None: + original = json.loads(local[0]) + if ( + public_request_digest(original) != local[1] + or local[1] != request["arguments"]["request_digest"] + or original["scope_binding_identity"] != request["scope_binding_identity"] + ): + raise PublicHostError("invalid_host_request") + self._checked_response(original, saved) evidence = saved["receipt"] if evidence is not None and ( evidence["scope_binding_identity"] != request["scope_binding_identity"] @@ -254,8 +280,7 @@ def retry(self, operation_id: str) -> dict[str, Any]: raise PublicHostError("invalid_host_request") if row[3] is not None: response = json.loads(row[3]) - validate_public_message(response, response=True) - return cast(dict[str, Any], response) + return self._checked_response(request, cast(dict[str, Any], response)) response = self._send(row[0], request) if response["status"] != "pending": with self._connect() as db: @@ -277,6 +302,9 @@ def receipt(self, operation_id: str) -> dict[str, Any]: if row is None: raise PublicHostError("outcome_unknown") saved = json.loads(row[1]) + if canonical_bytes(saved) != row[1] or public_request_digest(saved) != row[2]: + raise PublicHostError("invalid_host_request") + request = { "protocol": _PROTOCOL, "protocol_version": 1, diff --git a/src/determa/state/public_host.py b/src/determa/state/public_host.py index 879e802..8c82011 100644 --- a/src/determa/state/public_host.py +++ b/src/determa/state/public_host.py @@ -4,8 +4,10 @@ import copy import json +import re import sqlite3 -from collections.abc import Mapping +from collections.abc import Iterator, Mapping +from contextlib import contextmanager from pathlib import Path from typing import Any, cast @@ -24,6 +26,31 @@ _OPERATIONS = ["capabilities", "create", "admit", "process", "read", "inspect", "receipt"] _MUTATIONS = {"create", "admit", "process"} +_TABLES = { + "determa_public_host_binding": "singleton INTEGER PRIMARY KEY CHECK(singleton=1), " + "schema_version INTEGER NOT NULL CHECK(schema_version=1), scope_binding_identity TEXT NOT NULL", + "determa_public_host_checkpoints": ( + "root_instance_id TEXT PRIMARY KEY, checkpoint BLOB NOT NULL" + ), + "determa_public_host_responses": "operation_id TEXT PRIMARY KEY, request BLOB NOT NULL, " + "request_digest TEXT NOT NULL, response BLOB NOT NULL", +} +_TRIGGERS = { + f"{table}_forbid_{action.lower()}": f"CREATE TRIGGER {table}_forbid_{action.lower()} " + f"BEFORE {action} ON {table} " + "BEGIN SELECT RAISE(ABORT,'public_host_immutable'); END" + for table, actions in { + "determa_public_host_binding": ("INSERT", "UPDATE", "DELETE"), + "determa_public_host_checkpoints": ("DELETE",), + "determa_public_host_responses": ("UPDATE", "DELETE"), + }.items() + for action in actions +} + + +def _sql_tokens(source: str) -> list[str]: + return re.findall(r"[a-z_][a-z0-9_]*|[0-9]+|[(),=]", source.lower()) + class SQLitePublicExecutionHost: """One authenticated logical scope in one explicitly initialized SQLite file. @@ -55,37 +82,65 @@ def __init__( self.authorized_principals = frozenset(authorized_principals) self.resolver = resolver - def _connect(self) -> sqlite3.Connection: + @contextmanager + def _connect(self) -> Iterator[sqlite3.Connection]: db = sqlite3.connect(self.path) - db.execute("PRAGMA journal_mode=WAL") - db.execute("PRAGMA synchronous=FULL") - return db + try: + db.execute("PRAGMA journal_mode=WAL") + db.execute("PRAGMA synchronous=FULL") + if ( + db.execute("PRAGMA journal_mode").fetchone()[0] != "wal" + or db.execute("PRAGMA synchronous").fetchone()[0] != 2 + ): + raise PublicHostError("host_capability_mismatch") + with db: + yield db + finally: + db.close() def setup_schema(self) -> None: + """Create the exact local schema without replacing prior scope evidence.""" with self._connect() as db: db.execute("BEGIN IMMEDIATE") + for name, columns in _TABLES.items(): + db.execute(f"CREATE TABLE IF NOT EXISTS {name} ({columns})") + if not db.execute("SELECT 1 FROM determa_public_host_binding").fetchone(): + db.execute( + "INSERT INTO determa_public_host_binding VALUES (1,1,?)", + (self.scope_binding_identity,), + ) + for name, definition in _TRIGGERS.items(): + existing = db.execute( + "SELECT sql FROM sqlite_master WHERE type='trigger' AND name=?", (name,) + ).fetchone() + if existing is None: + db.execute(definition) + self._check_binding(db) + + @staticmethod + def _check_schema(db: sqlite3.Connection) -> None: + for name, columns in _TABLES.items(): + row = db.execute( + "SELECT sql FROM sqlite_master WHERE type='table' AND name=?", (name,) + ).fetchone() + if row is None or _sql_tokens(row[0]) != _sql_tokens( + f"CREATE TABLE {name} ({columns})" + ): + raise PublicHostError("host_capability_mismatch") + actual = dict( db.execute( - "CREATE TABLE IF NOT EXISTS determa_public_host_binding (" - "singleton INTEGER PRIMARY KEY CHECK(singleton=1), " - "schema_version INTEGER NOT NULL CHECK(schema_version=1), " - "scope_binding_identity TEXT NOT NULL)" - ) - db.execute( - "CREATE TABLE IF NOT EXISTS determa_public_host_checkpoints (" - "root_instance_id TEXT PRIMARY KEY, checkpoint BLOB NOT NULL)" - ) - db.execute( - "CREATE TABLE IF NOT EXISTS determa_public_host_responses (" - "operation_id TEXT PRIMARY KEY, request BLOB NOT NULL, " - "request_digest TEXT NOT NULL, response BLOB NOT NULL)" - ) - db.execute( - "INSERT OR IGNORE INTO determa_public_host_binding VALUES (1,1,?)", - (self.scope_binding_identity,), + "SELECT name,sql FROM sqlite_master WHERE type='trigger' AND tbl_name IN (?,?,?)", + tuple(_TABLES), ) - self._check_binding(db) + ) + if set(actual) != set(_TRIGGERS) or any( + _sql_tokens(actual[name]) != _sql_tokens(definition) + for name, definition in _TRIGGERS.items() + ): + raise PublicHostError("host_capability_mismatch") def _check_binding(self, db: sqlite3.Connection) -> None: + self._check_schema(db) rows = db.execute( "SELECT singleton,schema_version,scope_binding_identity " "FROM determa_public_host_binding" diff --git a/tests/test_public_host.py b/tests/test_public_host.py index 4bb3614..6ff6501 100644 --- a/tests/test_public_host.py +++ b/tests/test_public_host.py @@ -226,3 +226,77 @@ def transport(_endpoint, request): client.submit("one", creation(bundle)) with pytest.raises(PublicHostError, match="invalid_host_request"): client.receipt("operation-create") + + +def test_native_response_insert_failure_rolls_back_checkpoint_and_ledger(tmp_path, monkeypatch): + import sqlite3 + + import pytest + + host, bundle = open_host(tmp_path) + original_connect = sqlite3.connect + + class FailingConnection(sqlite3.Connection): + def execute(self, sql, parameters=()): + if sql.startswith("INSERT INTO determa_public_host_responses"): + raise sqlite3.OperationalError("injected response write failure") + return super().execute(sql, parameters) + + monkeypatch.setattr( + "determa.state.public_host.sqlite3.connect", + lambda path: original_connect(path, factory=FailingConnection), + ) + with pytest.raises(sqlite3.OperationalError): + host.handle(creation(bundle), principal="alice") + monkeypatch.undo() + observed = host.handle(base("read", "after-failure"), principal="alice") + assert observed["value"]["result"]["checkpoint"] is None + with original_connect(host.path) as db: + assert db.execute("SELECT COUNT(*) FROM determa_public_host_responses").fetchone()[0] == 0 + + +def test_native_commit_with_lost_fate_reconciles_to_first_response(tmp_path, monkeypatch): + import sqlite3 + + import pytest + + host, bundle = open_host(tmp_path) + original_connect = sqlite3.connect + + class LostCommitConnection(sqlite3.Connection): + def __exit__(self, *args): + result = super().__exit__(*args) + if args[0] is None: + raise sqlite3.OperationalError("commit response lost after native commit") + return result + + monkeypatch.setattr( + "determa.state.public_host.sqlite3.connect", + lambda path: original_connect(path, factory=LostCommitConnection), + ) + request = creation(bundle) + with pytest.raises(sqlite3.OperationalError): + host.handle(request, principal="alice") + monkeypatch.undo() + + def forbidden_core(*_args, **_kwargs): + raise AssertionError("unknown native commit must reconcile without core execution") + + monkeypatch.setattr("determa.state.public_host.create_checkpoint_v1", forbidden_core) + result = host.handle(request, principal="alice") + assert result["status"] == "committed" + assert result["receipt"] is not None + with original_connect(host.path) as db: + assert db.execute("SELECT COUNT(*) FROM determa_public_host_responses").fetchone()[0] == 1 + + +def test_changed_native_schema_refuses_before_mutation(tmp_path): + import sqlite3 + + host, bundle = open_host(tmp_path) + with sqlite3.connect(host.path) as db: + db.execute("DROP TRIGGER determa_public_host_responses_forbid_delete") + response = host.handle(creation(bundle), principal="alice") + assert response["error"]["code"] == "host_capability_mismatch" + with sqlite3.connect(host.path) as db: + assert db.execute("SELECT COUNT(*) FROM determa_public_host_checkpoints").fetchone()[0] == 0 From be971f9e21151fbf689ed2e086439d3e2ea8f32a Mon Sep 17 00:00:00 2001 From: Christian-Manuel Butzke Date: Mon, 5 Oct 2026 11:40:23 +0000 Subject: [PATCH 05/10] fix: keep discovery reports isolated and reject ephemeral journal paths --- src/determa/state/public_client.py | 9 +++++++-- src/determa/state/public_host.py | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/determa/state/public_client.py b/src/determa/state/public_client.py index 658d82b..8961aa7 100644 --- a/src/determa/state/public_client.py +++ b/src/determa/state/public_client.py @@ -68,7 +68,12 @@ class EndpointBinding: scope_alias: str def __post_init__(self) -> None: - if not self.endpoint or not self.scope_alias: + if ( + not isinstance(self.endpoint, str) + or not self.endpoint + or not isinstance(self.scope_alias, str) + or not self.scope_alias + ): raise PublicHostError("binding_unavailable") @@ -86,7 +91,7 @@ def __init__( bindings: Mapping[str, EndpointBinding], transport: Callable[[str, dict[str, Any]], dict[str, Any]], ) -> None: - if str(path) == ":memory:": + if not str(path) or str(path) == ":memory:": raise PublicHostError("binding_unavailable") self.path = str(path) self.bindings = dict(bindings) diff --git a/src/determa/state/public_host.py b/src/determa/state/public_host.py index 8c82011..a2c5c18 100644 --- a/src/determa/state/public_host.py +++ b/src/determa/state/public_host.py @@ -70,7 +70,7 @@ def __init__( resolver: DefinitionResolver, ) -> None: if ( - str(path) == ":memory:" + (not str(path) or str(path) == ":memory:") or not scope_alias or not scope_binding_identity or not authorized_principals @@ -165,7 +165,7 @@ def _response( def _capabilities(self) -> dict[str, Any]: result = { "scope_binding_identity": self.scope_binding_identity, - "supported_operations": _OPERATIONS, + "supported_operations": list(_OPERATIONS), "supported_scope_actions": [], "supported_determa_capabilities": [], "supported_timer_commands": [], From d41432f2787a7f167daf3df3b7774c26a8aad303 Mon Sep 17 00:00:00 2001 From: Christian-Manuel Butzke Date: Mon, 5 Oct 2026 11:46:35 +0000 Subject: [PATCH 06/10] Preserve SQLite constraint operators in public host schema checks --- src/determa/state/public_host.py | 13 ++++++++++-- tests/test_public_host.py | 34 ++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/determa/state/public_host.py b/src/determa/state/public_host.py index a2c5c18..fa6f279 100644 --- a/src/determa/state/public_host.py +++ b/src/determa/state/public_host.py @@ -49,7 +49,11 @@ def _sql_tokens(source: str) -> list[str]: - return re.findall(r"[a-z_][a-z0-9_]*|[0-9]+|[(),=]", source.lower()) + tokens = re.findall( + r"'(?:(?:'')|[^'])*'|\"(?:(?:\"\")|[^\"])*\"|[A-Za-z_][A-Za-z0-9_]*|[0-9]+|[^\s]", + source, + ) + return [token if token.startswith(("'", '"')) else token.lower() for token in tokens] class SQLitePublicExecutionHost: @@ -104,6 +108,7 @@ def setup_schema(self) -> None: db.execute("BEGIN IMMEDIATE") for name, columns in _TABLES.items(): db.execute(f"CREATE TABLE IF NOT EXISTS {name} ({columns})") + self._check_tables(db) if not db.execute("SELECT 1 FROM determa_public_host_binding").fetchone(): db.execute( "INSERT INTO determa_public_host_binding VALUES (1,1,?)", @@ -118,7 +123,7 @@ def setup_schema(self) -> None: self._check_binding(db) @staticmethod - def _check_schema(db: sqlite3.Connection) -> None: + def _check_tables(db: sqlite3.Connection) -> None: for name, columns in _TABLES.items(): row = db.execute( "SELECT sql FROM sqlite_master WHERE type='table' AND name=?", (name,) @@ -127,6 +132,10 @@ def _check_schema(db: sqlite3.Connection) -> None: f"CREATE TABLE {name} ({columns})" ): raise PublicHostError("host_capability_mismatch") + + @staticmethod + def _check_schema(db: sqlite3.Connection) -> None: + SQLitePublicExecutionHost._check_tables(db) actual = dict( db.execute( "SELECT name,sql FROM sqlite_master WHERE type='trigger' AND tbl_name IN (?,?,?)", diff --git a/tests/test_public_host.py b/tests/test_public_host.py index 6ff6501..ad4cd9a 100644 --- a/tests/test_public_host.py +++ b/tests/test_public_host.py @@ -2,6 +2,8 @@ import copy +import pytest + import determa.state as ds from determa.state.public_client import validate_public_message from determa.state.public_host import SQLitePublicExecutionHost @@ -300,3 +302,35 @@ def test_changed_native_schema_refuses_before_mutation(tmp_path): assert response["error"]["code"] == "host_capability_mismatch" with sqlite3.connect(host.path) as db: assert db.execute("SELECT COUNT(*) FROM determa_public_host_checkpoints").fetchone()[0] == 0 + + +@pytest.mark.parametrize("column", ["singleton", "schema_version"]) +@pytest.mark.parametrize("operator", [">=", "!=", "<=", "<>"]) +def test_setup_rejects_changed_constraint_operators_before_binding_write( + tmp_path, column, operator +): + import sqlite3 + + from determa.state.public_client import PublicHostError + from determa.state.public_host import _TABLES + + path = tmp_path / "host.db" + definition = _TABLES["determa_public_host_binding"].replace( + f"CHECK({column}=1)", f"CHECK({column}{operator}1)" + ) + with sqlite3.connect(path) as db: + db.execute(f"CREATE TABLE determa_public_host_binding ({definition})") + with pytest.raises(PublicHostError, match="host_capability_mismatch"): + open_host(tmp_path) + with sqlite3.connect(path) as db: + assert db.execute("SELECT COUNT(*) FROM determa_public_host_binding").fetchone()[0] == 0 + + +def test_schema_comparison_preserves_literal_case_and_operator_tokens(): + from determa.state.public_host import _sql_tokens + + assert _sql_tokens("CHECK (singleton = 1)") == _sql_tokens("check(singleton=1)") + assert _sql_tokens("CHECK(singleton>=1)") != _sql_tokens("CHECK(singleton=1)") + assert _sql_tokens("RAISE(ABORT,'PUBLIC_HOST_IMMUTABLE')") != _sql_tokens( + "RAISE(ABORT,'public_host_immutable')" + ) From 4d0135e793f5c5409d14bfdf14ecdc5c97e6ff37 Mon Sep 17 00:00:00 2001 From: Christian-Manuel Butzke Date: Mon, 5 Oct 2026 11:48:07 +0000 Subject: [PATCH 07/10] Execute public delivery goldens and preserve deferral step identity --- conformance/test_public_host.py | 37 ++++++++++++++++++++++++++++++ src/determa/state/checkpoint_v1.py | 5 +++- src/determa/state/queueing.py | 3 --- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/conformance/test_public_host.py b/conformance/test_public_host.py index 721c5e6..0890e15 100644 --- a/conformance/test_public_host.py +++ b/conformance/test_public_host.py @@ -78,3 +78,40 @@ def test_local_host_exact_public_core_goldens(tmp_path): assert host.handle(case["request"], principal="alice") == case["response"], case["name"] observed.add(case["name"]) assert observed == names + + +@pytest.mark.parametrize( + ("name", "fixture", "initial"), + [ + ("admit_declared_event", "execution-checkpoint-transfer-v1.json", "before_admission"), + ("process_unhandled_event", "execution-checkpoint-transfer-v1.json", "after_admission"), + ("process_deferred_event", "queue-placement-checkpoints-v1.json", "after_second_admission"), + ("process_recall_event", "queue-placement-checkpoints-v1.json", "after_received_admission"), + ], +) +def test_local_host_exact_delivery_goldens(tmp_path, name, fixture, initial): + import sqlite3 + + from determa.state import restore_execution_checkpoint_v1 + + spec = Path(os.environ["DETERMA_SPEC_DIR"]) + bundle = load_bundle((spec / "examples/portable-event-deferral.yaml").read_text()) + resolver = MemoryArtifactResolver(definitions={bundle.fingerprint: bundle}) + checkpoint = json.loads((spec / "examples/delivery" / fixture).read_text())[initial] + restore_execution_checkpoint_v1(checkpoint, resolver) + host = SQLitePublicExecutionHost( + tmp_path / "host.db", + scope_alias="scope", + scope_binding_identity="binding-local-1", + authorized_principals=frozenset({"alice"}), + resolver=resolver, + ) + host.setup_schema() + with sqlite3.connect(host.path) as db: + db.execute( + "INSERT INTO determa_public_host_checkpoints VALUES (?,?)", + (checkpoint["root_instance_id"], canonical_bytes(checkpoint)), + ) + case = next(case for case in goldens("positive-v1.json")["cases"] if case["name"] == name) + assert host.handle(case["request"], principal="alice") == case["response"] + assert host.handle(case["request"], principal="alice") == case["response"] diff --git a/src/determa/state/checkpoint_v1.py b/src/determa/state/checkpoint_v1.py index 333118b..e2ebbe8 100644 --- a/src/determa/state/checkpoint_v1.py +++ b/src/determa/state/checkpoint_v1.py @@ -960,7 +960,10 @@ def step_checkpoint_v1( candidate["root_record"]["aggregate_state"] = result["state"] if result["disposition"] == "deferred": _synchronize_mailbox_references(candidate) - return seal_execution_checkpoint(candidate) + sealed = seal_execution_checkpoint(candidate) + if _include_host_response: + return {"checkpoint": sealed, "core_result": copy.deepcopy(result), "receipt": None} + return sealed receipt_sequence = candidate["next_operation_receipt_sequence"] lifecycle_sequences = [ str(int(receipt_sequence) + index + 1) diff --git a/src/determa/state/queueing.py b/src/determa/state/queueing.py index c3054fc..0fac8ce 100644 --- a/src/determa/state/queueing.py +++ b/src/determa/state/queueing.py @@ -946,9 +946,6 @@ def step_aggregate_v1( moved["deferral_count"] = str(int(moved["deferral_count"]) + 1) deferred.append(moved) candidate = copy.deepcopy(before) - candidate["next_logical_step_sequence"] = str( - int(candidate["next_logical_step_sequence"]) + 1 - ) candidate["next_queue_sequence"] = str(int(candidate["next_queue_sequence"]) + 1) for runtime in candidate["runtimes"]: if runtime["runtime_id"] == target_runtime_id: From 46cfd8a74351475c2445749c26dbd9d2fa70a6c0 Mon Sep 17 00:00:00 2001 From: Christian-Manuel Butzke Date: Mon, 5 Oct 2026 11:58:09 +0000 Subject: [PATCH 08/10] Protect durable public client requests and first responses in native SQLite --- src/determa/state/public_client.py | 69 +++++++++++++++++++++++++++--- src/determa/state/public_host.py | 16 +++---- tests/test_public_client.py | 41 ++++++++++++++++++ 3 files changed, 110 insertions(+), 16 deletions(-) diff --git a/src/determa/state/public_client.py b/src/determa/state/public_client.py index 8961aa7..7c9deb6 100644 --- a/src/determa/state/public_client.py +++ b/src/determa/state/public_client.py @@ -8,6 +8,7 @@ import copy import json +import re import sqlite3 import uuid from collections.abc import Callable, Iterator, Mapping @@ -23,6 +24,30 @@ _DATA = Path(__file__).with_name("data") _PROTOCOL = "determa.execution_host" _READS = {"capabilities", "read", "inspect", "receipt"} +_CLIENT_TABLE = ( + "CREATE TABLE determa_public_client_requests (" + "operation_id TEXT PRIMARY KEY, binding_name TEXT NOT NULL, " + "endpoint TEXT NOT NULL, request BLOB NOT NULL, request_digest TEXT NOT NULL, response BLOB)" +) +_CLIENT_TRIGGERS = { + "determa_public_client_forbid_delete": "CREATE TRIGGER determa_public_client_forbid_delete " + "BEFORE DELETE ON determa_public_client_requests " + "BEGIN SELECT RAISE(ABORT,'public_client_immutable'); END", + "determa_public_client_guard_update": "CREATE TRIGGER determa_public_client_guard_update " + "BEFORE UPDATE ON determa_public_client_requests WHEN " + "OLD.operation_id IS NOT NEW.operation_id OR OLD.binding_name IS NOT NEW.binding_name OR " + "OLD.endpoint IS NOT NEW.endpoint OR OLD.request IS NOT NEW.request OR " + "OLD.request_digest IS NOT NEW.request_digest OR OLD.response IS NOT NULL OR " + "NEW.response IS NULL BEGIN SELECT RAISE(ABORT,'public_client_immutable'); END", +} + + +def _sql_tokens(source: str) -> list[str]: + tokens = re.findall( + r"'(?:(?:'')|[^'])*'|\"(?:(?:\"\")|[^\"])*\"|[A-Za-z_][A-Za-z0-9_]*|[0-9]+|[^\s]", + source, + ) + return [token if token.startswith(("'", '"')) else token.lower() for token in tokens] class PublicHostError(ArtifactError): @@ -99,21 +124,53 @@ def __init__( def setup_schema(self) -> None: """Explicitly create a durable client request journal.""" - with self._connect() as db: + with self._connect(setup=True) as db: + db.execute("BEGIN IMMEDIATE") + db.execute(_CLIENT_TABLE.replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS", 1)) + for name, definition in _CLIENT_TRIGGERS.items(): + if not db.execute( + "SELECT 1 FROM sqlite_master WHERE type='trigger' AND name=?", (name,) + ).fetchone(): + db.execute(definition) + self._check_schema(db) + + @staticmethod + def _check_schema(db: sqlite3.Connection) -> None: + row = db.execute( + "SELECT sql FROM sqlite_master WHERE type='table' " + "AND name='determa_public_client_requests'" + ).fetchone() + triggers = dict( db.execute( - "CREATE TABLE IF NOT EXISTS determa_public_client_requests (" - "operation_id TEXT PRIMARY KEY, binding_name TEXT NOT NULL, " - "endpoint TEXT NOT NULL, request BLOB NOT NULL, " - "request_digest TEXT NOT NULL, response BLOB)" + "SELECT name,sql FROM sqlite_master WHERE type='trigger' " + "AND tbl_name='determa_public_client_requests'" ) + ) + if ( + row is None + or _sql_tokens(row[0]) != _sql_tokens(_CLIENT_TABLE) + or set(triggers) != set(_CLIENT_TRIGGERS) + or any( + _sql_tokens(triggers[name]) != _sql_tokens(definition) + for name, definition in _CLIENT_TRIGGERS.items() + ) + ): + raise PublicHostError("host_capability_mismatch") @contextmanager - def _connect(self) -> Iterator[sqlite3.Connection]: + def _connect(self, *, setup: bool = False) -> Iterator[sqlite3.Connection]: db = sqlite3.connect(self.path) try: db.execute("PRAGMA journal_mode=WAL") db.execute("PRAGMA synchronous=FULL") + if ( + db.execute("PRAGMA journal_mode").fetchone()[0] != "wal" + or db.execute("PRAGMA synchronous").fetchone()[0] != 2 + ): + raise PublicHostError("host_capability_mismatch") with db: + if not setup: + self._check_schema(db) yield db finally: db.close() diff --git a/src/determa/state/public_host.py b/src/determa/state/public_host.py index fa6f279..767c465 100644 --- a/src/determa/state/public_host.py +++ b/src/determa/state/public_host.py @@ -4,7 +4,6 @@ import copy import json -import re import sqlite3 from collections.abc import Iterator, Mapping from contextlib import contextmanager @@ -20,7 +19,12 @@ from .definition import Bundle, load_bundle from .errors import ArtifactError from .inspection import inspect_candidate -from .public_client import PublicHostError, public_request_digest, validate_public_message +from .public_client import ( + PublicHostError, + _sql_tokens, + public_request_digest, + validate_public_message, +) from .wire import DefinitionResolver, canonical_bytes, decoded_typed_value, hash_value _OPERATIONS = ["capabilities", "create", "admit", "process", "read", "inspect", "receipt"] @@ -48,14 +52,6 @@ } -def _sql_tokens(source: str) -> list[str]: - tokens = re.findall( - r"'(?:(?:'')|[^'])*'|\"(?:(?:\"\")|[^\"])*\"|[A-Za-z_][A-Za-z0-9_]*|[0-9]+|[^\s]", - source, - ) - return [token if token.startswith(("'", '"')) else token.lower() for token in tokens] - - class SQLitePublicExecutionHost: """One authenticated logical scope in one explicitly initialized SQLite file. diff --git a/tests/test_public_client.py b/tests/test_public_client.py index 9e553f1..b3e627b 100644 --- a/tests/test_public_client.py +++ b/tests/test_public_client.py @@ -146,3 +146,44 @@ def forbidden_transport(*_args): client.retry("missing") with pytest.raises(PublicHostError, match="outcome_unknown"): client.receipt("missing") + + +def test_native_client_journal_preserves_request_and_first_response(tmp_path): + import sqlite3 + + client = PublicHostClient( + tmp_path / "client.db", + {"one": EndpointBinding("endpoint", "scope")}, + lambda _endpoint, candidate: ( + capabilities(candidate) + if candidate["operation"] == "capabilities" + else rejected(candidate) + ), + ) + client.setup_schema() + response = client.submit("one", request()) + with sqlite3.connect(client.path) as db: + for mutation in ( + "DELETE FROM determa_public_client_requests", + "UPDATE determa_public_client_requests SET endpoint='replacement'", + "UPDATE determa_public_client_requests SET request=X'00'", + "UPDATE determa_public_client_requests SET response=NULL", + ): + with pytest.raises(sqlite3.IntegrityError, match="public_client_immutable"): + db.execute(mutation) + assert client.retry("create-1") == response + + +def test_client_with_missing_retention_guard_refuses_before_transport(tmp_path): + import sqlite3 + + client = PublicHostClient( + tmp_path / "client.db", + {"one": EndpointBinding("endpoint", "scope")}, + lambda *_args: pytest.fail("changed native storage must refuse before transport"), + ) + client.setup_schema() + with sqlite3.connect(client.path) as db: + db.execute("DROP TRIGGER determa_public_client_guard_update") + with pytest.raises(PublicHostError, match="host_capability_mismatch"): + client.submit("one", request()) From a48e6dd2c30f9dd797f0143beaca41f81d89d680 Mon Sep 17 00:00:00 2001 From: Christian-Manuel Butzke Date: Mon, 5 Oct 2026 12:00:34 +0000 Subject: [PATCH 09/10] Pin reviewed deferral conformance and document public client host profile --- .github/workflows/test.yml | 6 +++--- AGENTS.md | 2 +- README.md | 19 ++++++++++++++++++- conformance/pins.py | 2 +- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 94dc77a..b2fbdbc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,7 +53,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: fruwehq/determa-state-conformance - ref: 7f09321fb483a22eb677a4342f8d9537a7a18e82 + ref: affe3fe3bcc4d13fa7c5374471568e94af36f0d1 path: .pinned/determa-state-conformance - name: Check out pinned specification uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -88,7 +88,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: fruwehq/determa-state-conformance - ref: 7f09321fb483a22eb677a4342f8d9537a7a18e82 + ref: affe3fe3bcc4d13fa7c5374471568e94af36f0d1 path: .pinned/determa-state-conformance - name: Check out pinned specification uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -126,7 +126,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: fruwehq/determa-state-conformance - ref: 7f09321fb483a22eb677a4342f8d9537a7a18e82 + ref: affe3fe3bcc4d13fa7c5374471568e94af36f0d1 path: .pinned/determa-state-conformance - name: Check out pinned specification uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/AGENTS.md b/AGENTS.md index b126b5d..cacab48 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,7 +12,7 @@ The implementation is conformant only when it passes the language-neutral suite. The implementation uses these immutable inputs: - specification: `86bb88dd21cb1f799eefe5020b6e49dabf6e7225`; -- conformance: `7f09321fb483a22eb677a4342f8d9537a7a18e82` (99 format-1 +- conformance: `affe3fe3bcc4d13fa7c5374471568e94af36f0d1` (99 format-1 core cases, 162 version-1 vectors, 142 durable-host vectors, 49 core inspection vectors, 47 extension negotiation vectors, 7 optional native-provider vectors, 30 runtime-provider vectors, and 484 JSON artifacts). diff --git a/README.md b/README.md index 4dcc399..fa3e486 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ commit `86bb88dd21cb1f799eefe5020b6e49dabf6e7225`. Correctness is determined by 99 format-1 core cases, 162 version-1 vectors, 142 durable-host vectors, 49 core inspection vectors, 47 extension negotiation vectors, 7 native guard-provider inspection vectors, 30 runtime-provider vectors, and 484 manifest artifacts at -conformance commit `7f09321fb483a22eb677a4342f8d9537a7a18e82`. The native +conformance commit `affe3fe3bcc4d13fa7c5374471568e94af36f0d1`. The native runtime-provider profile is optional and is exercised through its production adapter. The package metadata is `0.3.0`. Artifact and checkpoint schema version 1 is the @@ -35,6 +35,23 @@ returns its receipt before current row input is mapped. The optional aggregate-o step path uses the mapping's `reconstruct_aggregate` and `project_aggregate` methods when no checkpoint exists. +`determa.state.public_client.PublicHostClient` binds deployment names to +`EndpointBinding(endpoint, scope_alias)` values. Initialize its SQLite journal with +`setup_schema()` and supply an authenticated transport callable. Before sending a +mutation it discovers the immutable scope binding and saves the complete request, +request digest and endpoint. `retry(operation_id)` and `receipt(operation_id)` use +that saved endpoint after restart, including when a deployment alias has changed. +A transport exception leaves the outcome unknown; it does not prove rollback. + +`determa.state.public_host.SQLitePublicExecutionHost` implements the closed public +version-1 capabilities, create, admit, process, read, structural inspect and receipt +operations for one local scope. The transport authenticates a principal; the host +authorizes it before root or receipt lookup. Explicit schema setup verifies native +SQLite tables and retention triggers. A mutation commits its checkpoint and complete +first public response in one transaction; equal retries return that response before +calling the core or resolving definitions. This local profile advertises no authority, +native effects, timers, archive or recovery operations. + ## Install To try the `0.3.0` release candidate before publication, install it from a checkout: diff --git a/conformance/pins.py b/conformance/pins.py index ea356eb..c083a0c 100644 --- a/conformance/pins.py +++ b/conformance/pins.py @@ -4,7 +4,7 @@ from pathlib import Path -CONFORMANCE_COMMIT = "7f09321fb483a22eb677a4342f8d9537a7a18e82" +CONFORMANCE_COMMIT = "affe3fe3bcc4d13fa7c5374471568e94af36f0d1" SPEC_COMMIT = "86bb88dd21cb1f799eefe5020b6e49dabf6e7225" ROOT = Path(__file__).resolve().parent.parent From 77a05281584b756379c68ffd69af0a892d98838d Mon Sep 17 00:00:00 2001 From: Christian-Manuel Butzke Date: Mon, 5 Oct 2026 12:08:29 +0000 Subject: [PATCH 10/10] Report expired public admission evidence without partial batch commit --- src/determa/state/public_host.py | 21 +++++++---- tests/test_public_host.py | 65 ++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/src/determa/state/public_host.py b/src/determa/state/public_host.py index 767c465..4aa5569 100644 --- a/src/determa/state/public_host.py +++ b/src/determa/state/public_host.py @@ -446,15 +446,20 @@ def _execute(self, db: sqlite3.Connection, request: dict[str, Any]) -> tuple[Any updated = result.get("checkpoint", result) if "execution_checkpoint_digest" not in updated: updated = checkpoint - receipts = [ - next( - r - for r in updated["operation_receipts"] - if r["operation_kind"] == "acceptance" - and r["event_id"] == d["envelope"]["event_id"] + receipts = [] + for delivery in deliveries: + receipt = next( + ( + r + for r in updated["operation_receipts"] + if r["operation_kind"] == "acceptance" + and r["event_id"] == delivery["envelope"]["event_id"] + ), + None, ) - for d in deliveries - ] + if receipt is None: + raise PublicHostError("replay_evidence_expired") + receipts.append(receipt) root = next( r for r in updated["root_record"]["aggregate_state"]["runtimes"] diff --git a/tests/test_public_host.py b/tests/test_public_host.py index ad4cd9a..a223424 100644 --- a/tests/test_public_host.py +++ b/tests/test_public_host.py @@ -334,3 +334,68 @@ def test_schema_comparison_preserves_literal_case_and_operator_tokens(): assert _sql_tokens("RAISE(ABORT,'PUBLIC_HOST_IMMUTABLE')") != _sql_tokens( "RAISE(ABORT,'public_host_immutable')" ) + + +@pytest.mark.parametrize("mixed", [False, True]) +def test_pruned_acceptance_replay_refuses_without_committing_fresh_members(tmp_path, mixed): + import sqlite3 + + from determa.state.wire import canonical_bytes + + host, bundle = open_host(tmp_path) + checkpoint = ds.create_checkpoint_v1(bundle, "counter", "root", "root:create") + root = checkpoint["root_record"]["aggregate_state"]["runtimes"][0] + + def delivery(event_id): + envelope = ds.portable_envelope( + "increment", event_id, root["target_identity"], {"amount": 4} + ) + return { + "delivery_mode": "input", + "envelope": envelope, + "envelope_digest": ds.delivery_request_digest("root", "input", envelope), + } + + original = delivery("retained-event") + checkpoint = ds.admit_checkpoint_v1( + checkpoint, + [original], + host.resolver, + expected_revision=checkpoint["revision"], + expected_checkpoint_digest=checkpoint["execution_checkpoint_digest"], + ) + checkpoint = ds.step_checkpoint_v1( + checkpoint, + root["runtime_id"], + host.resolver, + expected_revision=checkpoint["revision"], + expected_checkpoint_digest=checkpoint["execution_checkpoint_digest"], + ) + checkpoint = ds.prune_checkpoint_v1( + checkpoint, + str(int(checkpoint["next_operation_receipt_sequence"]) - 1), + host.resolver, + target_mode="bounded", + policy_identifier="test-retention", + expected_revision=checkpoint["revision"], + expected_checkpoint_digest=checkpoint["execution_checkpoint_digest"], + ) + encoded = canonical_bytes(checkpoint) + with sqlite3.connect(host.path) as db: + db.execute("INSERT INTO determa_public_host_checkpoints VALUES (?,?)", ("root", encoded)) + request = base("admit", "new-public-operation") + request["precondition"] = { + "revision": checkpoint["revision"], + "checkpoint_digest": checkpoint["execution_checkpoint_digest"], + } + request["arguments"] = { + "ordered_deliveries": [original, delivery("fresh-event")] if mixed else [original] + } + response = host.handle(request, principal="alice") + assert response["error"]["code"] == "replay_evidence_expired" + validate_public_message(response, response=True) + with sqlite3.connect(host.path) as db: + assert db.execute("SELECT checkpoint FROM determa_public_host_checkpoints").fetchone()[ + 0 + ] == (encoded) + assert db.execute("SELECT COUNT(*) FROM determa_public_host_responses").fetchone()[0] == 0