Skip to content
Draft
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
2 changes: 1 addition & 1 deletion openadapt_flow/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""openadapt-flow: record once, compile, replay deterministically, heal on drift."""

__version__ = "1.33.0"
__version__ = "1.34.0"

from openadapt_flow.ir import ( # noqa: F401
ActionKind,
Expand Down
35 changes: 26 additions & 9 deletions openadapt_flow/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,23 @@
import sys
from contextlib import contextmanager
from pathlib import Path
from typing import TYPE_CHECKING, Any, Iterator, Literal, Optional, Sequence, cast
from typing import (
TYPE_CHECKING,
Any,
Iterator,
Literal,
Mapping,
Optional,
Sequence,
cast,
)
from urllib.parse import urlsplit
from uuid import UUID

if TYPE_CHECKING: # pragma: no cover
from openadapt_flow.backend import Backend
from openadapt_flow.ir import ExecutionTargetKind, RunReport
from openadapt_flow.runtime.authorization import RuntimeParamScalar
from openadapt_flow.tutorial import BreakItResult

_VIEWPORT = {"width": 1280, "height": 800}
Expand Down Expand Up @@ -258,7 +268,7 @@ def _resolve_record_capture_window(
def _replay_params(
pairs: Sequence[str] | None,
params_file: str | None = None,
) -> dict[str, str]:
) -> dict[str, "RuntimeParamScalar"]:
"""Load replay bindings without requiring sensitive values in argv.

