utils.roles.applications package¶
Subpackages¶
- utils.roles.applications.services package
- Submodules
- utils.roles.applications.services.config module
- utils.roles.applications.services.database module
- utils.roles.applications.services.engines module
- utils.roles.applications.services.errors module
- utils.roles.applications.services.registry module
GROUP_GATED_CONSTANTSServiceRegistryErrorbucket_order_key()build_covered_key_to_role()build_role_to_covered_keys()build_role_to_primary_service_key()build_service_registry_from_applications()build_service_registry_from_roles_dir()canonical_service_key()detect_deploy_type()detect_service_bucket()detect_service_channel()discover_role_services()equivalent_service_keys()expand_service_tokens()is_explicit_truth()load_applications_from_roles_dir()load_run_after_from_roles_dir()ordered_primary_service_entries()read_yaml_file()resolve_service_dependency_roles_from_config()run_after_topological_order()
- utils.roles.applications.services.resolver module
- utils.roles.applications.services.resources module
- utils.roles.applications.services.sso module
- Module contents
Submodules¶
utils.roles.applications.config module¶
- exception utils.roles.applications.config.AppConfigKeyError(message: str = '', obj: Any = None, show_content: bool = True, suppress_extended_error: bool | EllipsisType = Ellipsis, orig_exc: BaseException | None = None, help_text: str | None = None)¶
Bases:
AnsibleTemplatePluginError,ValueErrorRaised when a required application config key is missing (strict mode). Compatible with Ansible error handling and Python ValueError.
- exception utils.roles.applications.config.ConfigEntryNotSetError(message: str = '', obj: Any = None, show_content: bool = True, suppress_extended_error: bool | EllipsisType = Ellipsis, orig_exc: BaseException | None = None, help_text: str | None = None)¶
Bases:
AppConfigKeyErrorRaised when a config entry is defined in schema but not set in application.
- utils.roles.applications.config.get(applications, application_id, config_path, strict=True, default=None, skip_missing_app=False)¶
utils.roles.applications.in_group_deps module¶
- utils.roles.applications.in_group_deps.applications_if_group_and_all_deps(applications: dict[str, Any], group_names: list[str], *, project_root: str | None = None, roles_dir: str | None = None, service_registry: dict[str, Any] | None = None, meta_deps_resolver: Callable[[str, str], list[str]] | None = None, blocked: frozenset[str] | set[str] | None = None) dict[str, Any]¶
- utils.roles.applications.in_group_deps.meta_deps_from_disk(role: str, roles_dir: str) list[str]¶
- utils.roles.applications.in_group_deps.reachable_roles(applications: dict[str, Any], seeds: list[str], *, project_root: str | None = None, roles_dir: str | None = None, service_registry: dict[str, Any] | None = None, meta_deps_resolver: Callable[[str, str], list[str]] | None = None, blocked: frozenset[str] | set[str] | None = None) set[str]¶
Return every role reachable from
seedsthrough meta and service edges.- Parameters:
applications – the merged applications map.
seeds – role ids to start the traversal from.
project_root – repository root, used to derive
roles_dirand registry.roles_dir – roles directory, when it is not derived from
project_root.service_registry – prebuilt registry, otherwise built from the inputs.
meta_deps_resolver – reads a role’s
meta/main.ymldependencies.blocked – roles the traversal must not enter.
utils.roles.applications.mcp module¶
Vocabulary of the mcp block in meta/services.yml.
Every role with an application_id declares exactly one mcp: entry.
The block is an audit disposition first and a deployable surface second:
classification states what the role is, and only a deployable
classification may also carry enabled/shared and a served contract.
Field vocabulary (see docs/contributing/design/role/services/mcp.md):
classification: what this role is in the MCP audit. Required.reason: why a non-deployable classification cannot serve.direction: whether the role exposes MCP, consumes it, or both.transport: wire protocol of the endpoint.exposure: who may reach the endpoint.auth: authentication scheme guarding the endpoint.auth_subject: identity MCP calls execute as.userandoidcadditionally require adelegationblock, because a rendered deployment bearer is a service account no matter what the metadata claims.delegation: recorded proof, at one exact upstream version, that per-user token acquisition, refresh, audience binding and revocation exist.implementation: how the surface is provided, ordered by precedence.credential: which principal the provider authenticates as, and where its secret is read from. Replaces the administrator token path.
Admission is not declared here. A client marks itself once, in its own
meta/services.yml self-entry, as mcp_consumer: true; a provider
deviates by carrying mcp_consumer: false on that client’s entry in its
own meta/services.yml. derive_allowed_consumers resolves the pair.
supported_transports/supported_auths: what a client can present.endpoint: connection metadata for clients (server roles only).adapter: immutable source contract of a repository-owned adapter.category_allowlistsuits an upstream that switches whole categories and offers no per-tool filter, where a tool allowlist would be unenforceable;mcp_passthroughfronts an upstream that already speaks MCP instead of translating REST.upstream_pathnames that upstream where it lives on the application’s own vhost, so the proxy can hide it: reaching it directly walks past the allowlist the adapter exists to enforce. A provider whose upstream is a separate sidecar declaresupstream_networkinstead and has nothing to hide there.limits: the request, response, timeout, concurrency, pagination and stream ceilings the surface enforces.tools:allowlistis what a reader may reach andwriter_allowlistwhat a writer may reach; the deployment renders one contract per role, each with its own bearer, because the gateway tells callers apart by credential and by nothing else.allowlistis policy, what the platform lets a consumer reach, and is only a constraint where a gateway enforces it.upstream_servesis the observation: every tool the upstream offers at the deployed version. State it only where it DIFFERS from the allowlist; absence means the two are identical, which is the normal case for a provider that serves exactly what is allowed. The difference between them measures unenforced exposure, and a provider whose difference is non-empty needs a gateway in front. Plus schema hash and mutation policy.mutating_proofs: required oncetools.mutating_tools_enabledis true. Names the artifact proving each of confirmation, authorization, idempotency, audit and reversal for the mutating variant, separately from the read-only one.source_url: upstream documentation or source of the MCP surface.
- utils.roles.applications.mcp.admits_mcp_consumer(provider_services: object, consumer_id: str) bool¶
Return whether a provider admits one client.
An omitted flag inherits the client’s own declaration; only an explicit
mcp_consumer: falseexcludes, so a deliberate exclusion is distinguishable from a forgotten entry.- Parameters:
provider_services – the provider’s
servicesmapping.consumer_id – the client’s
application_id.
- utils.roles.applications.mcp.declares_delegation(mcp: object) bool¶
Return whether this block claims to act as the requesting end user.
- Parameters:
mcp – the role’s
mcpmapping, or anything else.
- utils.roles.applications.mcp.declares_mcp_consumer(role_id: str, services: object) bool¶
Return whether a role marks itself an MCP client in its own services.
- Parameters:
role_id – the role’s
application_id.services – that role’s
servicesmapping, or anything else.
- utils.roles.applications.mcp.delegation_is_proven(mcp: object) bool¶
Return whether a recorded per-version audit backs the delegation claim.
- Parameters:
mcp – the role’s
mcpmapping, or anything else.
- utils.roles.applications.mcp.derive_allowed_consumers(provider_id: str, services_by_role: dict[str, object]) list[str]¶
Return the client application ids a provider admits, sorted.
- Parameters:
provider_id – the provider’s
application_id.services_by_role – every role’s
servicesmapping, keyed by role id.
- utils.roles.applications.mcp.derive_mcp_presence(applications: dict[str, Any]) dict[str, Any]¶
Materialise every
mcptopic’s enable state from its declared peers.- Parameters:
applications –
{application_id: config}, mutated in place.- Returns:
The same mapping.
A surface is reachable only while a peer that uses it is deployed, so both keys resolve to the
in group_namesform the rest of the tree uses, and toFalsewhen a role has no peer at all. An explicitly configuredenabled/sharedalways wins, so config stays the override channel. Keying on peer PRESENCE rather than on a peer’s own value is required: a re-entrant read under_RENDER_GUARDreturns the unrendered dict.The resolved block replaces the mapping rather than being written into it.
_build_role_base_configparks the YAML cache’s own object undermcp, so an in-place write would inject these keys into every later raw read ofmeta/mcp.yml.
- utils.roles.applications.mcp.is_deployable(mcp: object) bool¶
Return whether this block may carry a served surface and be discovered.
- Parameters:
mcp – the role’s
mcpmapping, or anything else.
- utils.roles.applications.mcp.resolve_credential(server: Mapping[str, Any], users: Mapping[str, Any], role_credentials: Mapping[str, Any]) tuple[str, str]¶
Return the provider’s declared secret and the owner it belongs to.
- Parameters:
server – a discovered
direction=serverentry.users – the merged users mapping, carrying each principal’s tokens.
role_credentials – the provider role’s own
secrets.credentialsmapping, which is where every other consumer addresses them.
Returns an empty token when the declaration is incomplete or the principal holds nothing; the caller turns that into
credential_missing.
- utils.roles.applications.mcp.value_is_templated(value: object) bool¶
utils.roles.applications.mounts module¶
Normalize and validate the role-level mount declaration in
roles/<role>/meta/volumes.yml.
Canonical shape (dict-of-dicts; the YAML key is the semantic short name):
- data: # YAML key = semantic short name
type: volume # bind | volume | config | secret | tmpfs name: matrix_synapse_data # OPTIONAL docker volume name; defaults to the key nfs: true # only meaningful for type: volume mounts:
service: synapse target: /data read_only: false # per-mount override of volume-level when: “{{ FLAG | bool }}”
- config:
type: bind # bind | config | secret may carry source source: “{{ FOO }}” read_only: true mode: “0440” # only for config/secret mounts:
service: synapse target: /etc/synapse/config
- scratch:
type: tmpfs mounts:
service: synapse target: /var/run/synapse size: 64m # per-mount tmpfs size
The legacy list-of-dicts shape is no longer accepted.
- exception utils.roles.applications.mounts.VolumesSchemaError¶
Bases:
ValueErrorRaised when a meta/volumes.yml fails schema validation.
- utils.roles.applications.mounts.content_hash(text: str, length: int = 8) str¶
Stable short hex digest of a string (used for swarm config/secret name rotation).
- utils.roles.applications.mounts.mount_default_read_only(volume: dict[str, Any]) bool¶
Default read-only policy per type.
bind/volume default to rw (False); config/secret are conceptually read-only at the docker primitive level; tmpfs is rw.
- utils.roles.applications.mounts.mount_when_passes(mount: dict[str, Any], render_jinja: Callable[[str], str] | None) bool¶
Evaluate the optional
when:gate of a single mount entry.- Parameters:
mount – one dict from
volume['mounts'].render_jinja – renders a Jinja expression string; None disables rendering.
- Returns:
True when the mount is active.
- utils.roles.applications.mounts.mounts_for_service(volume: dict[str, Any], service: str) Iterable[dict[str, Any]]¶
Yield mount dicts in
volume['mounts']that targetservice.
- utils.roles.applications.mounts.normalize_volumes_meta(meta_volumes: Any) dict[str, dict[str, Any]]¶
Return a dict[semantic_name -> canonical entry].
The YAML key is the semantic short name. Each entry value MAY carry
name(the docker volume name); when absent andtypeisvolume, callers default the docker name to the semantic key.
utils.roles.applications.security_headers module¶
Response security headers the front proxy adds for one application.
WEBSERVER_SECURITY_HEADERS holds the platform values. An application
overrides any of them under security_headers in its meta/server.yml.
- utils.roles.applications.security_headers.security_headers(applications: Mapping[str, Any], application_id: str, platform: Mapping[str, Any]) dict[str, Any]¶
Return the security header values the proxy sends for application_id.
- Parameters:
applications – the applications SPOT.
application_id – the role whose vhost is rendered; a redirect vhost passes its domain, which owns no override.
platform –
WEBSERVER_SECURITY_HEADERS.
- Returns:
nosniffas a bool,referrer_policyandpermissions_policyas header values (empty when not sent) andhstsas the completeStrict-Transport-Securityvalue.
utils.roles.applications.status_codes module¶
HTTP status codes for a deployed domain.
roles/<app>/meta/server.yml declares status_codes keyed like
domains.canonical; it is the single source of truth for every prober.
- utils.roles.applications.status_codes.accepted_status_codes(applications, app_id: str, domain: str) list[int]¶
Return the codes that prove domain is being served at all.
Most declarations are narrower than
DEFAULT_OK, so this gate adds to the default instead of replacing it; narrowing it would escalate vhosts that answer perfectly well. Contract checking is a different question and belongs tosys-ctl-hlth-webserver.- Parameters:
applications – the applications SPOT.
app_id – the role the domain belongs to.
domain – the fully qualified domain being probed.
- Returns:
DEFAULT_OKplus whatever the app declares for this domain.
- utils.roles.applications.status_codes.codes_by_key(raw) dict[str, list[int]]¶
Normalize a raw
server.status_codesdeclaration.- Parameters:
raw – the declaration as read from the app config.
- Returns:
The declared keys mapped to their codes, dropping keys that declare nothing usable.
- utils.roles.applications.status_codes.declared_status_codes(applications, app_id: str, domain: str) list[int]¶
Return what app_id declares for domain, or an empty list.
The vhost is identified by the
domains.canonicalkey that carries the domain, which is the same keyserver.status_codesuses. A domain that is not canonical, or a key that declares nothing, falls back to the declareddefault.- Parameters:
applications – the applications SPOT.
app_id – the role the domain belongs to.
domain – the fully qualified domain being probed.
- Returns:
The declared codes, empty when the app declares none for this domain.
- utils.roles.applications.status_codes.normalize_codes(value) list[int]¶
Return value as a list of plausible HTTP status codes.
- Parameters:
value – a scalar, list or mapping straight out of the declaration.
- Returns:
The codes as de-duplicated integers, dropping anything outside 100-599.
utils.roles.applications.topics module¶
The config topics a variant may dictate for the providers it pulls in.
A round deploys a provider once, so the role that pulls it in is the only place that can say how lean it has to be. A variant entry may therefore carry any application config topic under the service it depends on:
- services:
- openwebui:
enabled: true shared: true services: null # brings nothing along addons:
- whisper:
enabled: false # everything else in addons survives
A mapping deep-merges, so naming one key changes that key and nothing else.
null empties the topic, which is the only way to say “bring nothing”:
merged, an empty mapping is a no-op and would leave exactly the services the
round was trying to shed.
YAML writes null three ways - null, ~, and a key with nothing after
it - so a half-typed line empties a topic rather than being ignored.
- utils.roles.applications.topics.apply_topic(base_value: Any, override_value: Any) Any¶
Return a provider’s topic after one override is applied.
- Parameters:
base_value – what the provider declares for the topic today.
override_value – what the pulling role demands.
Noneempties the topic and is normalised to{}: the closure resolver skips a non-mapping payload, so leaving the bareNonewould silently keep the services it was meant to drop.
- utils.roles.applications.topics.overridden_providers(roles_dir: str | Path) dict[tuple[str, int], set[str]]¶
Return
{(role, variant index): {provider role, ...}}across every variant.- Parameters:
roles_dir – roles directory the variants and service registry are read from.
A variant that dictates a provider’s config does not deploy that provider as the provider declares it, so nothing about that run states what the provider does on its own. Callers that reason about coverage need to know which pairs those are before they credit one role’s deploy to another.
- utils.roles.applications.topics.provider_of(service_key: str, service_registry: Mapping) str¶
Return the role providing
service_key, or “” when unregistered.- Parameters:
service_key – a first-level key of a role’s services map.
service_registry – the project-wide service registry.
utils.roles.applications.variants module¶
What one round’s variants mean for the roles that round pulls in.
A round picks one variant per role, and that choice decides two things no
single role’s meta/services.yml can state on its own: which providers the
role still pulls in, and how lean those providers have to be
(utils.roles.applications.topics). Both are resolved here, so the
matrix planner’s include set, the inventory bake and the CI selection read
one answer instead of three.
The module is deliberately free of deploy-runtime imports: the CI selection resolves its closure on a runner that has no inventory, no container and no INFINITO_SRC_DIR.
- exception utils.roles.applications.variants.NestedOverrideConflictError¶
Bases:
RuntimeErrorTwo roles in one round demand different config for one provider.
- utils.roles.applications.variants.collect_provider_overrides(payloads: Mapping[str, Any], *, roles_dir: str) dict[str, dict[str, Any]]¶
Return
{provider: {topic: value}}demanded by the round’s payloads.- Parameters:
payloads –
{role: variant payload}for one round.roles_dir – roles directory the service registry is built from.
- Raises:
NestedOverrideConflictError – two roles demand different values for the same topic of the same provider.
- utils.roles.applications.variants.services_overrides_for_round(*, roles_dir: str, round_index: int, primary_app_variants: Mapping[str, int]) dict[str, dict]¶
For every role with meta/variants.yml, return the services map that results from merging the round’s variant payload onto the role’s on-disk meta/services.yml.
Apps in primary_app_variants use the supplied (already clamped) index. Other roles with their own variants clamp round_index to their own variant count. Roles without variants are absent from the result so the resolver falls through to its disk-read path.
The merged map is what the inventory ALSO bakes into host_vars, so feeding the same dict into CombinedResolver(services_overrides=…) eliminates the topology-vs-host_vars drift that variant-blind resolution produced.