graflo.architecture.evolution.autogenerate¶
Derive a contract change set from two manifests.
Before this, nothing produced :data:~graflo.architecture.evolution.ops.ManifestOp
values: every op in the codebase was hand-built. SchemaDiff (migrate)
produces description records on a different plane — it reports what changed
but emits nothing that can be applied — and it only ever looks at Schema,
never at ingestion_model or bindings.
:func:diff_manifests closes that gap for the mechanically derivable ops, and
is explicit about the rest. Its contract is the replay invariant::
ops, warnings = diff_manifests(base, target)
manifest_hash(apply_evolution(base, ops)) == manifest_hash(target)
Where that does not hold, :func:diff_manifests_verified reports the residual
rather than claiming success. Silence about an incomplete diff is the one
failure mode a change-set generator must not have — it produces a revision that
looks applied and is not.
Renames are ambiguous by construction: a dropped mail plus an added
email is indistinguishable from a rename. Pass :class:RenameHints when the
intent is known; otherwise the pair is emitted as a drop and an add.
Attributes¶
__all__ = ['RenameHints', 'diff_manifests', 'diff_manifests_verified']
module-attribute
¶
logger = logging.getLogger(__name__)
module-attribute
¶
Classes¶
RenameHints
¶
Bases: ConfigBaseModel
Renames the differ cannot infer, supplied by the caller.
A drop plus an add is structurally identical to a rename. Guessing would turn a data-preserving rename into a destructive drop (or the reverse), so the differ never guesses.
Source code in graflo/architecture/evolution/autogenerate.py
Attributes¶
edge_properties = PydanticField(default_factory=dict, description='``{relation: {old_field: new_field}}``.')
class-attribute
instance-attribute
¶
relations = PydanticField(default_factory=dict, description='``{old_relation: new_relation}``.')
class-attribute
instance-attribute
¶
resources = PydanticField(default_factory=dict, description='``{old_resource: new_resource}``.')
class-attribute
instance-attribute
¶
vertex_properties = PydanticField(default_factory=dict, description='``{vertex_name: {old_field: new_field}}``.')
class-attribute
instance-attribute
¶
vertices = PydanticField(default_factory=dict, description='``{old_vertex_name: new_vertex_name}``.')
class-attribute
instance-attribute
¶
Functions:¶
diff_manifests(base, target, *, hints=None)
¶
Ops turning base into target, plus warnings for what was not expressed.
Ops are ordered so each one's preconditions hold when it runs: renames first (so later ops address the new names), then additions, then property and identity changes, then removals last.
Source code in graflo/architecture/evolution/autogenerate.py
diff_manifests_verified(base, target, *, hints=None)
¶
:func:diff_manifests, with the replay invariant actually checked.
Applies the derived ops to a copy of base and compares the result's hash to target's. A mismatch appends a warning naming the residual difference instead of letting an incomplete change set pass as complete.