``--params-file`` is intended for managed runners: the file can be staged
Expand All @@ -267,7 +277,9 @@ def _replay_params(
"""
import json

params: dict[str, str] = {}
from openadapt_flow.runtime.authorization import is_runtime_param_scalar

params: dict[str, RuntimeParamScalar] = {}
if params_file:
path = Path(params_file)
try:
Expand All @@ -281,11 +293,9 @@ def _replay_params(
for key, value in raw.items():
if not isinstance(key, str) or not key:
raise SystemExit("--params-file keys must be non-empty strings")
if not isinstance(value, (str, int, float, bool)) or isinstance(
value, (dict, list)
):
if not is_runtime_param_scalar(value):
raise SystemExit(f"--params-file value for {key!r} must be a scalar")
params[key] = str(value)
params[key] = value
params.update(_parse_params(pairs))
return params

Expand Down Expand Up @@ -400,7 +410,10 @@ def _deployment_sections(args: argparse.Namespace):
return cfg, effects, actuation


def _deployment_runtime(args: argparse.Namespace, params: dict[str, str] | None = None):
def _deployment_runtime(
args: argparse.Namespace,
params: Mapping[str, "RuntimeParamScalar"] | None = None,
):
"""Resolve the deployment wiring for a replay/run from ``--config`` + flags.

Returns ``(cfg, effect_verifier, api_actuator, durable, allow_egress)``.
Expand All @@ -416,10 +429,14 @@ def _deployment_runtime(args: argparse.Namespace, params: dict[str, str] | None
ignores it.
"""
from openadapt_flow.deployment import build_api_actuator, build_effect_verifier
from openadapt_flow.runtime.authorization import runtime_params_for_gui

cfg, effects, actuation = _deployment_sections(args)
try:
effect_verifier = build_effect_verifier(effects, params=params)
effect_verifier = build_effect_verifier(
effects,
params=runtime_params_for_gui(params or {}),
)
api_actuator = build_api_actuator(actuation)
except ValueError as e:
raise SystemExit(str(e))
Expand Down
10 changes: 7 additions & 3 deletions openadapt_flow/action_evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from typing import Any, Optional

from openadapt_flow.ir import ActionKind, Step
from openadapt_flow.runtime.authorization import RuntimeParamScalar, runtime_param_text

AUTOMATED_GUI_ACTUATIONS = frozenset(
{"uia", "dom", "guarded_coordinate", "guarded_keyboard", "remote_guarded"}
Expand Down Expand Up @@ -177,7 +178,7 @@ def _delivery_receipt_error(
step: Step,
result: Any,
*,
params: Mapping[str, str],
params: Mapping[str, RuntimeParamScalar],
) -> Optional[str]:
receipt = result.delivery_receipt
if receipt is None:
Expand Down Expand Up @@ -226,7 +227,10 @@ def _delivery_receipt_error(
return "non-drag delivery receipt contains a destination fingerprint"

if step.action is ActionKind.SELECT_OPTION:
selected = params.get(step.param) if step.param is not None else step.text
selected_value = params.get(step.param) if step.param is not None else step.text
selected = (
runtime_param_text(selected_value) if selected_value is not None else None
)
if selected is None or step.selection_commit_key is None:
return "selection delivery receipt lacks its compiled input contract"
if (
Expand Down Expand Up @@ -287,7 +291,7 @@ def action_evidence_error(
step: Step,
result: Any,
*,
params: Mapping[str, str] | None = None,
params: Mapping[str, RuntimeParamScalar] | None = None,
identity_required: bool = False,
strict_production: bool = True,
) -> Optional[str]:
Expand Down
6 changes: 5 additions & 1 deletion openadapt_flow/compiler/induction.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
Transition,
Workflow,
)
from openadapt_flow.runtime.authorization import runtime_param_text

TraceInput = Union[Workflow, str, Path]

Expand Down Expand Up @@ -717,7 +718,10 @@ def induce_program(
program=program,
subflows=subflows,
param_specs=param_specs,
params={k: (v.example or "") for k, v in param_specs.items()},
params={
key: runtime_param_text(spec.example) if spec.example is not None else ""
for key, spec in param_specs.items()
},
data_sources=data_sources,
)
result.program = program
Expand Down
5 changes: 3 additions & 2 deletions openadapt_flow/console/halt_detail.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
from openadapt_flow.console import data
from openadapt_flow.ir import ActionKind, Anchor, Rung, Step, Workflow
from openadapt_flow.runtime import identity as _id
from openadapt_flow.runtime.authorization import runtime_params_for_gui
from openadapt_flow.runtime.durable.checkpoint import CheckpointStore, PendingEscalation

#: The resolution ladder in strongest-first order, taken from the engine's own
Expand Down Expand Up @@ -444,9 +445,9 @@ def halt_detail(
anchor = step.anchor if step is not None else None
params: dict[str, str] = {}
if report is not None and getattr(report, "params", None):
params = dict(report.params)
params = runtime_params_for_gui(report.params)
elif pending is not None:
params = dict(pending.params)
params = runtime_params_for_gui(pending.params)

role, label = _safe_target_label(step, params)
resolved_rung = None
Expand Down
6 changes: 3 additions & 3 deletions openadapt_flow/deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -908,7 +908,7 @@ def build_replayer(
def _resolve_config_exprs(
section: str,
exprs: Mapping[str, ValueExpr],
params: Optional[Mapping[str, str]],
params: Optional[Mapping[str, object]],
) -> dict[str, str]:
"""Resolve a config's ``ValueExpr`` mapping against the run's params.

Expand Down Expand Up @@ -945,7 +945,7 @@ def _require_env(name: str, what: str) -> str:


def build_effect_verifier(
cfg: EffectsConfig, params: Optional[Mapping[str, str]] = None
cfg: EffectsConfig, params: Optional[Mapping[str, object]] = None
) -> Optional[Any]:
"""Construct the configured ``EffectVerifier`` (or None for ``kind: none``).

Expand Down Expand Up @@ -1073,7 +1073,7 @@ def sanitized(value: Any, *, key: str = "") -> Any:


def _build_effect_verifier_unredacted(
cfg: EffectsConfig, params: Optional[Mapping[str, str]] = None
cfg: EffectsConfig, params: Optional[Mapping[str, object]] = None
) -> Optional[Any]:
"""The per-kind construction behind :func:`build_effect_verifier`."""
kind = (cfg.kind or "none").strip().lower()
Expand Down
Loading
Loading