[docs]classRole(HasNarratorKnowledge,Dependency[Actor]):"""Role() Story-specific dependency edge that binds an actor provider into gathered scope. Why ---- ``Role`` turns generic dependency resolution into a narrative concept with a stable namespace contract, making resolved actors available under both the role label and derived metadata keys. Key Features ------------ * Extends :class:`~tangl.vm.provision.requirement.Dependency` so role edges participate in standard provisioning and frontier resolution. * Publishes the resolved actor under the role label plus derived metadata keys such as ``guide_name``. * Publishes additive aliases such as ``guide_role`` and ``role_edges`` so templates and filters can address role-level epistemic state separately from provider-level knowledge. * Contributes a merged ``roles`` mapping during namespace gathering. API --- - :meth:`provide_role_symbols` returns the local symbol payload reused by gather-time assembly. See also -------- :class:`Actor` Default provider type bound by role dependencies. :class:`~tangl.vm.provision.requirement.Dependency` Base provisioning edge contract used by story roles. """@staticmethoddef_invoke_provider_ns(provider:Any)->dict[str,Any]:get_ns=getattr(provider,"get_ns",None)ifnotcallable(get_ns):return{}value=get_ns()ifvalueisNone:return{}ifnotisinstance(value,Mapping):raiseTypeError(f"{type(provider).__name__}.get_ns must return Mapping | None",)payload=dict(value)return{key:itemforkey,iteminpayload.items()ifitemisnotprovider}defprovide_role_symbols(self)->dict[str,Any]:"""Publish role/provider symbols for gather-time namespace assembly."""provider=self.providerlabel=self.get_label()ifproviderisNoneornotlabel:return{}payload:dict[str,Any]={label:provider}provider_ns=self._invoke_provider_ns(provider)forkey,valueinprovider_ns.items():payload[f"{label}_{key}"]=valuereturnpayload
def_role_sort_key(role:Role)->tuple[str,str]:returnrole.get_label()or"",str(role.uid)@on_gather_nsdefcontribute_roles(*,caller,ctx,**_kw):"""Inject role providers and role metadata into assembled scoped namespaces."""ifnothasattr(caller,"edges_out"):returnNonescope_nodes=list(caller.ancestors)ifhasattr(caller,"ancestors")else[caller]contributions:dict[str,Any]={}roles:dict[str,Any]={}role_edges:dict[str,Role]={}forscopeinreversed(scope_nodes):scope_roles=sorted(scope.edges_out(Selector(has_kind=Role)),key=_role_sort_key)forroleinscope_roles:role_payload=role.provide_role_symbols()ifrole_payload:contributions.update(role_payload)provider=role.providerlabel=role.get_label()ifproviderisnotNoneandlabel:roles[label]=provideriflabel:contributions[f"{label}_role"]=rolerole_edges[label]=roleifroles:contributions["roles"]=rolesifrole_edges:contributions["role_edges"]=role_edgesreturncontributionsorNone