Files
aios/active-runtime/app/task_plane/models.py
T

830 lines
29 KiB
Python

"""Core data models for the task plane."""
from __future__ import annotations
import hashlib
import json
import re
import time
import uuid
import urllib.parse
from enum import StrEnum
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any
SENSITIVE_KEYS = {
"secret",
"password",
"token",
"api_key",
"access_token",
"refresh_token",
"authorization",
"private_key",
"client_secret",
"credential",
}
IDENTIFIER_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,127}")
TYPE_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,127}")
RISK_LEVELS = {"low", "medium", "high"}
DATA_CLASSIFICATIONS = {
"public",
"internal",
"confidential",
"credential-secret",
}
SECRET_TEXT_PATTERNS = (
re.compile(r"(?i)\bAuthorization\s*:\s*Bearer\s+\S+"),
re.compile(r"(?i)\b(?:password|passwd|api[_-]?key|access[_-]?token|secret)\s*[:=]\s*\S+"),
re.compile(r"\bsk-[A-Za-z0-9_-]{12,}\b"),
re.compile(r"\bAKIA[0-9A-Z]{16}\b"),
re.compile(r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----"),
)
TASK_PLAN_KEYS = {
"schema_version",
"task_id",
"task_type",
"issuer",
"intent",
"required_capabilities",
"preferred_workers",
"risk",
"approval_policy",
"dependencies",
"max_delegation_depth",
"budgets",
"evidence_requirements",
"parent_task_id",
"correlation_id",
"metadata",
"data_classification",
"execution_zone",
"network_policy",
"logging_policy",
"allowed_input_paths",
"allowed_output_paths",
"output_policy",
}
def _now_ts() -> float:
return time.time()
def canonical_json(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def compute_sha256_hex(value: Any) -> str:
return hashlib.sha256(canonical_json(value).encode("utf-8")).hexdigest()
def contains_secret_text(value: Any) -> bool:
if isinstance(value, str):
return any(pattern.search(value) for pattern in SECRET_TEXT_PATTERNS)
if isinstance(value, dict):
return any(contains_secret_text(item) for item in value.values())
if isinstance(value, (list, tuple)):
return any(contains_secret_text(item) for item in value)
return False
def validate_relative_paths(values: Any, name: str) -> list[str]:
paths = _normalize_list(values)
for value in paths:
decoded = urllib.parse.unquote(value)
parts = value.replace("\\", "/").split("/")
if (
decoded != value
or "\\" in value
or value.startswith("/")
or re.match(r"^[A-Za-z]:", value)
or any(part in {"", ".", ".."} for part in parts)
):
raise ValueError(f"invalid {name}")
return paths
def validate_identifier(value: Any, name: str) -> str:
text = str(value or "")
if (
not IDENTIFIER_RE.fullmatch(text)
or urllib.parse.unquote(text) != text
or any(ord(ch) < 32 or ord(ch) == 127 for ch in text)
):
raise ValueError(f"invalid {name}")
return text
def validate_type(value: Any, name: str) -> str:
text = str(value or "")
if not TYPE_RE.fullmatch(text):
raise ValueError(f"invalid {name}")
return text
def redact_value(value: Any) -> Any:
if isinstance(value, dict):
redacted = {}
for key, inner in value.items():
lowered = str(key).lower()
if lowered in SENSITIVE_KEYS or lowered.endswith(
("_password", "_secret", "_access_token", "_refresh_token", "_private_key")
):
redacted[key] = "[REDACTED]"
else:
redacted[key] = redact_value(inner)
return redacted
if isinstance(value, list):
return [redact_value(v) for v in value]
if isinstance(value, tuple):
return tuple(redact_value(v) for v in value)
return value
class TaskStateTransitionError(ValueError):
pass
class TaskStatus(StrEnum):
CREATED = "CREATED"
QUEUED = "QUEUED"
CLAIMED = "CLAIMED"
RUNNING = "RUNNING"
WAITING_CHILD = "WAITING_CHILD"
VERIFYING = "VERIFYING"
COMPLETED = "COMPLETED"
FAILED_RECOVERABLE = "FAILED_RECOVERABLE"
FAILED_FINAL = "FAILED_FINAL"
CANCELLED = "CANCELLED"
MANUAL_REVIEW_REQUIRED = "MANUAL_REVIEW_REQUIRED"
TASK_STATES = {
status.value for status in TaskStatus
}
TASK_NEXT_STATES = {
"CREATED": {"QUEUED", "CLAIMED", "CANCELLED", "MANUAL_REVIEW_REQUIRED"},
"QUEUED": {"CLAIMED", "CANCELLED", "FAILED_RECOVERABLE", "FAILED_FINAL"},
"CLAIMED": {"RUNNING", "FAILED_RECOVERABLE", "FAILED_FINAL", "CANCELLED"},
"RUNNING": {"VERIFYING", "WAITING_CHILD", "COMPLETED", "FAILED_RECOVERABLE", "FAILED_FINAL", "CANCELLED"},
"WAITING_CHILD": {"RUNNING", "VERIFYING", "FAILED_RECOVERABLE", "FAILED_FINAL", "CANCELLED"},
"VERIFYING": {"COMPLETED", "FAILED_RECOVERABLE", "FAILED_FINAL", "CANCELLED"},
"COMPLETED": set(),
"FAILED_RECOVERABLE": {"CLAIMED", "RUNNING", "CANCELLED"},
"FAILED_FINAL": set(),
"CANCELLED": set(),
"MANUAL_REVIEW_REQUIRED": {"RUNNING", "CANCELLED"},
}
@dataclass
class Budget:
time_seconds: int | None = None
attempts: int | None = None
tokens: int | None = None
cost: float | None = None
@classmethod
def from_dict(cls, data: dict[str, Any] | None) -> "Budget":
if not data:
return cls()
if not isinstance(data, dict) or set(data) - {
"time_seconds",
"attempts",
"tokens",
"cost",
}:
raise ValueError("invalid budget")
for name in ("time_seconds", "attempts", "tokens", "cost"):
value = data.get(name)
if value is not None:
try:
if float(value) < 0:
raise ValueError("budget values must be non-negative")
except (TypeError, ValueError) as exc:
raise ValueError("invalid budget value") from exc
return cls(
time_seconds=_to_positive_int(data.get("time_seconds")),
attempts=_to_positive_int(data.get("attempts")),
tokens=_to_positive_int(data.get("tokens")),
cost=_to_float(data.get("cost")),
)
def to_dict(self) -> dict[str, Any]:
return {
"time_seconds": self.time_seconds,
"attempts": self.attempts,
"tokens": self.tokens,
"cost": self.cost,
}
def merge(self, other: "Budget") -> "Budget":
return Budget(
time_seconds=_first_non_none(self.time_seconds, other.time_seconds),
attempts=_first_non_none(self.attempts, other.attempts),
tokens=_first_non_none(self.tokens, other.tokens),
cost=_first_non_none(self.cost, other.cost),
)
@dataclass
class TaskPlan:
schema_version: int = 1
task_id: str | None = None
task_type: str = "code.change"
issuer: dict[str, Any] = field(default_factory=dict)
intent: str | None = None
required_capabilities: list[str] = field(default_factory=list)
preferred_workers: list[str] = field(default_factory=list)
risk: str = "medium"
approval_policy: str = "patch-only"
dependencies: list[str] = field(default_factory=list)
max_delegation_depth: int = 5
budgets: Budget = field(default_factory=Budget)
evidence_requirements: list[str] = field(default_factory=list)
parent_task_id: str | None = None
correlation_id: str | None = None
metadata: dict[str, Any] = field(default_factory=dict)
data_classification: str = "internal"
execution_zone: str = "isolated-development"
network_policy: str = "controlled"
logging_policy: str = "redacted"
allowed_input_paths: list[str] = field(default_factory=list)
allowed_output_paths: list[str] = field(default_factory=list)
output_policy: str = "standard"
@classmethod
def from_dict(cls, data: dict[str, Any] | None) -> "TaskPlan":
if not isinstance(data, dict):
raise ValueError("task plan payload must be an object")
unknown = set(data) - TASK_PLAN_KEYS
if unknown:
raise ValueError("unknown task plan fields")
if "task_id" not in data or data["task_id"] is None:
data = dict(data)
data["task_id"] = f"task-{uuid.uuid4()}"
risk = str(data.get("risk", "medium"))
if risk not in RISK_LEVELS:
raise ValueError("invalid risk")
issuer = data.get("issuer", {})
if not isinstance(issuer, dict):
raise ValueError("issuer must be an object")
schema_version = int(data.get("schema_version", 1))
if schema_version != 1:
raise ValueError("unsupported schema_version")
max_depth = _to_positive_int(data.get("max_delegation_depth"), default=5)
if max_depth is None or max_depth > 32:
raise ValueError("invalid max_delegation_depth")
classification = str(data.get("data_classification", "internal"))
if classification not in DATA_CLASSIFICATIONS:
raise ValueError("invalid data_classification")
execution_zone = validate_type(
data.get("execution_zone", "isolated-development"), "execution_zone"
)
network_policy = validate_type(
data.get("network_policy", "controlled"), "network_policy"
)
logging_policy = validate_type(
data.get("logging_policy", "redacted"), "logging_policy"
)
output_policy = validate_type(
data.get("output_policy", "standard"), "output_policy"
)
if contains_secret_text(
{
"intent": data.get("intent"),
"metadata": data.get("metadata", {}),
}
):
raise ValueError("task content contains secret material")
if classification == "credential-secret":
if execution_zone != "secure-file-transformer":
raise ValueError("credential-secret requires secure-file-transformer")
if network_policy != "denied":
raise ValueError("credential-secret requires network denied")
if logging_policy != "metadata-only":
raise ValueError("credential-secret requires metadata-only logging")
if output_policy != "hash-count-status-only":
raise ValueError("credential-secret requires restricted output")
return cls(
schema_version=schema_version,
task_id=validate_identifier(data["task_id"], "task_id"),
task_type=validate_type(data.get("task_type", "code.change"), "task_type"),
issuer=issuer,
intent=data.get("intent"),
required_capabilities=_normalize_list(data.get("required_capabilities", [])),
preferred_workers=_normalize_list(data.get("preferred_workers", [])),
risk=risk,
approval_policy=validate_type(
data.get("approval_policy", "patch-only"), "approval_policy"
),
dependencies=_normalize_list(data.get("dependencies", [])),
max_delegation_depth=max_depth,
budgets=Budget.from_dict(data.get("budgets")),
evidence_requirements=_normalize_list(data.get("evidence_requirements", [])),
parent_task_id=data.get("parent_task_id"),
correlation_id=data.get("correlation_id"),
metadata=data.get("metadata", {}) if isinstance(data.get("metadata", {}), dict) else {},
data_classification=classification,
execution_zone=execution_zone,
network_policy=network_policy,
logging_policy=logging_policy,
allowed_input_paths=validate_relative_paths(
data.get("allowed_input_paths", []), "allowed_input_paths"
),
allowed_output_paths=validate_relative_paths(
data.get("allowed_output_paths", []), "allowed_output_paths"
),
output_policy=output_policy,
)
def to_dict(self, *, redact: bool = False) -> dict[str, Any]:
payload = {
"schema_version": self.schema_version,
"task_id": self.task_id,
"task_type": self.task_type,
"issuer": self.issuer,
"intent": self.intent,
"required_capabilities": list(self.required_capabilities),
"preferred_workers": list(self.preferred_workers),
"risk": self.risk,
"approval_policy": self.approval_policy,
"dependencies": list(self.dependencies),
"max_delegation_depth": self.max_delegation_depth,
"budgets": self.budgets.to_dict(),
"evidence_requirements": list(self.evidence_requirements),
"parent_task_id": self.parent_task_id,
"correlation_id": self.correlation_id,
"metadata": dict(self.metadata),
"data_classification": self.data_classification,
"execution_zone": self.execution_zone,
"network_policy": self.network_policy,
"logging_policy": self.logging_policy,
"allowed_input_paths": list(self.allowed_input_paths),
"allowed_output_paths": list(self.allowed_output_paths),
"output_policy": self.output_policy,
}
if redact:
payload = redact_value(payload)
return payload
def operation_digest(self) -> str:
return compute_sha256_hex(self.to_dict())
@dataclass
class TaskRecord:
task_id: str
status: str
revision: int
task_plan: TaskPlan
created_at: float
updated_at: float
root_task_id: str
idempotency_key: str | None = None
failure_reason: str | None = None
def with_status(self, status: str) -> "TaskRecord":
allowed = TASK_NEXT_STATES.get(self.status, set())
if status != self.status and (not allowed and self.status not in {"COMPLETED", "FAILED_FINAL", "CANCELLED"}):
raise TaskStateTransitionError(f"Cannot transition {self.status} -> {status}")
if status not in TASK_STATES:
raise TaskStateTransitionError(f"Invalid status {status}")
if status != self.status and self.status in TASK_NEXT_STATES and status not in TASK_NEXT_STATES[self.status]:
raise TaskStateTransitionError(f"Cannot transition {self.status} -> {status}")
return TaskRecord(
task_id=self.task_id,
status=status,
revision=self.revision + 1,
task_plan=self.task_plan,
created_at=self.created_at,
updated_at=_now_ts(),
root_task_id=self.root_task_id,
idempotency_key=self.idempotency_key,
failure_reason=self.failure_reason,
)
def to_dict(self, *, include_task=True, redact_payload: bool = True) -> dict[str, Any]:
payload = {
"task_id": self.task_id,
"status": self.status,
"revision": self.revision,
"root_task_id": self.root_task_id,
"parent_task_id": self.task_plan.parent_task_id,
"created_at": _fmt_ts(self.created_at),
"updated_at": _fmt_ts(self.updated_at),
"failure_reason": self.failure_reason,
}
if include_task:
payload["plan"] = self.task_plan.to_dict(redact=redact_payload)
if self.idempotency_key:
payload["idempotency_key"] = self.idempotency_key
return payload
@dataclass
class WorkerRegistration:
worker_id: str
worker_type: str
identity: str
capabilities: list[str]
trust_zone: str
max_concurrency: int
heartbeat_interval_seconds: int
supported_protocols: list[str]
registered_at: float
last_heartbeat: float
status: str = "active"
disabled: bool = False
owner_identity: str = ""
registration_revision: int = 1
registration_policy: str = "self"
created_at: float = 0.0
updated_at: float = 0.0
disabled_reason: str | None = None
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "WorkerRegistration":
if not isinstance(data, dict):
raise ValueError("worker registration payload must be an object")
now = _now_ts()
worker_id = validate_identifier(data.get("worker_id"), "worker_id")
worker_type = validate_type(data.get("worker_type"), "worker_type")
identity = str(data.get("identity") or "").strip()
if not identity or any(ord(ch) < 32 for ch in identity):
raise ValueError("invalid identity")
max_concurrency = _to_positive_int(data.get("max_concurrency"), default=1)
heartbeat_interval = _to_positive_int(
data.get("heartbeat_interval_seconds"), default=15
)
if not max_concurrency:
raise ValueError("max_concurrency must be positive")
if not heartbeat_interval:
raise ValueError("heartbeat_interval_seconds must be positive")
return cls(
worker_id=worker_id,
worker_type=worker_type,
identity=identity,
capabilities=_normalize_list(data.get("capabilities", [])),
trust_zone=str(data.get("trust_zone") or "default"),
max_concurrency=max_concurrency,
heartbeat_interval_seconds=heartbeat_interval,
supported_protocols=_normalize_list(
data.get("supported_protocols", ["aios-task-v1"])
),
registered_at=float(data.get("registered_at") or now),
last_heartbeat=float(data.get("last_heartbeat") or now),
status=str(data.get("status") or "active"),
disabled=bool(data.get("disabled", False)),
owner_identity=str(data.get("owner_identity") or identity),
registration_revision=int(data.get("registration_revision") or 1),
registration_policy=str(data.get("registration_policy") or "self"),
created_at=float(data.get("created_at") or data.get("registered_at") or now),
updated_at=float(data.get("updated_at") or now),
disabled_reason=data.get("disabled_reason"),
)
def to_dict(self, redact_payload: bool = True) -> dict[str, Any]:
payload = {
"worker_id": self.worker_id,
"worker_type": self.worker_type,
"identity": self.identity,
"capabilities": list(self.capabilities),
"trust_zone": self.trust_zone,
"max_concurrency": self.max_concurrency,
"heartbeat_interval_seconds": self.heartbeat_interval_seconds,
"supported_protocols": list(self.supported_protocols),
"registered_at": _fmt_ts(self.registered_at),
"last_heartbeat": _fmt_ts(self.last_heartbeat),
"status": self.status,
"disabled": self.disabled,
"owner_identity": self.owner_identity,
"registration_revision": self.registration_revision,
"registration_policy": self.registration_policy,
"created_at": _fmt_ts(self.created_at or self.registered_at),
"updated_at": _fmt_ts(self.updated_at or self.last_heartbeat),
"disabled_reason": self.disabled_reason,
}
if redact_payload:
payload = redact_value(payload)
return payload
@dataclass(frozen=True)
class EvidenceEnvelope:
schema_version: int
evidence_id: str
task_id: str
claim_id: str
kind: str
worker_identity: str
payload: dict[str, Any]
created_at: float
previous_evidence_hash: str
evidence_digest: str
signature: str | None = None
attestation_ref: str | None = None
@classmethod
def create(
cls,
*,
task_id: str,
claim_id: str,
kind: str,
worker_identity: str,
payload: dict[str, Any],
previous_evidence_hash: str,
signature: str | None = None,
attestation_ref: str | None = None,
evidence_id: str | None = None,
created_at: float | None = None,
) -> "EvidenceEnvelope":
envelope = cls(
schema_version=1,
evidence_id=evidence_id or f"evd-{uuid.uuid4()}",
task_id=validate_identifier(task_id, "task_id"),
claim_id=validate_identifier(claim_id, "claim_id"),
kind=validate_type(kind, "evidence kind"),
worker_identity=str(worker_identity),
payload=redact_value(payload),
created_at=float(created_at or _now_ts()),
previous_evidence_hash=str(previous_evidence_hash or ""),
evidence_digest="",
signature=signature,
attestation_ref=attestation_ref,
)
return dataclass_replace(
envelope, evidence_digest=compute_sha256_hex(envelope.digest_payload())
)
def digest_payload(self) -> dict[str, Any]:
return {
"schema_version": self.schema_version,
"evidence_id": self.evidence_id,
"task_id": self.task_id,
"claim_id": self.claim_id,
"kind": self.kind,
"worker_identity": self.worker_identity,
"payload": self.payload,
"created_at": self.created_at,
"previous_evidence_hash": self.previous_evidence_hash,
"attestation_ref": self.attestation_ref,
}
def verify(
self,
*,
expected_task_id: str,
expected_claim_id: str,
expected_previous_hash: str,
attestation_verifier: Any = None,
) -> bool:
if (
self.schema_version != 1
or self.task_id != expected_task_id
or self.claim_id != expected_claim_id
or self.previous_evidence_hash != expected_previous_hash
or self.evidence_digest != compute_sha256_hex(self.digest_payload())
):
return False
if self.signature or self.attestation_ref:
return True if attestation_verifier is None else bool(attestation_verifier(self))
return True
def to_dict(self) -> dict[str, Any]:
return {
**self.digest_payload(),
"evidence_digest": self.evidence_digest,
"signature": self.signature,
}
class ProbeOutcome(StrEnum):
NOT_APPLIED = "NOT_APPLIED"
APPLIED_VERIFIED = "APPLIED_VERIFIED"
INDETERMINATE = "INDETERMINATE"
CONFLICT = "CONFLICT"
@dataclass(frozen=True)
class ProbeEvidence:
schema_version: int
probe_id: str
task_id: str
claim_id: str
worker_id: str
worker_identity: str
operation_digest: str
outcome: ProbeOutcome
observed_at: float
expires_at: float
probe_executor_identity: str
evidence_digest: str
signature: str | None = None
attestation_ref: str | None = None
@classmethod
def create(cls, **data: Any) -> "ProbeEvidence":
outcome = data["outcome"]
if not isinstance(outcome, ProbeOutcome):
outcome = ProbeOutcome(str(outcome))
probe = cls(
schema_version=1,
probe_id=data.get("probe_id") or f"probe-{uuid.uuid4()}",
task_id=validate_identifier(data["task_id"], "task_id"),
claim_id=validate_identifier(data["claim_id"], "claim_id"),
worker_id=validate_identifier(data["worker_id"], "worker_id"),
worker_identity=str(data["worker_identity"]),
operation_digest=str(data["operation_digest"]),
outcome=outcome,
observed_at=float(data["observed_at"]),
expires_at=float(data["expires_at"]),
probe_executor_identity=str(data["probe_executor_identity"]),
evidence_digest="",
signature=data.get("signature"),
attestation_ref=data.get("attestation_ref"),
)
return dataclass_replace(
probe, evidence_digest=compute_sha256_hex(probe.digest_payload())
)
def digest_payload(self) -> dict[str, Any]:
return {
"schema_version": self.schema_version,
"probe_id": self.probe_id,
"task_id": self.task_id,
"claim_id": self.claim_id,
"worker_id": self.worker_id,
"worker_identity": self.worker_identity,
"operation_digest": self.operation_digest,
"outcome": self.outcome.value,
"observed_at": self.observed_at,
"expires_at": self.expires_at,
"probe_executor_identity": self.probe_executor_identity,
"attestation_ref": self.attestation_ref,
}
def verify(self, *, now: float, attestation_verifier: Any) -> bool:
if (
self.schema_version != 1
or self.expires_at <= now
or self.observed_at > now
or self.evidence_digest != compute_sha256_hex(self.digest_payload())
or not (self.signature or self.attestation_ref)
):
return False
return bool(attestation_verifier and attestation_verifier(self))
def to_dict(self) -> dict[str, Any]:
return {
**self.digest_payload(),
"evidence_digest": self.evidence_digest,
"signature": self.signature,
}
def dataclass_replace(value: Any, **changes: Any) -> Any:
from dataclasses import replace
return replace(value, **changes)
@dataclass
class WorkerClaim:
claim_id: str
task_id: str
worker_id: str
worker_identity: str
claimed_at: float
lease_until: float
attempt: int
task_revision: int
operation_digest: str
status: str = "ACTIVE"
def to_dict(self, redact_payload: bool = True) -> dict[str, Any]:
payload = {
"claim_id": self.claim_id,
"task_id": self.task_id,
"worker_id": self.worker_id,
"worker_identity": self.worker_identity,
"claimed_at": _fmt_ts(self.claimed_at),
"lease_until": _fmt_ts(self.lease_until),
"attempt": self.attempt,
"task_revision": self.task_revision,
"operation_digest": self.operation_digest,
"status": self.status,
}
if redact_payload:
payload = redact_value(payload)
return payload
@dataclass
class ResultEnvelope:
task_id: str
claim_id: str
worker_identity: str
status: str
outputs: list[dict[str, Any]]
evidence: list[dict[str, Any]]
started_at: str
completed_at: str
result_digest: str | None = None
signature: str | None = None
attestation_ref: str | None = None
operation_digest: str | None = None
trust_status: str = "untrusted_shadow_result"
def digest_payload(self) -> dict[str, Any]:
return {
"task_id": self.task_id,
"claim_id": self.claim_id,
"worker_identity": self.worker_identity,
"status": self.status,
"outputs": self.outputs,
"evidence": self.evidence,
"started_at": self.started_at,
"completed_at": self.completed_at,
"operation_digest": self.operation_digest,
"attestation_ref": self.attestation_ref,
}
def computed_digest(self) -> str:
return compute_sha256_hex(self.digest_payload())
def to_dict(self, redact_payload: bool = True) -> dict[str, Any]:
payload = {
"task_id": self.task_id,
"claim_id": self.claim_id,
"worker_identity": self.worker_identity,
"status": self.status,
"outputs": self.outputs,
"evidence": self.evidence,
"started_at": self.started_at,
"completed_at": self.completed_at,
"result_digest": self.result_digest,
"signature": self.signature,
"attestation_ref": self.attestation_ref,
"operation_digest": self.operation_digest,
"trust_status": self.trust_status,
}
if redact_payload:
payload = redact_value(payload)
return payload
def _normalize_list(value: Any) -> list[str]:
if not isinstance(value, list):
raise ValueError("expected list")
if len(value) > 128 or any(
not isinstance(v, str)
or not v
or len(v) > 256
or any(ord(ch) < 32 for ch in v)
for v in value
):
raise ValueError("invalid list item")
return list(value)
def _to_positive_int(value: Any, default: int | None = None) -> int | None:
if value is None:
return default
try:
number = int(value)
except (TypeError, ValueError):
return default
return number if number >= 0 else default
def _to_float(value: Any) -> float | None:
if value is None:
return None
try:
return float(value)
except (TypeError, ValueError):
return None
def _first_non_none(primary: Any, fallback: Any) -> Any:
if primary is not None:
return primary
return fallback
def iso8601_now() -> str:
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
def _fmt_ts(value: float | None) -> str | None:
if value is None:
return None
return datetime.fromtimestamp(value, tz=timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")