mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
A bond lives on the consumer's side of a service relation, so seeing how two roles couple means opening both roles' meta/services.yml and comparing, and retuning one means editing a scalar buried among dozens of unrelated keys. Add `infinito meta roles applications bond` (make bond / i8bond): it resolves every services.<key>.bond to a role-to-role edge through the service registry and serves the result as a matrix, each cell carrying both directions because the two need not agree. Brightness is the bond, black for none and white for 1, so the coupling of the whole repository reads at a glance. Clicking a cell writes the new value back to the declaring role. The write is line surgery rather than a YAML round-trip: these files carry `# nocheck:` directives and hand-tuned ordering that re-dumping would flatten, so only the bond scalar moves and a trailing comment on that line survives. The browser sends a role pair, never a path, and the server resolves the service key from its own edge map, so a pair it does not know is refused before any write. `--out FILE` exports a read-only page and `--format json` the raw edges, for callers that want to post-process rather than edit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
78 lines
2.6 KiB
Python
78 lines
2.6 KiB
Python
"""Read every ``services.<key>.bond`` and resolve it to a role-to-role edge.
|
|
|
|
``bond`` states how tightly a role couples to the service it declares: ``>= 1``
|
|
keeps the partner on the same host, ``< 1`` lets it live on its own. The value
|
|
sits on the consumer's side of the relation, so the pair is directed and the
|
|
two directions of one pair need not agree.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from utils.cache.yaml import load_yaml_any
|
|
from utils.roles.applications.services.registry import (
|
|
build_service_registry_from_roles_dir,
|
|
)
|
|
from utils.roles.mapping import ROLE_FILE_META_SERVICES
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
DEFAULT_BOND = 1.0
|
|
|
|
|
|
def _as_bond(value: Any) -> float | None:
|
|
"""Return the bond as a float, or None when the entry declares none."""
|
|
if isinstance(value, bool) or value is None:
|
|
return None
|
|
try:
|
|
return float(str(value).strip())
|
|
except (TypeError, ValueError):
|
|
return None
|
|
|
|
|
|
def collect_edges(roles_dir: Path) -> dict[tuple[str, str], dict[str, Any]]:
|
|
"""Return ``{(consumer, provider): {bond, service_key, enabled}}``.
|
|
|
|
A role's entry for its own entity is skipped: a role does not bond to
|
|
itself, and those entries carry unrelated topics such as users or domains.
|
|
"""
|
|
registry = build_service_registry_from_roles_dir(roles_dir)
|
|
provider_of = {
|
|
key: str((entry or {}).get("role") or "")
|
|
for key, entry in registry.items()
|
|
if isinstance(entry, dict)
|
|
}
|
|
|
|
edges: dict[tuple[str, str], dict[str, Any]] = {}
|
|
for path in sorted(roles_dir.glob(f"*/{ROLE_FILE_META_SERVICES}")):
|
|
consumer = path.parent.parent.name
|
|
services = load_yaml_any(str(path), default_if_missing={}) or {}
|
|
if not isinstance(services, dict):
|
|
continue
|
|
for key, entry in services.items():
|
|
if not isinstance(entry, dict):
|
|
continue
|
|
bond = _as_bond(entry.get("bond"))
|
|
if bond is None:
|
|
continue
|
|
provider = provider_of.get(str(key), "")
|
|
if not provider or provider == consumer:
|
|
continue
|
|
edges[(consumer, provider)] = {
|
|
"bond": bond,
|
|
"service_key": str(key),
|
|
"enabled": entry.get("enabled"),
|
|
}
|
|
return edges
|
|
|
|
|
|
def participants(edges: dict[tuple[str, str], dict[str, Any]]) -> list[str]:
|
|
"""Return every role on either end of a bond, in stable order."""
|
|
seen: set[str] = set()
|
|
for consumer, provider in edges:
|
|
seen.add(consumer)
|
|
seen.add(provider)
|
|
return sorted(seen)
|