tangl.vm.provision

Constraint, offer, and resolver mechanisms for satisfying frontier dependencies.

Related design docs

Related notes

Constraint types

class Requirement(has_kind: type | None, has_identifier: str | None)[source]

Unsatisfied resource contract carried by a frontier edge.

Why

Requirements separate the shape of what is needed from the graph structure that carries that need. This lets the resolver reason about candidates, policy, and diagnostics before mutating runtime topology.

Key Features

  • Extends Selector so requirement matching reuses the engine’s normal selection semantics.

  • Tracks satisfaction, selected policy, and resolution metadata for replay and diagnostics.

  • Supports soft requirements and deferred UPDATE or CLONE formulas.

API

  • satisfied reports whether the requirement is currently fulfilled.

  • satisfied_by() evaluates a candidate provider.

  • from_identifier() builds a simple identifier-driven requirement.

Example

>>> e = Entity(label='foo')
>>> req = Requirement.from_identifier('foo')
>>> req.satisfied
False
>>> req.satisfied_by(e)
True
>>> req.provider_id = e.uid
>>> req.satisfied
True
>>> req = Requirement(hard_requirement=False)
>>> req.satisfied
True
class HasRequirement(requirement: Requirement[PT])[source]

Mixin that makes a registry-aware carrier expose one embedded requirement.

Why

Resolver logic needs a uniform way to read satisfaction state, retrieve the resolved provider, and write resolution metadata back to the carrier edge.

Key Features

  • Centralizes provider bookkeeping on the embedded Requirement.

  • Mirrors satisfaction and resolution metadata through simple properties.

  • Validates provider compatibility before storing the resolved provider id.

API

  • provider and set_provider() synchronize the embedded requirement with registry linkage.

  • satisfied and satisfied_by() delegate to the embedded requirement.

Example

>>> reg = Registry()
>>> r = HasRequirement(requirement={'has_identifier': 'foo'}); reg.add(r)
>>> r.satisfied
False
>>> e = RegistryAware(label='foo'); reg.add(e)
>>> r.satisfied_by(e)
True
>>> r.provider = e
>>> r.satisfied
True
>>> r.provider
<RegistryAware:foo>
>>> f = RegistryAware(label='bar'); reg.add(f)
>>> r.satisfied_by(f)
False
>>> try:
...     r.provider = f
... except ValueError as e:
...     print(e)
Requirement ... not satisfied by <RegistryAware:bar>
class Dependency(predecessor_id: UUID | None = None, requirement: Requirement[PT])[source]

Pull-resource edge whose predecessor declares a needed provider.

Why

Dependencies make missing topology explicit. A frontier node can advertise what it needs before any provider exists, then let the resolver bind a concrete successor later during planning.

Key Features

  • Couples one embedded Requirement to graph topology.

  • Keeps provider and successor synchronized so resolution state is visible through normal edge traversal.

API

  • set_provider() validates and stores the resolved provider while synchronizing successor.

  • set_successor() provides a topology-first alias that also updates requirement state.

Example

>>> reg = Registry()
>>> r = Dependency(requirement={'has_identifier': 'foo'}); reg.add(r)
>>> r.satisfied
False
>>> e = RegistryAware(label='foo'); reg.add(e)
>>> r.successor = e
>>> r.satisfied
True
>>> r.provider
<RegistryAware:foo>
>>> r.successor
<RegistryAware:foo>
class Affordance(predecessor_id: UUID | None = None, requirement: Requirement[PT])[source]

Push-resource edge whose predecessor offers a provider to nearby consumers.

Why

Affordances model already-available local providers that should be treated as preferred EXISTING offers before wider search proceeds.

Key Features

  • Couples one embedded Requirement to a provider edge that pushes availability outward from its predecessor.

  • Keeps provider and successor synchronized so local offer graphs stay consistent.

API

  • set_provider() validates and stores the resolved provider while synchronizing successor.

  • set_successor() provides a topology-first alias that also updates requirement state.

Example

>>> reg = Registry()
>>> r = Affordance(requirement={'has_identifier': 'foo'}); reg.add(r)
>>> r.satisfied
False
>>> e = RegistryAware(label='foo'); reg.add(e)
>>> r.successor = e
>>> r.satisfied
True
>>> r.provider
<RegistryAware:foo>
>>> r.successor
<RegistryAware:foo>

Offers and provisioners

class ProvisionPolicy(*values)[source]

Bitflag describing allowed or selected provisioning offer kinds.

Requirement instances use these flags to restrict acceptable offer types, while ProvisionOffer instances use them to declare what kind of action they represent.

class ProvisionOffer(_ctx=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None, seq=None, origin_id=None, policy, callback, priority=Priority.NORMAL, distance_from_caller=999, specificity=0, exact_kind_match=False, scope_distance=0, build_plan=None, target_ctx=None, candidate=None, **kwargs)[source]

Deferred, ranked candidate for satisfying one requirement.

Why

Resolution gathers many possible candidates before committing to any single one. ProvisionOffer keeps the selection process pure by storing a lazy callback instead of materializing providers eagerly.

class Provisioner(*args, **kwargs)[source]

Structural protocol implemented by offer-producing provisioning helpers.

class FindProvisioner(values, distance=0)[source]

Offer EXISTING providers already present in local entity groups.

class TemplateProvisioner(registries=(), request_ctx='', graph=None, story_materialize=None, materialize_node=None)[source]

Offer CREATE candidates from template registries using scope matching.

class TokenProvisioner(catalogs=())[source]

Offer CREATE token providers from singleton token catalogs.

class InlineTemplateProvisioner(materialize_node=None, story_materialize=None)[source]

Offer inline requirement templates as normal CREATE candidates.

class StubProvisioner[source]

Preview-oriented provisioner that synthesizes minimal matching entities.

class UpdateCloneProvisioner[source]

Synthesizes deferred UPDATE/CLONE offers from selected FIND/CREATE offers.

This provisioner never executes upstream callbacks while constructing offers. It selects the best FIND and CREATE candidates by sort key and emits deferred composite callbacks that sub-accept only when the composite offer itself is accepted by the resolver.

CloneProvisioner

alias of UpdateCloneProvisioner

Resolution

class Resolver(location_entity_groups=(), template_scope_groups=())[source]

Gather, rank, preview, and bind provisioning offers for frontier requirements.

Why

Provisioning is where runtime graph gaps become concrete providers. The resolver centralizes offer discovery and binding so story-layer code can define policy without reimplementing the selection mechanics.

Key Features

  • Reads candidate providers from ordered entity groups and template scopes.

  • Merges FIND, CREATE, TOKEN, inline-template, and synthesized UPDATE/CLONE offers into one ranked stream.

  • Supports preview mode through blocker diagnostics and optional stub linkage.

  • Writes selected providers and decision metadata back onto dependency requirements.

API

Selected methods

classmethod Resolver.from_ctx(ctx)[source]

Build a resolver from the entity and template groups exposed by ctx.

Resolver.gather_offers(requirement, *, allow_stubs=False, preferred_offers=(), _ctx=None)[source]

Return ranked offers that are currently admissible for requirement.

Existing providers, template-driven candidates, token catalogs, inline templates, synthesized UPDATE or CLONE offers, and optional dispatch overrides all participate in this one ordered result set.

Resolver.resolve_requirement(requirement, *, allow_stubs=False, preferred_offers=(), _ctx=None)[source]

Resolve and materialize one provider for requirement if possible.

Resolver.resolve_dependency(dependency, *, allow_stubs=False, _ctx=None)[source]

Resolve one dependency edge and bind its provider into the graph.

Resolver.resolve_frontier_node(node, *, allow_stubs=False, _ctx=None)[source]

Resolve all open dependencies on node and verify traversal viability.

Resolver.preview_requirement(requirement, *, allow_stubs=False, preferred_offers=(), max_depth=8, _ctx=None)[source]

Return a non-mutating viability preview for one requirement.