Skip to content

graflo.architecture.evolution.state_core.plan

Planning the operations that lift a manifest into a twin-ready shape.

A pure planner: it emits :data:~graflo.architecture.evolution.ops.ManifestOp values and applies nothing. apply_evolution applies them, invert_ops undoes them, and the op list itself is the reviewable artifact -- an operator reads what the lift proposes before any of it runs. That is the whole argument for expressing this as Operations rather than as a bespoke transform: a transform is a black box that either did the right thing or did not.

What a lift does not do. It changes the contract, not the data flow. A lifted manifest declares <Type>State and Evidence, but no resource populates them, so a manifest that carries an ingestion_model will still report the profile's "provenance materialised at ingest" finding afterwards -- correctly. Scaffolding the pipelines needs resource-level ops and a statement of which resource feeds which type, and is deliberately out of scope here. This is the misreading the next reader will have, so it is also in the CLI help.

Attributes

__all__ = ['LiftError', 'plan_lift'] module-attribute

Classes

LiftError

Bases: ValueError

The lift cannot be planned, and the message says which declaration is missing.

Raised rather than guessed. Every case here is one where the planner can see that something is wrong but not what the right answer is -- an ungrounded type, a name it would have to overwrite, a property it was told to move that is part of the key.

Source code in graflo/architecture/evolution/state_core/plan.py
class LiftError(ValueError):
    """The lift cannot be planned, and the message says which declaration is missing.

    Raised rather than guessed. Every case here is one where the planner can see
    that something is wrong but not what the right answer is -- an ungrounded
    type, a name it would have to overwrite, a property it was told to move that
    is part of the key.
    """

Functions:

plan_lift(manifest, spec, *, authored=None)

The ops that lift manifest into a twin-ready schema.

Parameters:

Name Type Description Default
manifest GraphManifest

the manifest to lift, already finish_init()-ed.

required
spec LiftSpec

the semantic declarations the planner cannot infer.

required
authored dict[str, Any] | None

the manifest as written, when available. Only an authored document distinguishes "this vertex declared no identity" from "this vertex declared every property as its identity", so without it the planner cannot detect the fallback and does not try.

None

Returns:

Type Description
list[ManifestOp]

Ops in application order: grounding first, then structure, then the

list[ManifestOp]

scaffolding, then the removals. Ordering matters -- a property is moved

list[ManifestOp]

onto its State before it is removed from the entity.

Raises:

Type Description
LiftError

a declaration the planner needs is missing, or a name it would mint is already taken.

Source code in graflo/architecture/evolution/state_core/plan.py
def plan_lift(
    manifest: GraphManifest,
    spec: LiftSpec,
    *,
    authored: dict[str, Any] | None = None,
) -> list[ManifestOp]:
    """The ops that lift *manifest* into a twin-ready schema.

    Args:
        manifest: the manifest to lift, already ``finish_init()``-ed.
        spec: the semantic declarations the planner cannot infer.
        authored: the manifest as written, when available. Only an authored
            document distinguishes "this vertex declared no identity" from
            "this vertex declared every property as its identity", so without
            it the planner cannot detect the fallback and does not try.

    Returns:
        Ops in application order: grounding first, then structure, then the
        scaffolding, then the removals. Ordering matters -- a property is moved
        onto its ``State`` before it is removed from the entity.

    Raises:
        LiftError: a declaration the planner needs is missing, or a name it
            would mint is already taken.
    """
    schema = manifest.graph_schema
    if schema is None:
        raise LiftError(
            "cannot lift a manifest with no schema block: there are no types to "
            "ground and nothing to attach state to"
        )

    core = schema.core_schema
    vertex_config = core.vertex_config
    existing = set(vertex_config.vertex_set)
    by_name = {vertex.name: vertex for vertex in vertex_config.vertices}

    unknown = sorted(
        (set(spec.grounding) | set(spec.stateful) | set(spec.observed)) - existing
    )
    if unknown:
        raise LiftError(f"spec names types the manifest does not have: {unknown}")

    ops: list[ManifestOp] = []
    ops += _identity_ops(spec, vertex_config, authored)
    ops += _grounding_ops(spec, core)
    ops += _directionality_ops(core)
    ops += _unit_ops(spec, by_name)
    scaffold_ops, scaffolded = _scaffold_ops(spec, by_name, existing)
    ops += scaffold_ops
    ops += _provenance_ops(spec, existing, scaffolded)
    ops += _retire_ops(spec, by_name)
    return ops