graflo.architecture.evolution.alignment¶
Derived identity: lower a merged class's identity branches to fundamental ops.
A :class:~graflo.architecture.evolution.ops.VertexEquivalence declares the
merged key as ordered funnel branches. A branch over properties the members
already carry needs nothing but the funnel. A
:class:~graflo.architecture.evolution.ops.DerivedBranch or
:class:~graflo.architecture.evolution.ops.LocalKeyBranch needs each source to
compute its attribute, and :func:identity_to_ops emits only fundamental ops
for that —
AddVertexPropertiesOp— declare the derived attributes on the class;AddResourceTransformsOp— per-resource derivation steps (normalization, local-key namespacing) appended to the pipelines;EnsureExtractedFieldsOp— when a producing router restricts what it extracts;ReplaceIdentityOp— a priority funnel over the branches, in declared order.merge_manifeststhen demotes each member's own key to a secondary identity against that final funnel.
The division of labor is deliberate: a primary identity is a property of the
class, so the funnel references only canonical attributes; how a given
source populates them is resource knowledge and lives in that resource's
pipeline. Derivation inputs are RAW source-doc field names — property renames
rewrite vertex.from maps so documents keep their original keys, and
transform.call.input is never rewritten.
The member is the unit of derivation. Every record that becomes the
canonical class was produced as one member of the equivalence cluster by
one resource — by a vertex: Shop step, or by a vertex_router key
whose value was Shop. When a resource produces several members, its
derivations may be keyed by member; the lowering then reads the side
manifest (the merge has already rewritten router type_map values to the
canonical name, so the union no longer knows which key was which member) to
learn how the resource produces each member, and guards the step with
when on the router's discriminator. A guarded step that does not fire
writes nothing, so each member's derivation is the single writer of the
attribute for its own documents.
A derivation that is not keyed by member is guarded the same way whenever a
router produces the class: when admits the discriminator values that route
onto it — the type_map keys mapping to it, or its own name for
pass-through — so the step runs for no other class's documents. An explicit
when on the spec replaces that derived guard. Only a level where a plain
vertex step also produces the class lowers unguarded; there a sibling
class declaring a derived attribute name is refused, since the router would
hand it the derived value.
A level producing the class through routers that read different
discriminators — the two roles of an edge resource — hosts no derivation at
all, keyed or not: the level's transform buffer is shared by every router at
it, so one derived attribute cannot hold one value per role. Such a resource
stays out of the sources and references the class instead;
:func:hosts_member_derivation tells the two apart.
Attributes¶
ClusterMembers = Mapping[str, Collection[str]]
module-attribute
¶
MemberProductions = dict[str, dict[str, _MemberProduction]]
module-attribute
¶
SideManifests = Mapping[str, GraphManifest]
module-attribute
¶
SteppedBranch = DerivedBranch | LocalKeyBranch
module-attribute
¶
VocabularyMap = CanonicalMap | CanonicalizeOp
module-attribute
¶
__all__ = ['AlignmentConflictError', 'IdentityPlan', 'hosts_member_derivation', 'identity_to_ops', 'validate_identity']
module-attribute
¶
logger = logging.getLogger(__name__)
module-attribute
¶
Classes¶
AlignmentConflictError
¶
IdentityPlan
dataclass
¶
One class's declared identity branches, and where its sources derive them.
branches are in funnel order, as :attr:VertexEquivalence.identity
declares them; at is :attr:VertexEquivalence.derive_at;
digest_field is :attr:VertexEquivalence.digest_field; derive
holds :attr:VertexEquivalence.derive, one derivation per attribute, which
name and composite branches key on.
Source code in graflo/architecture/evolution/alignment.py
Attributes¶
at = dataclasses.field(default_factory=dict)
class-attribute
instance-attribute
¶
branches
instance-attribute
¶
derive = ()
class-attribute
instance-attribute
¶
derived
property
¶
Every derived attribute: the derive entries, then derived branches.
digest_field = 'id'
class-attribute
instance-attribute
¶
local_key
property
¶
The local_key branch, if declared.
raw
property
¶
Branches over properties the members carry, as field tuples.
A name or composite branch over derive attributes is not one: its
fields are derived, never all derived and properties at once.
stepped
property
¶
Branches whose attribute the pipelines derive, derived ones first.
vertex
instance-attribute
¶
Methods:¶
__init__(vertex, branches, at=dict(), digest_field='id', derive=())
¶
__post_init__()
¶
Source code in graflo/architecture/evolution/alignment.py
derived_names()
¶
Attributes the pipelines derive: every derived branch, then the local key.
Functions:¶
hosts_member_derivation(manifest, resource, member, *, at=None)
¶
Whether resource can derive an attribute for the records that become member.
False when the level producing member — at, or the one level that
does — produces it through routers reading more than one discriminator:
the level's transform buffer is shared by every router at it, so one
derived attribute cannot hold a different value per role, and a source
naming the resource, keyed or not, is refused. Merge gives such a resource
no automatic key; it references the class by the members' own keys.
True wherever the other validators decide — a resource or level that
does not produce the member, or several levels without at.
Source code in graflo/architecture/evolution/alignment.py
identity_to_ops(plan, *, manifest=None, canonical_maps=(), sides=None, cluster_members=None, member_identity=None, uncovered_producers='refuse', completes_property_branch=None, origins=None)
¶
Lower plan to an ordered list of fundamental ops.
Apply the result to the merged union with
:func:~graflo.architecture.evolution.apply.apply_evolution. When
manifest is given, :func:validate_identity runs first. Member-keyed
sources need sides (the pre-merge manifests) to resolve how each
resource produces each member; merge_manifests passes them. A
local_key source with no tag is tagged with the origin of its
resource's side: origins keyed by side, resolved through sides.
Source code in graflo/architecture/evolution/alignment.py
1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 | |
rekey_members(plan, *, sides, resolve)
¶
Name every member key as its side names the member.
resolve maps (side, key) to the member it names — merge_manifests
passes the cluster's resolution, so a member may be keyed by its own name
or its canonical one. Unresolved keys pass through for the validator to
report. Two keys naming one member under one resource are refused.
Source code in graflo/architecture/evolution/alignment.py
resolve_derivation_levels(plan, manifest, *, productions=None)
¶
Pipeline level each referenced resource derives at, keyed by resource.
A derivation must land at the level that produces the class: an actor
reads its transform buffer at its own LocationIndex with no ancestor
fallback, and a descend subtree runs before its own level's
transforms. Placing it anywhere else derives nothing, silently.
plan.at (the equivalence's derive_at) overrides the lookup. A
member-keyed resource takes the level its members are produced at (see
:func:resolve_member_productions). Otherwise a resource must produce the
class at exactly one level — zero and several are both
:class:AlignmentConflictError, because either answer the resolver could
pick would be a guess about where the source fields live.
Source code in graflo/architecture/evolution/alignment.py
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 | |
resolve_member_productions(plan, sides)
¶
How each resource produces every member its sources are keyed by.
Read off the sides — the pre-merge manifests — because the merge has
already rewritten each router's type_map values to the canonical name,
so the union cannot say which key produced which member.
All members a resource keys must resolve to one pipeline level: the
derivations are appended per resource at one level, and a member produced
under a different descend would not see them.
Source code in graflo/architecture/evolution/alignment.py
uncovered_producers(plan, manifest, *, completes_property_branch=None)
¶
Resources that upsert the class but can complete none of its key, sorted.
Every record of the class keys on the funnel over the plan's branches. A
resource no derived branch names derives none of their attributes, so
each record it upserts completes no branch and is dropped -- the whole
resource, and every edge it emits to the class -- unless it completes a
property branch. completes_property_branch answers that per resource
(merge knows each member's properties); without it, a plan with any
property branch is taken to cover every resource. A resource whose steps
producing the class only look it up (lookup_only) or find it by a
secondary identity (find), on a vertex step or a router, creates
nothing and is not listed.
Source code in graflo/architecture/evolution/alignment.py
validate_identity(plan, manifest, *, canonical_maps=(), sides=None, cluster_members=None, member_identity=None, uncovered_producers='refuse', completes_property_branch=None)
¶
Fail loudly when plan contradicts manifest or the canonical maps.
manifest is the merged union the ops will be applied to. Pass the maps
used to canonicalize the sides — declared :class:CanonicalMap s or the
composite :class:~graflo.architecture.evolution.ops.CanonicalizeOp
merge applied — to catch derivation inputs written in canonical
vocabulary: renamed documents still carry their raw field names, so a
rename target used as a derivation input reads an absent field and
silently derives nothing.
sides are the pre-merge side manifests, required by member-keyed sources; cluster_members are the cluster's members per side, which lets a member key be checked against the cluster it claims.
member_identity are the identity fields the class's records carry — for a merged class, its members' own pre-merge keys under their canonical names. A derived attribute named like one would overwrite that key's value with the derived one. It defaults to the class's current identity in manifest.
uncovered_producers decides a resource that upserts the class but
derives none of its key (see :func:uncovered_producers): refuse it,
or allow it for a caller that turns it into a reference -- merge does,
once the member keys it can look the class up by exist.
completes_property_branch says whether a resource's records complete
one of the plan's property branches; see :func:uncovered_producers.
Source code in graflo/architecture/evolution/alignment.py
730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 | |