From 5cd9e2dcfe4843ef7bc316e0632d91384af48ecd Mon Sep 17 00:00:00 2001 From: abrichr Date: Sat, 29 Aug 2026 14:38:20 -0400 Subject: [PATCH] feat(execute): bind Seal fields on the evidence receipt The receipt stays ExecuteEvidenceReceiptV1. It now carries program version, admission, environment, runner, oracle tier, and nonce so a consumer can treat it as a Seal without a second format. verified requires oracle tier 2. --- openadapt_types/__init__.py | 4 ++ openadapt_types/execute.py | 34 ++++++++++++++- .../schemas/execute-v1-openapi.json | 43 ++++++++++++++++++- tests/test_execute_contract.py | 40 +++++++++++++++++ 4 files changed, 119 insertions(+), 2 deletions(-) diff --git a/openadapt_types/__init__.py b/openadapt_types/__init__.py index 5306572..01e8182 100644 --- a/openadapt_types/__init__.py +++ b/openadapt_types/__init__.py @@ -120,6 +120,8 @@ EXECUTE_STATUS_SCHEMA, EXECUTE_WEBHOOK_SCHEMA, EffectStrengthV1, + OracleTierV1, + oracle_tier_from_effect_strength, ExecuteAcceptedV1, ExecuteAuthorizationContextV1, ExecuteDecisionRequiredWebhookV1, @@ -298,6 +300,8 @@ "EXECUTE_STATUS_SCHEMA", "EXECUTE_WEBHOOK_SCHEMA", "EffectStrengthV1", + "OracleTierV1", + "oracle_tier_from_effect_strength", "ExecuteAcceptedV1", "ExecuteAuthorizationContextV1", "ExecuteDecisionRequiredWebhookV1", diff --git a/openadapt_types/execute.py b/openadapt_types/execute.py index 11707ad..8a8ec36 100644 --- a/openadapt_types/execute.py +++ b/openadapt_types/execute.py @@ -70,6 +70,22 @@ class EffectStrengthV1(str, Enum): EffectStrengthV1.INDEPENDENT_SYSTEM_OF_RECORD: 4, } +# Seal oracle ladder. 0 visual, 1 second-session UI, 2 SoR, 3 counterparty. +# Production Seals require 2 or 3. Tier 3 has no EffectStrengthV1 member yet. +OracleTierV1: TypeAlias = Literal[0, 1, 2, 3] + + +def oracle_tier_from_effect_strength( + strength: EffectStrengthV1 | None, +) -> OracleTierV1: + """Map Execute effect strength onto the Seal oracle ladder.""" + + if strength is EffectStrengthV1.INDEPENDENT_SYSTEM_OF_RECORD: + return 2 + if strength is EffectStrengthV1.INDEPENDENT_SESSION: + return 1 + return 0 + class ExecuteLifecycleStateV1(str, Enum): QUEUED = "queued" @@ -154,12 +170,23 @@ class ExecuteEvidenceContractV1(_StrictContract): class ExecuteEvidenceReceiptV1(_StrictContract): - """A portable receipt that identifies evidence without carrying it.""" + """A portable receipt that identifies evidence without carrying it. + + This object is the Seal. It is not a second format. The fields below bind + program version, admission, environment, runner, independent effect, and a + per-receipt nonce so a consumer does not have to keep the original request. + """ schema_version: Literal[EXECUTE_EVIDENCE_RECEIPT_SCHEMA] = EXECUTE_EVIDENCE_RECEIPT_SCHEMA receipt_id: StrictStr = Field(pattern=_OPAQUE_ID_PATTERN) execution_id: StrictStr = Field(pattern=_OPAQUE_ID_PATTERN) workflow_digest: StrictStr = Field(pattern=_SHA256_PATTERN) + workflow_version: StrictStr = Field(pattern=_OPAQUE_ID_PATTERN) + qualification_id: StrictStr = Field(pattern=_OPAQUE_ID_PATTERN) + environment_id: StrictStr = Field(pattern=_OPAQUE_ID_PATTERN) + runner_id: StrictStr = Field(pattern=_OPAQUE_ID_PATTERN) + nonce: StrictStr = Field(pattern=_OPAQUE_ID_PATTERN) + oracle_tier: OracleTierV1 outcome: ExecuteTerminalOutcomeV1 contracts: ExecuteEvidenceContractV1 delivery_uncertain: StrictBool @@ -198,6 +225,11 @@ def _validate_proof(self) -> "ExecuteEvidenceReceiptV1": if self.outcome is ExecuteTerminalOutcomeV1.RECONCILIATION_REQUIRED: if not self.delivery_uncertain: raise ValueError("reconciliation_required requires an uncertain effect or delivery") + derived = oracle_tier_from_effect_strength(self.contracts.observed_effect_strength) + if self.oracle_tier != derived: + raise ValueError("oracle_tier must match observed_effect_strength") + if verified and self.oracle_tier < 2: + raise ValueError("a verified outcome requires oracle tier 2 or 3") return self diff --git a/openadapt_types/schemas/execute-v1-openapi.json b/openadapt_types/schemas/execute-v1-openapi.json index 64c24b3..1965e3e 100644 --- a/openadapt_types/schemas/execute-v1-openapi.json +++ b/openadapt_types/schemas/execute-v1-openapi.json @@ -510,7 +510,7 @@ }, "ExecuteEvidenceReceiptV1": { "additionalProperties": false, - "description": "A portable receipt that identifies evidence without carrying it.", + "description": "A portable receipt that identifies evidence without carrying it.\n\nThis object is the Seal. It is not a second format. The fields below bind\nprogram version, admission, environment, runner, independent effect, and a\nper-receipt nonce so a consumer does not have to keep the original request.", "properties": { "compensation_effect_verified": { "default": false, @@ -524,6 +524,11 @@ "title": "Delivery Uncertain", "type": "boolean" }, + "environment_id": { + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$", + "title": "Environment Id", + "type": "string" + }, "evidence_digest": { "pattern": "^sha256:[0-9a-f]{64}$", "title": "Evidence Digest", @@ -539,14 +544,39 @@ "title": "Issued At", "type": "string" }, + "nonce": { + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$", + "title": "Nonce", + "type": "string" + }, + "oracle_tier": { + "enum": [ + 0, + 1, + 2, + 3 + ], + "title": "Oracle Tier", + "type": "integer" + }, "outcome": { "$ref": "#/components/schemas/ExecuteTerminalOutcomeV1" }, + "qualification_id": { + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$", + "title": "Qualification Id", + "type": "string" + }, "receipt_id": { "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$", "title": "Receipt Id", "type": "string" }, + "runner_id": { + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$", + "title": "Runner Id", + "type": "string" + }, "schema_version": { "const": "openadapt.execute-evidence-receipt/v1", "default": "openadapt.execute-evidence-receipt/v1", @@ -557,12 +587,23 @@ "pattern": "^sha256:[0-9a-f]{64}$", "title": "Workflow Digest", "type": "string" + }, + "workflow_version": { + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$", + "title": "Workflow Version", + "type": "string" } }, "required": [ "receipt_id", "execution_id", "workflow_digest", + "workflow_version", + "qualification_id", + "environment_id", + "runner_id", + "nonce", + "oracle_tier", "outcome", "contracts", "delivery_uncertain", diff --git a/tests/test_execute_contract.py b/tests/test_execute_contract.py index 1d88a56..c1299fd 100644 --- a/tests/test_execute_contract.py +++ b/tests/test_execute_contract.py @@ -20,6 +20,7 @@ ExecuteTerminalOutcomeV1, ExecuteWebhookEventTypeV1, execute_openapi_document, + oracle_tier_from_effect_strength, sign_execute_webhook_hmac, ) from openadapt_types.execute_client import _RejectRedirectHandler @@ -45,6 +46,12 @@ def _receipt(**updates: object) -> ExecuteEvidenceReceiptV1: "receipt_id": "receipt_12345678", "execution_id": "execution_12345678", "workflow_digest": "sha256:" + "a" * 64, + "workflow_version": "workflow_20260729", + "qualification_id": "qualification_12345678", + "environment_id": "environment_12345678", + "runner_id": "runner:hosted", + "nonce": "nonce:execution_12345678", + "oracle_tier": 2, "outcome": ExecuteTerminalOutcomeV1.VERIFIED, "contracts": _contract(), "delivery_uncertain": False, @@ -129,8 +136,41 @@ def test_reconciliation_requires_uncertainty_not_a_success_shaped_receipt() -> N outcome=ExecuteTerminalOutcomeV1.RECONCILIATION_REQUIRED, delivery_uncertain=True, contracts=_contract(effect_passed=False, observed_effect_strength=None), + oracle_tier=0, ) assert receipt.outcome is ExecuteTerminalOutcomeV1.RECONCILIATION_REQUIRED + assert receipt.oracle_tier == 0 + + +def test_receipt_is_the_seal_and_verified_requires_sor_oracle() -> None: + receipt = _receipt() + assert receipt.workflow_version == "workflow_20260729" + assert receipt.qualification_id == "qualification_12345678" + assert receipt.environment_id == "environment_12345678" + assert receipt.runner_id == "runner:hosted" + assert receipt.nonce == "nonce:execution_12345678" + assert receipt.oracle_tier == 2 + assert oracle_tier_from_effect_strength( + EffectStrengthV1.INDEPENDENT_SYSTEM_OF_RECORD + ) == 2 + assert oracle_tier_from_effect_strength( + EffectStrengthV1.INDEPENDENT_SESSION + ) == 1 + assert oracle_tier_from_effect_strength( + EffectStrengthV1.IMMEDIATE_SCREEN_CONFIRMATION + ) == 0 + + with pytest.raises(ValidationError, match="oracle_tier must match"): + _receipt(oracle_tier=1) + + with pytest.raises(ValidationError, match="oracle tier 2 or 3"): + _receipt( + contracts=_contract( + minimum_effect_strength=EffectStrengthV1.INDEPENDENT_SESSION, + observed_effect_strength=EffectStrengthV1.INDEPENDENT_SESSION, + ), + oracle_tier=1, + ) def test_webhook_signature_binds_the_closed_state_payload() -> None: