graflo.architecture.evolution.aspects¶
An aspect adds the types and relations every domain needs and none owns --
where a fact came from, how a fact changes over time -- grounded in a standard
vocabulary, as a reviewable op list over the manifest. Each aspect plans on
its own; :func:plan_lift composes the ones a caller names.
Four ship today:
provenance:SourceRecordandAgentwithwasDerivedFromandwasAttributedTo(PROV-O). Needs no spec.state:<Type>Statewith a validity interval and<Type>Observationwith a unit per row (PROV-O, SOSA, QUDT). Needs a :class:~graflo.architecture.evolution.aspects.spec.LiftSpec, because a planner can see structure but not meaning.access: a reservedaccess_zoneslabel on the types a spec names, each with a merge mode. Needs a spec; not lifted by default.sync: a reservedsync_hashon every type a resource'ssyncblock owns, written by bulk and sync runs. Needs no spec; not lifted by default.
The output is an op list, so the conversion is replayable, invertible and
reviewable through the machinery every other manifest change already uses.
A :class:~graflo.architecture.evolution.aspects.recipe.LiftRecipe records
what was asked of a lift, so that it can be planned again (:func:plan_relift).
Modules:
| Name | Description |
|---|---|
access |
The access aspect: integer zone labels on declared types. |
base |
What every aspect shares: the refusal, and the entry that names it. |
iris |
The IRIs the aspects ground their types in. |
join |
A lift is a join: the manifest gains what an aspect's vocabulary has and it lacks. |
provenance |
The |
recipe |
What a lift was asked to do, recorded so that it can be done again. |
spec |
What a caller must declare for a lift, and what the lift works out itself. |
state |
The |
sync |
The sync aspect: a record's sync hash on the vertex types it owns. |
Attributes¶
ACCESS = 'access'
module-attribute
¶
ASPECTS = {PROVENANCE: Aspect(name=PROVENANCE, description='SourceRecord and Agent with wasDerivedFrom and wasAttributedTo: where a fact came from and who produced it (PROV-O).', needs_spec=False, version=1), STATE: Aspect(name=STATE, description='<Type>State with a validity interval and <Type>Observation with a unit per row: how facts change and what was measured (PROV-O, SOSA, QUDT).', needs_spec=True, version=1), ACCESS: Aspect(name=ACCESS, description='A reserved access_zones label on declared types: the zone ids a writer stamps and a read guard checks.', needs_spec=True, version=1), SYNC: Aspect(name=SYNC, description="A reserved sync_hash on the types a resource's sync block owns: what lets a sync run skip an unchanged record.", needs_spec=False, version=1)}
module-attribute
¶
DEFAULT_ASPECTS = (PROVENANCE, STATE)
module-attribute
¶
PROVENANCE = 'provenance'
module-attribute
¶
STATE = 'state'
module-attribute
¶
SYNC = 'sync'
module-attribute
¶
__all__ = ['ACCESS', 'ASPECTS', 'DEFAULT_ASPECTS', 'PROVENANCE', 'STATE', 'SYNC', 'Aspect', 'EdgeGrounding', 'Grounding', 'LiftError', 'LiftRecipe', 'LiftSpec', 'StatePlan', 'build_lift_recipe', 'plan_access', 'plan_lift', 'plan_provenance', 'plan_relift', 'plan_state', 'plan_sync', 'recipe_drift', 'recorded_spec', 'resolve_aspects']
module-attribute
¶
Classes¶
Aspect
¶
Bases: ConfigBaseModel
One cross-cutting vocabulary a manifest can be lifted with.
An aspect is domain-free: it adds the types and relations that let a schema say something every domain needs to say -- where a fact came from, how a fact changes over time -- grounded in a standard vocabulary.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
How the CLI and a caller address it ( |
description |
str
|
One line, for listings. |
needs_spec |
bool
|
Whether planning it needs declarations only a
:class: |
version |
int
|
Bumped when the vocabulary, or what the planner emits for a given spec, changes. A recorded lift names it, so a re-lift can tell which aspects moved since. |
Source code in graflo/architecture/evolution/aspects/base.py
EdgeGrounding
¶
Bases: Grounding
A grounding addressed at one edge triple.
Source code in graflo/architecture/evolution/aspects/spec.py
Attributes¶
relation = PydanticField(default=None, description='Relation name.')
class-attribute
instance-attribute
¶
source = PydanticField(..., description='Source vertex type name.')
class-attribute
instance-attribute
¶
target = PydanticField(..., description='Target vertex type name.')
class-attribute
instance-attribute
¶
Grounding
¶
Bases: ConfigBaseModel
An external-vocabulary anchor a caller asserts for one element.
Source code in graflo/architecture/evolution/aspects/spec.py
Attributes¶
exact_match = PydanticField(default_factory=list, description='IRIs asserted equivalent to it.')
class-attribute
instance-attribute
¶
iri = PydanticField(default=None, description='IRI of the concept this element denotes.')
class-attribute
instance-attribute
¶
synonyms = PydanticField(default_factory=list, description='Alternative labels an agent may meet.')
class-attribute
instance-attribute
¶
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/aspects/base.py
LiftRecipe
¶
Bases: ConfigBaseModel
The aspects a manifest was lifted with, their versions and the spec.
Source code in graflo/architecture/evolution/aspects/recipe.py
Attributes¶
aspects = PydanticField(..., min_length=1, description='The aspects lifted, in the order named.')
class-attribute
instance-attribute
¶
base = PydanticField(..., description='Content hash of the manifest the lift was planned on.')
class-attribute
instance-attribute
¶
graflo_version = PydanticField(default=None, description='The graflo that planned the lift, when known.')
class-attribute
instance-attribute
¶
kind = 'lift'
class-attribute
instance-attribute
¶
spec = PydanticField(default_factory=dict, description='The lift spec, without its defaults (see `recorded_spec`).')
class-attribute
instance-attribute
¶
versions = PydanticField(..., description='The version of each aspect at the time of the lift.')
class-attribute
instance-attribute
¶
Methods:¶
content_hash()
¶
Content address of what the lift declared.
graflo_version is left out: it says which tool planned the lift,
not what was asked of it, and the aspect versions already say whether
the vocabulary differs.
Source code in graflo/architecture/evolution/aspects/recipe.py
LiftSpec
¶
Bases: ConfigBaseModel
The semantic input to :func:~graflo.architecture.evolution.aspects.plan_lift.
Source code in graflo/architecture/evolution/aspects/spec.py
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | |
Attributes¶
access_edges = PydanticField(default_factory=dict, description='Edge relations that gain ``access_zones``, each with the merge mode of its zones on upsert: ``replace`` or ``union``.')
class-attribute
instance-attribute
¶
access_for = PydanticField(default_factory=dict, description='Vertex types that gain ``access_zones``, each with the merge mode of its zones on upsert: ``replace`` or ``union``.')
class-attribute
instance-attribute
¶
edge_grounding = PydanticField(default_factory=list, description='Per-edge grounding, addressed by triple.')
class-attribute
instance-attribute
¶
grounding = PydanticField(default_factory=dict, description='Per-vertex grounding: ``{type_name: Grounding}``.')
class-attribute
instance-attribute
¶
identity = PydanticField(default_factory=dict, description='Explicit identity for types that would otherwise fall back to ``identity_from_all_properties``. Required only for those.')
class-attribute
instance-attribute
¶
measured = PydanticField(default_factory=dict, description="Units for existing properties, as ``{'Type.property': ucum_token}``. UCUM has no currency, so currency uses ISO-4217 (``USD``).")
class-attribute
instance-attribute
¶
mentions_for = PydanticField(default_factory=list, description='Existing types a source record can name: ``SourceRecord`` gains a ``mentions`` edge to each.')
class-attribute
instance-attribute
¶
observed = PydanticField(default_factory=list, description='Types that gain a ``<Type>Observation`` scaffold for measurements taken of them at a time.')
class-attribute
instance-attribute
¶
provenance = PydanticField(default=True, description="Lift the ``provenance`` aspect as well: ``SourceRecord`` and ``Agent`` with the edges that make 'where did this fact come from' answerable. ``false`` leaves it out, as does not naming the aspect.")
class-attribute
instance-attribute
¶
provenance_for = PydanticField(default_factory=list, description='Existing types whose facts are read from a source record: each gains a ``wasDerivedFrom`` edge to ``SourceRecord``. The state and observation scaffolds get one regardless.')
class-attribute
instance-attribute
¶
retire = PydanticField(default='move', description='What happens to a property named in ``stateful``. ``move`` removes it from the entity, since a fact that changes over time does not belong on the thing it is about. ``keep`` leaves it as a denormalized current value beside the history.')
class-attribute
instance-attribute
¶
stateful = PydanticField(default_factory=dict, description='Per-type mutable properties: ``{type_name: [property, ...]}``. Each named type gains a ``<Type>State`` carrying those properties over a validity interval.')
class-attribute
instance-attribute
¶
Methods:¶
measured_for(vertex)
¶
{property: unit} for one type.
StatePlan
¶
Bases: ConfigBaseModel
What the state aspect planned, in the two halves a composed lift needs apart.
Attributes:
| Name | Type | Description |
|---|---|---|
ops |
list[ManifestOp]
|
The additive ops, in application order: grounding first, then structure, then the scaffolding. |
scaffolded |
list[str]
|
Names of the |
retire |
list[ManifestOp]
|
The destructive half, to be applied last. |
Source code in graflo/architecture/evolution/aspects/state/plan.py
Functions:¶
build_lift_recipe(manifest, spec=None, *, aspects)
¶
The recipe of lifting manifest with aspects under spec.
Raises:
| Type | Description |
|---|---|
LiftError
|
an aspect name no aspect has. |
Source code in graflo/architecture/evolution/aspects/recipe.py
plan_access(manifest, *, vertices, edges)
¶
The ops that label vertices and edges with access_zones.
A type that already declares the field keeps it when it is the same field;
one declared with another type or merge mode is a contradiction
(:func:~graflo.architecture.evolution.aspects.join.join).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
manifest
|
GraphManifest
|
the manifest to lift, already |
required |
vertices
|
Mapping[str, FieldMerge]
|
vertex type -> merge mode of its zones. |
required |
edges
|
Mapping[str, FieldMerge]
|
edge relation -> merge mode of its zones. |
required |
Raises:
| Type | Description |
|---|---|
LiftError
|
nothing to label, a type or relation the manifest does not have, or a field that contradicts the aspect's. |
Source code in graflo/architecture/evolution/aspects/access/plan.py
plan_lift(manifest, spec=None, *, aspects=DEFAULT_ASPECTS, authored=None)
¶
The ops that lift manifest with the named aspects.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
manifest
|
GraphManifest
|
the manifest to lift, already |
required |
spec
|
LiftSpec | None
|
the semantic declarations a planner cannot infer. Required by the
|
None
|
aspects
|
Iterable[str]
|
which aspects to lift, by name. The |
DEFAULT_ASPECTS
|
authored
|
dict[str, Any] | None
|
the manifest as written, when available; see
:func: |
None
|
Returns:
| Type | Description |
|---|---|
list[ManifestOp]
|
Ops in application order: the additive ops of every aspect, then the |
list[ManifestOp]
|
removals, so a reviewer sees everything that is added before anything |
list[ManifestOp]
|
that is taken away. |
Raises:
| Type | Description |
|---|---|
LiftError
|
an unknown aspect, a missing declaration, or a name the lift would mint that is already taken. |
Source code in graflo/architecture/evolution/aspects/__init__.py
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | |
plan_provenance(manifest, *, attach=(), mentions=())
¶
The ops that make lineage expressible in manifest.
SourceRecord -wasAttributedTo-> Agent is emitted even when there is
nothing else to attach: it is what makes the schema able to say where a
fact came from, which is the question the world-model profile asks.
The vocabulary is joined with what the manifest has
(:func:~graflo.architecture.evolution.aspects.join.join): a
SourceRecord or Agent it already declares keeps its own properties
and gains the aspect's missing ones, so manifests that carry the
vocabulary, such as source kits, compose and lift in any order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
manifest
|
GraphManifest
|
the manifest to lift, already |
required |
attach
|
Iterable[str]
|
vertex type names that gain a |
()
|
mentions
|
Iterable[str]
|
vertex type names |
()
|
Raises:
| Type | Description |
|---|---|
LiftError
|
the manifest has no schema, or declares part of the vocabulary in a way that contradicts it. |
Source code in graflo/architecture/evolution/aspects/provenance/plan.py
plan_relift(recipe, manifest, *, authored=None)
¶
The ops that lift manifest again as recipe records.
manifest need not be the one the recipe was planned on: a re-lift is
for a manifest that has moved on, or for an aspect whose version has
(see :func:recipe_drift). A manifest that already carries everything
plans no ops.
Raises:
| Type | Description |
|---|---|
LiftError
|
as :func: |
Source code in graflo/architecture/evolution/aspects/recipe.py
plan_state(manifest, spec, *, authored=None)
¶
The ops that lift manifest into one that tracks state and measurements.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
manifest
|
GraphManifest
|
the manifest to lift, already |
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 |
|---|---|
StatePlan
|
The additive ops, the scaffolded type names, and the removals kept |
StatePlan
|
apart so they can run last. Ordering matters -- a property is moved |
StatePlan
|
onto its |
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/aspects/state/plan.py
plan_sync(manifest)
¶
The ops that give every type a resource owns a sync_hash.
Raises:
| Type | Description |
|---|---|
LiftError
|
no schema, or no resource declares a |
Source code in graflo/architecture/evolution/aspects/sync/plan.py
recipe_drift(recipe)
¶
The aspects whose version moved since recipe: {name: (recorded, now)}.
Raises:
| Type | Description |
|---|---|
LiftError
|
an aspect the recipe names that no longer exists. |
Source code in graflo/architecture/evolution/aspects/recipe.py
recorded_spec(spec)
¶
spec as a recipe records it: no defaults, set-like lists sorted.
Without defaults, a field added to :class:LiftSpec later does not move
the hash of a recipe recorded before it. The lists the planners read as
sets are sorted, so two declarations that plan the same lift are the same
recipe.
Source code in graflo/architecture/evolution/aspects/recipe.py
resolve_aspects(names)
¶
The aspects called names, in the order given.
Raises:
| Type | Description |
|---|---|
LiftError
|
a name no aspect has. |