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
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,16 @@ out the pixels. Coordinates are the thing that breaks when a window moves.
| `ActionResult` | The outcome, with an error taxonomy and a state delta |
| `Episode` / `Step` | A whole trajectory: observation, action, result |
| `FailureRecord` | A classified failure, for dataset pipelines |
| `OracleObservation` | One independent effect read. Production `VERIFIED` needs tier 2 or 3 |

Plus the versioned wire contracts: `ControlOverlayFrameV1`/`V2` and
`ControlOverlayTimelineV1`/`V2` for PHI-safe execution overlays,
`ExecuteRequestV1` / `ExecuteStatusV1` / `ExecuteEvidenceReceiptV1` for
asynchronous qualified execution, `EffectStrengthV1`, and the
`BusinessDecision*V1` family for signed, finite human choices. What those
contracts may and may not carry is in
[docs/CONTRACTS.md](docs/CONTRACTS.md).
[docs/CONTRACTS.md](docs/CONTRACTS.md). Oracle tiers and the ten-line
adapter are in [docs/ORACLE.md](docs/ORACLE.md).

## JSON Schema for everything else

Expand Down Expand Up @@ -111,7 +113,9 @@ state = from_benchmark_observation(obs.__dict__)
A partner sends an authorized request, gets an execution ID back, and then
either reads a terminal receipt or waits for a signed webhook. The contract
exposes no runner, no customer data, no evidence bytes, and no control-plane
internals.
internals. A `verified` receipt needs an oracle at tier 2 or 3 (API, DB,
file, ack, or a counterparty artifact). Visual and OCR reads are tier 0
and cannot mint it.

```python
from openadapt_types import ExecuteClient
Expand Down
9 changes: 9 additions & 0 deletions docs/CONTRACTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,12 @@ client = ExecuteClient(
The client does not poll forever. A workflow can wait for a human decision or
reconciliation. Use the terminal receipt or signed webhook as the completion
signal.

## Oracle tiers

`verified` and `rolled_back_verified` require an observed effect strength
that maps to Seal oracle tier 2 or 3. Tier 0 is visual / OCR. Tier 1 is a
second-session UI read. Neither can mint production `VERIFIED`.

The partner adapter is `channel` plus `read`. Tiers, the gate, and a
ten-line file oracle are in [ORACLE.md](ORACLE.md).
58 changes: 58 additions & 0 deletions docs/ORACLE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Oracle adapters

A production `VERIFIED` receipt needs an independent read of the system of
record. The click is the commodity. The read is what a Seal is for.

## Tiers

| Tier | What you read | Production `VERIFIED` |
| --- | --- | --- |
| 0 | Pixels, OCR, same-surface banner | No |
| 1 | Second session / independent UI | No |
| 2 | API, DB, file, ack | Yes, if the other contracts pass |
| 3 | Counterparty artifact (payer status, legal export) | Yes, if the other contracts pass |

Tier 0 is for local development. `issue_production_verified` raises
`ProductionSealRefused` for tier 0 and tier 1. The Execute receipt does
the same: `verified` and `rolled_back_verified` require observed effect
strength that maps to tier 2 or 3.

This numbering is the Seal ladder. `EffectVerificationTier` counts the
other way (1 is strongest there). Use `OracleTier` when you talk about a
Seal.

## Adapter

Implement `channel` and `read`. That's it.

```python
from pathlib import Path
import json
from openadapt_types import OracleChannel, OracleObservation

class FileStatusOracle:
channel = OracleChannel.FILE

def __init__(self, path: Path) -> None:
self.path = path

