Provisioning Pipeline

Overview

Provisioning is the process the VM uses to satisfy :class:~tangl.core.requirement.Requirement objects before the story advances. Each frontier node exposes requirements for actors, locations, or resources. Provisioners compete to satisfy those requirements by producing :class:~tangl.vm.provision.offer.Offer objects. The planner selects the best offer for each requirement, applies it, and records a :class:~tangl.vm.provision.receipt.BuildReceipt for auditing.

Provisioner Sequence

  1. GraphProvisioner – Finds already-instantiated nodes that satisfy the requirement.

  2. TemplateProvisioner – Instantiates templates (see below) if the requirement references one.

  3. Updating / Cloning Provisioners – Modify or duplicate existing entities when requested.

Provisioners may return zero or more offers. The planner picks the lowest-cost viable offer, so custom provisioners should surface clear cost semantics.

TemplateProvisioner Flow

Template provisioning is the most common path for casting roles and populating settings:

  1. Requirement intake: Requirements coming from story scripts now include template_ref when a RoleScript or SettingScript references a template label. Inline templates are still supported via the legacy template dict.

  2. Template lookup: The provisioner pulls templates from world.template_registry. Templates are :class:~tangl.ir.core_ir.base_script_model.BaseScriptItem instances and already include a content-addressed hash (content_hash).

  3. Scope filtering: Before an offer is created, _is_in_scope validates that the template’s scope allows it to be used for the current source node. See :doc:../authoring/TEMPLATE_SCOPE for examples of block, scene, and ancestor filtering.

  4. Offer creation: Offers embed the template, its registry label, and a short content identifier (template.get_content_identifier()). No additional hashing is necessary because :class:~tangl.core.content_addressable.ContentAddressable handles it.

  5. Build step: When selected, the offer is converted into a concrete entity. The provisioner resolves kind, hydrates the payload from the template, applies requirement overrides, and writes a build receipt including the template reference and hash for provenance.

If any step fails—missing template, scope rejection, or unresolved class—the provisioner simply returns no offers and logs a debug/warning message, allowing other provisioners or policies to handle the requirement.

Scope Semantics

Template scope determines where a template can be used:

  • Global templates (scope=None) can satisfy any role/setting.

  • Scene templates (inferred parent_label) are limited to blocks within that scene.

  • Block templates (inferred source_label) can only fill roles/settings in the block where the template was declared.

  • Advanced selectors like ancestor_labels and ancestor_tags enforce ancestry constraints.

Because scope enforcement runs inside the TemplateProvisioner, authors get immediate feedback in planning logs when a template reference is out of scope. Refer to :doc:../authoring/TEMPLATE_SCOPE for author-facing guidance and YAML examples.

Debugging Provisioning

  • Enable debug logging for tangl.vm.provisioners.template_provisioner to see why templates are rejected (missing source, parent mismatch, missing tags, etc.).

  • Inspect build receipts in the ledger; each contains template_ref and template_hash so you can trace exactly which template was instantiated.

  • Use world.template_registry.find_all(content_hash=...) to locate duplicate templates if you suspect multiple declarations share the same content.

Cost Model & Auditing

Offer selection is now deterministic and proximity-aware:

  • Base costs come from :class:~tangl.vm.provision.offer.ProvisionCost (e.g., DIRECT=10, CREATE=200).

  • Graph proximity adds a modifier before the planner compares offers:

    Scenario

    Modifier

    Same block

    +0

    Same scene

    +5

    Same episode

    +10

    Elsewhere

    +20

  • Template offers use the fixed create cost (200) so nearby existing providers almost always win unless the requirement policy is CREATE.

Every call to :func:~tangl.vm.provision.resolver._select_best_offer records audit metadata. The final :class:~tangl.vm.provision.offer.PlanningReceipt includes selection_audit, a list of the offers considered for each requirement plus the reason the winner was chosen. Developers can print these decisions with :class:tangl.vm.debug.PlanningDebugger.

See :doc:COST_MODEL for an extended breakdown of the calculations and troubleshooting tips.