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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions openadapt_types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@
EXECUTE_STATUS_SCHEMA,
EXECUTE_WEBHOOK_SCHEMA,
EffectStrengthV1,
OracleTierV1,
oracle_tier_from_effect_strength,
ExecuteAcceptedV1,
ExecuteAuthorizationContextV1,
ExecuteDecisionRequiredWebhookV1,
Expand Down Expand Up @@ -298,6 +300,8 @@
"EXECUTE_STATUS_SCHEMA",
"EXECUTE_WEBHOOK_SCHEMA",
"EffectStrengthV1",
"OracleTierV1",
"oracle_tier_from_effect_strength",
"ExecuteAcceptedV1",
"ExecuteAuthorizationContextV1",
"ExecuteDecisionRequiredWebhookV1",
Expand Down
34 changes: 33 additions & 1 deletion openadapt_types/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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


Expand Down
43 changes: 42 additions & 1 deletion openadapt_types/schemas/execute-v1-openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down
40 changes: 40 additions & 0 deletions tests/test_execute_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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:
Expand Down
Loading