graflo.architecture.evolution.alignment¶
Identity alignment: merge an equivalence identity from fundamental ops.
An :class:IdentityAlignment states, for one canonical class, which canonical
attributes carry cross-source entity equivalence and how each resource derives
them. It is a composer, not a mechanism: :func:alignment_to_ops emits only
fundamental ops —
AddVertexPropertiesOp— declare the canonical attributes on the class;AddResourceTransformsOp— per-resource derivation steps (gating, normalization, local-key namespacing) appended to the pipelines;ReplaceIdentityOp— a priority funnel over the canonical attributes, in declared order, with the namespacedlocal_keyas the last branch;AddSecondaryIdentitiesOp— the retired side keys as lookup-only secondary identities.
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. Only a level
where a plain vertex step also produces the class, or whose routers read
different discriminators, lowers unguarded; there a sibling class declaring a
canonical attribute name is refused, since the router would hand it the
derived value.
Attributes¶
AlignmentRow = AlignmentAttribute
module-attribute
¶
ClusterMembers = Mapping[str, Collection[str]]
module-attribute
¶
MemberProductions = dict[str, dict[str, _MemberProduction]]
module-attribute
¶
SideManifests = Mapping[str, GraphManifest]
module-attribute
¶
VocabularyMap = CanonicalMap | CanonicalizeOp
module-attribute
¶
__all__ = ['AlignmentAttribute', 'AlignmentConflictError', 'AlignmentRow', 'DerivationSpec', 'IdentityAlignment', 'LocalKeySource', 'LocalKeySpec', 'SharedDerivation', 'alignment_to_ops', 'validate_alignment']
module-attribute
¶
logger = logging.getLogger(__name__)
module-attribute
¶
Classes¶
AlignmentAttribute
¶
Bases: ConfigBaseModel
One aligned canonical attribute; list position = funnel priority.
Each entry lowers to one
:class:~graflo.architecture.schema.identity_funnel.IdentityBranch over
name, so the list order is the funnel order. name is what the
derived attribute is called — nothing collapses onto it, which is why it is
not into; into is accepted as a legacy alias.
sources is keyed by resource because derivation inputs are that
resource's raw column names. An entry takes one of three shapes:
- a single :class:
DerivationSpec— the resource produces one member of the cluster, or all its members share the key column; - a list of specs — the resource produces several members, each with
its own key column, and at most one spec yields a value for any
document (the others read an empty column). Lowers to scratch fields
plus a
coalesce_fieldsstep; - a dict keyed by member class — the resource produces several members
and which one a document is must decide the derivation (the members
share a column, or each carries its own marker). The lowering asks the
side manifest how the resource produces each member and guards the step
accordingly (
whenon the router's discriminator, or nothing for a plainvertexstep). A member is keyed by its own name on its side or, throughmerge_manifests, by its canonical name; - a :class:
SharedDerivation— the same dict, spelled once: one call shared by the listed members, with only the parameters that differ.
Behind a vertex_router the first two shapes are guarded as well: the
lowering reads which discriminator values route onto the class and puts
them in when, so the steps run for no other class's documents.
Source code in graflo/architecture/evolution/ops.py
1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 | |
Attributes¶
name = PydanticField(..., validation_alias=AliasChoices('name', 'into'), description='Canonical attribute name on the class; funnel branch id. ``into`` is accepted as a legacy alias.')
class-attribute
instance-attribute
¶
sources = PydanticField(..., min_length=1, description='Per-resource derivation: ``{resource: spec}``; ``{resource: [spec, ...]}`` when the members the resource produces carry different key columns; ``{resource: {member_class: spec}}`` when the member a document is must decide the derivation, or a ``SharedDerivation`` spelling that dict once.')
class-attribute
instance-attribute
¶
Methods:¶
members_for(resource)
¶
Member classes keying resource's specs, or None if unkeyed.
specs_for(resource)
¶
Derivations resource contributes to this attribute, in order.
Source code in graflo/architecture/evolution/ops.py
AlignmentConflictError
¶
DerivationSpec
¶
Bases: ConfigBaseModel
How one resource derives a canonical attribute from its raw doc fields.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
foo = PydanticField(default='gated_normalized_key', description='Function name; called as ``foo(*values, **params)``.')
class-attribute
instance-attribute
¶
input = PydanticField(..., min_length=1, description='RAW source-doc field names fed to the function, in order. Documents keep their original keys after property renames, so canonical property names are usually wrong here.')
class-attribute
instance-attribute
¶
module = PydanticField(default='graflo.util.transform', description='Module holding the derivation function.')
class-attribute
instance-attribute
¶
params = PydanticField(default_factory=dict, description='Keyword parameters for the function (gate prefix, ...).')
class-attribute
instance-attribute
¶
IdentityAlignment
¶
Bases: ConfigBaseModel
Cross-source identity alignment for one canonical class.
attributes order is funnel priority: a record keys by the highest-priority
aligned attribute it carries. Two records fuse when their strongest
present attribute coincides — a match on a lower-priority attribute does
NOT fuse records when one of them also carries a higher-priority one.
Source code in graflo/architecture/evolution/ops.py
2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 | |
Attributes¶
at = PydanticField(default_factory=dict, description='Per-resource pipeline level to derive at, as ``descend`` step indices. Omitted resources resolve to the single level producing ``vertex`` (for member-keyed sources: the level producing the member on its side); supply a path only when a resource produces it at more than one level.')
class-attribute
instance-attribute
¶
attributes = PydanticField(default_factory=list, validation_alias=AliasChoices('attributes', 'rows'), description='Aligned canonical attributes, in priority order. ``rows`` is accepted as a legacy alias.')
class-attribute
instance-attribute
¶
local_key = PydanticField(default=None, description='Fallback identity for records carrying no aligned attribute. Without it such records get no identity and are dropped.')
class-attribute
instance-attribute
¶
secondary_identities = PydanticField(default_factory=dict, description='Retired side keys kept as lookup-only secondary identities: ``{name: [field, ...]}``.')
class-attribute
instance-attribute
¶
vertex = PydanticField(..., description='The canonical class whose identity is being aligned.')
class-attribute
instance-attribute
¶
LocalKeySource
¶
Bases: ConfigBaseModel
Where one resource's side-local key comes from, and its namespace tag.
The tag is what keeps records of different sources apart once they fail to
fuse: f2 from one source and f2 from another are different
entities, and a:f2 / b:f2 say so. It is required so that opting
out is a statement, not an omission: tag=None (stored as "", the
neutral element, so it survives serialization) keeps the raw value as the
local key with no separator — the author's claim that the values are
already unique across every source of the class (UUIDs, IRIs, ids the
source itself prefixes).
Source code in graflo/architecture/evolution/ops.py
Attributes¶
field = PydanticField(..., description='RAW doc field carrying the side-local key.')
class-attribute
instance-attribute
¶
gate = PydanticField(default=None, description="Optional RAW doc field deciding whether this source applies — the router's discriminator when one resource contributes several local keys. Omit when ``field`` is empty for the other branches, which already selects.")
class-attribute
instance-attribute
¶
gate_prefix = PydanticField(default='', description='Required prefix of the ``gate`` value; ``""`` always passes. Meaningless without ``gate``.')
class-attribute
instance-attribute
¶
tag = PydanticField(..., description='Namespace tag: tag \'a\' turns \'f2\' into \'a:f2\'. ``None`` or ``""`` keeps the raw value, no separator — only for values already unique across every source of the class.')
class-attribute
instance-attribute
¶
LocalKeySpec
¶
Bases: ConfigBaseModel
The canonical fallback identity attribute for non-aligned records.
sources takes the same three shapes as
:attr:AlignmentAttribute.sources: one source, a list (one per member,
each reading its own column), or a dict keyed by member class (the member
decides; the gate is derived from how the resource produces it, so a
member-keyed source must not set gate).
Source code in graflo/architecture/evolution/ops.py
2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 | |
Attributes¶
name = PydanticField(default='local_key', validation_alias=AliasChoices('name', 'into'), description='Canonical fallback property name on the class. ``into`` is accepted as a legacy alias.')
class-attribute
instance-attribute
¶
sep = PydanticField(default=':', description='Separator between tag and key.')
class-attribute
instance-attribute
¶
sources = PydanticField(..., min_length=1, description='Per-resource local-key wiring: ``{resource: source}``; ``{resource: [source, ...]}`` when the members carry different key columns; ``{resource: {member_class: source}}`` when the member decides.')
class-attribute
instance-attribute
¶
Methods:¶
members_for(resource)
¶
Member classes keying resource's sources, or None if unkeyed.
sources_for(resource)
¶
Local-key sources resource contributes, in order.
Source code in graflo/architecture/evolution/ops.py
SharedDerivation
¶
Bases: ConfigBaseModel
One derivation shared by several members, varying only in parameters.
The compact spelling of the member-keyed form for the common case: the
call is the same for every member and only a parameter changes — a marker
prefix per class — or nothing does. members is a list of member
classes, or a dict from member to the parameters that differ; each
member's derivation is spec with those parameters laid over
spec.params. Anything else that differs between members — the input
columns, the function — is a different derivation: spell it with the
explicit {member: spec} dict.
Expands to that dict; the lowering never sees this model.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
members = PydanticField(..., description='Member classes sharing ``spec``: a list when nothing varies, or ``{member: {param: value}}`` naming what does.')
class-attribute
instance-attribute
¶
spec = PydanticField(..., description='The derivation every member shares.')
class-attribute
instance-attribute
¶
Methods:¶
expand()
¶
The explicit {member: spec} dict this stands for.
Source code in graflo/architecture/evolution/ops.py
Functions:¶
alignment_to_ops(alignment, *, manifest=None, canonical_maps=(), sides=None, cluster_members=None)
¶
Merge the alignment into 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_alignment runs first. Member-keyed
sources need sides (the pre-merge manifests) to resolve how each
resource produces each member; merge_manifests passes them.
Source code in graflo/architecture/evolution/alignment.py
910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 | |
rekey_members(alignment, *, 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 aligned 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(alignment, manifest, *, productions=None)
¶
Pipeline level each referenced resource derives at, keyed by resource.
A derivation must land at the level that produces the aligned 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.
IdentityAlignment.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
428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 | |
resolve_member_productions(alignment, 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
validate_alignment(alignment, manifest, *, canonical_maps=(), sides=None, cluster_members=None)
¶
Fail loudly when alignment contradicts manifest or the canonical maps.
manifest is the merged union the alignment 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 aligned cluster's members per side, which lets a member key be checked against the cluster it claims.
Source code in graflo/architecture/evolution/alignment.py
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 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 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 | |