def read(self, identity):
rec = json.loads(self.path.read_text(encoding="utf-8"))[identity["record_id"]]
return OracleObservation(
channel=self.channel,
identity={"record_id": identity["record_id"]},
value={"status": rec["status"]},
)
```

API, DB, ack, and second-session adapters keep that shape. The channel
sets the tier. A visual adapter that stuffs a JSON body into `value`
is still tier 0.

Worked file: [`examples/oracle/file_oracle.py`](../examples/oracle/file_oracle.py).

Don't put a per-vendor recipe in this repository. The interface is
public. Productionized connector recipes stay private.

Flow will call this contract on the Execute path. This package holds the
adapter and the Seal gate. It does not run the GUI.
13 changes: 13 additions & 0 deletions examples/oracle/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Oracle adapter

A production `VERIFIED` receipt needs a read that did not come from the
acting screen. The adapter is `channel` plus `read`. That's the whole
interface.

Tiers are in [`docs/ORACLE.md`](../../docs/ORACLE.md). Tier 0 (pixels, OCR)
cannot mint production `VERIFIED`. Tier 2 is an API, DB, file, or ack
read. Tier 1 is a second session; same adapter, no production Seal.

`file_oracle.py` is the file-channel shape. Swap the body of `read` for an
HTTP GET, a SQL SELECT, or a second-session UI query. Do not copy a
vendor recipe into this package.
24 changes: 24 additions & 0 deletions examples/oracle/file_oracle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""A ten-line Tier-2 oracle. The file is the system of record, not the screen."""

from __future__ import annotations

import json
from pathlib import Path
from typing import Mapping

from openadapt_types import OracleChannel, OracleObservation


class FileStatusOracle:
channel = OracleChannel.FILE

def __init__(self, path: Path) -> None:
self.path = path

def read(self, identity: Mapping[str, str]) -> OracleObservation:
rec = json.loads(self.path.read_text(encoding="utf-8"))[identity["record_id"]]
return OracleObservation(
channel=self.channel,
identity={"record_id": identity["record_id"]},
value={"status": rec["status"]},
)
27 changes: 25 additions & 2 deletions openadapt_types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@
EXECUTE_WEBHOOK_SCHEMA,
EffectStrengthV1,
OracleTierV1,
oracle_tier_from_effect_strength,
ExecuteAcceptedV1,
ExecuteAuthorizationContextV1,
ExecuteDecisionRequiredWebhookV1,
Expand All @@ -140,6 +139,19 @@
)
from openadapt_types.execute_client import ExecuteApiError, ExecuteClient
from openadapt_types.execute_openapi import execute_openapi_document
from openadapt_types.oracle import (
OracleAdapter,
OracleChannel,
OracleObservation,
OracleTier,
ProductionSealRefused,
Tier2Oracle,
issue_production_verified,
oracle_tier_from_effect_strength,
production_seal_allowed,
refuse_production_verified,
tier_of,
)
from openadapt_types.execution_requirements import (
CAPABILITY_MATCH_SCHEMA,
EXECUTION_REQUIREMENTS_SCHEMA,
Expand Down Expand Up @@ -301,7 +313,6 @@
"EXECUTE_WEBHOOK_SCHEMA",
"EffectStrengthV1",
"OracleTierV1",
"oracle_tier_from_effect_strength",
"ExecuteAcceptedV1",
"ExecuteAuthorizationContextV1",
"ExecuteDecisionRequiredWebhookV1",
Expand All @@ -320,6 +331,18 @@
"execute_openapi_document",
"ExecuteApiError",
"ExecuteClient",
# oracle adapter
"OracleAdapter",
"OracleChannel",
"OracleObservation",
"OracleTier",
"ProductionSealRefused",
"Tier2Oracle",
"issue_production_verified",
"oracle_tier_from_effect_strength",
"production_seal_allowed",
"refuse_production_verified",
"tier_of",
# runner capability and execution requirements
"CAPABILITY_MATCH_SCHEMA",
"EXECUTION_REQUIREMENTS_SCHEMA",
Expand Down
24 changes: 12 additions & 12 deletions openadapt_types/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@

from openadapt_types.business_decision import BusinessDecisionTaskV1
from openadapt_types.human_decision import HumanDecisionTaskV1
from openadapt_types.oracle import (
oracle_tier_from_effect_strength,
production_seal_allowed,
)

EXECUTE_REQUEST_SCHEMA = "openadapt.execute-request/v1"
EXECUTE_ACCEPTED_SCHEMA = "openadapt.execute-accepted/v1"
Expand Down Expand Up @@ -72,21 +76,11 @@ class EffectStrengthV1(str, Enum):

# 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.
# Mapping and production_seal_allowed live in oracle.py; this alias is the
# receipt field so OpenAPI stays an integer enum.
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"
RUNNING = "running"
Expand Down Expand Up @@ -217,6 +211,12 @@ def _validate_proof(self) -> "ExecuteEvidenceReceiptV1":
< _EFFECT_STRENGTH_RANK[self.contracts.minimum_effect_strength]
):
raise ValueError("observed effect strength is below the required strength")
if not production_seal_allowed(
oracle_tier_from_effect_strength(
self.contracts.observed_effect_strength
)
):
raise ValueError("a verified outcome requires oracle tier 2 or 3")
if self.outcome is ExecuteTerminalOutcomeV1.ROLLED_BACK_VERIFIED:
if not self.compensation_effect_verified:
raise ValueError("rolled_back_verified requires independently verified compensation")
Expand Down
152 changes: 152 additions & 0 deletions openadapt_types/oracle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"""Partner oracle adapter: one read, a tier, a production Seal gate.

This module is the public interface. It does not contain per-system-of-record
recipes, thresholds, or connector credentials. A partner implements
``channel`` and ``read``. Flow consumes the same contract later.

Seal ladder (higher is stronger):

* 0 visual / OCR. Local development. Never a production ``VERIFIED``.
* 1 second session / independent UI read. Not a production Seal.
* 2 system-of-record read (API, DB, file, ack).
* 3 counterparty artifact (payer status, legal export).

This numbering is the Seal ladder. It is not
``EffectVerificationTier``, which counts the other way.
"""

from __future__ import annotations

from enum import Enum, IntEnum
from typing import Mapping, Protocol, runtime_checkable

from pydantic import (
BaseModel,
ConfigDict,
Field,
JsonValue,
StrictStr,
model_validator,
)

PRODUCTION_SEAL_MINIMUM_TIER = 2


class OracleTier(IntEnum):
"""Seal oracle ladder. Higher is stronger. Production Seals require 2 or 3."""

VISUAL = 0
INDEPENDENT_SESSION = 1
SYSTEM_OF_RECORD = 2
COUNTERPARTY = 3


class OracleChannel(str, Enum):
"""How the oracle read the effect. The channel, not the payload, sets the tier."""

VISUAL = "visual"
OCR = "ocr"
SECOND_SESSION = "second_session"
API = "api"
DB = "db"
FILE = "file"
ACK = "ack"
COUNTERPARTY = "counterparty"


_CHANNEL_TIER: Mapping[OracleChannel, OracleTier] = {
OracleChannel.VISUAL: OracleTier.VISUAL,
OracleChannel.OCR: OracleTier.VISUAL,
OracleChannel.SECOND_SESSION: OracleTier.INDEPENDENT_SESSION,
OracleChannel.API: OracleTier.SYSTEM_OF_RECORD,
OracleChannel.DB: OracleTier.SYSTEM_OF_RECORD,
OracleChannel.FILE: OracleTier.SYSTEM_OF_RECORD,
OracleChannel.ACK: OracleTier.SYSTEM_OF_RECORD,
OracleChannel.COUNTERPARTY: OracleTier.COUNTERPARTY,
}

_EFFECT_STRENGTH_TO_TIER: Mapping[str, OracleTier] = {
"independent_system_of_record": OracleTier.SYSTEM_OF_RECORD,
"independent_session": OracleTier.INDEPENDENT_SESSION,
}


class ProductionSealRefused(ValueError):
"""Raised when a production ``VERIFIED`` stamp is requested below tier 2."""


class OracleObservation(BaseModel):
"""One read-only observation. The channel decides the tier."""

model_config = ConfigDict(extra="forbid", frozen=True)

channel: OracleChannel
identity: dict[str, StrictStr] = Field(min_length=1)
value: dict[str, JsonValue]

@model_validator(mode="after")
def _identity_keys_are_present(self) -> "OracleObservation":
if any(not key for key in self.identity):
raise ValueError("oracle identity keys must be non-empty")
return self

@property
def tier(self) -> OracleTier:
return _CHANNEL_TIER[self.channel]


@runtime_checkable
class OracleAdapter(Protocol):
"""Read-only effect check. Implement ``channel`` and ``read``.

Tier-2 channels are ``api``, ``db``, ``file``, and ``ack``.
``second_session`` uses the same adapter and classifies as tier 1.
"""

channel: OracleChannel

def read(self, identity: Mapping[str, str]) -> OracleObservation: ...


Tier2Oracle = OracleAdapter


def tier_of(channel: OracleChannel) -> OracleTier:
"""Return the Seal tier for one channel."""

return _CHANNEL_TIER[channel]


def oracle_tier_from_effect_strength(strength: object | None) -> OracleTier:
"""Map an Execute ``EffectStrengthV1`` value onto the Seal ladder."""

name = getattr(strength, "value", strength)
if not isinstance(name, str):
return OracleTier.VISUAL
return _EFFECT_STRENGTH_TO_TIER.get(name, OracleTier.VISUAL)


def production_seal_allowed(tier: OracleTier | int) -> bool:
"""True when ``tier`` may stamp a production ``VERIFIED`` Seal."""

return int(tier) >= PRODUCTION_SEAL_MINIMUM_TIER


def refuse_production_verified(tier: OracleTier | int) -> None:
"""Raise if ``tier`` cannot mint production ``VERIFIED``."""

if not production_seal_allowed(tier):
raise ProductionSealRefused(
"a verified outcome requires oracle tier 2 or 3"
)


def issue_production_verified(observation: OracleObservation) -> OracleTier:
"""Return ``observation.tier`` if it may stamp production ``VERIFIED``.

Visual / OCR (tier 0) and second-session UI (tier 1) raise
``ProductionSealRefused``. The payload cannot upgrade the channel.
"""

refuse_production_verified(observation.tier)
return observation.tier
Loading
Loading