Files
lda-wf/src/wf_core/context_contracts.py
T

138 lines
3.8 KiB
Python

from __future__ import annotations
from collections.abc import Mapping
from copy import deepcopy
from dataclasses import dataclass
from typing import Any
type ContextSchema = dict[str, Any]
PRIOR_OUTCOME_CONTEXT_KEY = "prior_outcome"
ACTIVATED_INCOMING_EDGE_CONTEXT_KEY = "activated_incoming_edge"
SCOPE_ID_CONTEXT_KEY = "scope_id"
LINEAGE_ID_CONTEXT_KEY = "lineage_id"
PARENT_LINEAGE_ID_CONTEXT_KEY = "parent_lineage_id"
LOOP_ITEM_CONTEXT_KEY = "loop_item"
LOOP_INDEX_CONTEXT_KEY = "loop_index"
FOREACH_CONTEXT_KEY = "foreach"
@dataclass(frozen=True, slots=True)
class ContextFieldContract:
"""Semantic contract for one key exposed by a runtime execution frame."""
name: str
schema: ContextSchema
description: str
STANDARD_CONTEXT_FIELDS = (
ContextFieldContract(
PRIOR_OUTCOME_CONTEXT_KEY,
{"type": ["string", "null"]},
"Prior route outcome",
),
ContextFieldContract(
ACTIVATED_INCOMING_EDGE_CONTEXT_KEY,
{"type": ["string", "null"]},
"Incoming step id",
),
ContextFieldContract(
SCOPE_ID_CONTEXT_KEY,
{"type": "string"},
"Execution scope id",
),
ContextFieldContract(
LINEAGE_ID_CONTEXT_KEY,
{"type": "string"},
"Execution lineage id",
),
ContextFieldContract(
PARENT_LINEAGE_ID_CONTEXT_KEY,
{"type": ["string", "null"]},
"Parent lineage id",
),
)
STANDARD_CONTEXT_FIELD_NAMES = frozenset(
field.name for field in STANDARD_CONTEXT_FIELDS
)
RESERVED_CONTEXT_KEYS = STANDARD_CONTEXT_FIELD_NAMES | {
LOOP_ITEM_CONTEXT_KEY,
LOOP_INDEX_CONTEXT_KEY,
FOREACH_CONTEXT_KEY,
}
def foreach_context_fields(
alias: str,
item_schema: ContextSchema,
) -> tuple[ContextFieldContract, ...]:
"""Return the iteration keys, including a configured item alias once."""
item_contract = ContextFieldContract(
LOOP_ITEM_CONTEXT_KEY,
deepcopy(item_schema),
"Current foreach item",
)
index_contract = ContextFieldContract(
LOOP_INDEX_CONTEXT_KEY,
{"type": "integer"},
"Current foreach item index",
)
fields = [item_contract, index_contract]
if alias and alias not in RESERVED_CONTEXT_KEYS:
fields.append(
ContextFieldContract(
alias,
deepcopy(item_schema),
"Current foreach item",
)
)
return tuple(fields)
def foreach_entry_schema(node_id: str, item_schema: ContextSchema) -> ContextSchema:
"""Return the JSON schema for one structured ``foreach.<id>`` entry.
Identity fields disclose the dynamic activation/frame/scope/lineage when
advanced runtime code needs them; ``item`` carries the inferred
collection item schema.
"""
return {
"type": "object",
"properties": {
"node_id": {"const": node_id},
"activation_id": {"type": "string"},
"frame_id": {"type": "string"},
"scope_id": {"type": "string"},
"lineage_id": {"type": "string"},
"index": {"type": "integer"},
"item": deepcopy(item_schema),
},
"required": [
"node_id",
"activation_id",
"frame_id",
"scope_id",
"lineage_id",
"index",
"item",
],
"additionalProperties": False,
}
def structured_foreach_contract(
entry_schemas: Mapping[str, ContextSchema],
) -> ContextFieldContract:
"""Return the ``foreach`` map contract for one static owner stack."""
return ContextFieldContract(
FOREACH_CONTEXT_KEY,
{
"type": "object",
"properties": dict(entry_schemas),
"required": sorted(entry_schemas),
"additionalProperties": False,
},
"Structured foreach context by static node id",
)