mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-10 04:36:40 +00:00
feat(cli): edit role bonds in a matrix instead of hunting through files
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>
This commit is contained in:
1 parent
8cc9c86f8a
commit
f320d64836
11 files changed
+696
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,76 @@
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
from cli.meta.roles.applications.bond.edit import EditError, parse_bond, set_bond
|
||||
from utils.roles.mapping import ROLE_FILE_META_SERVICES
|
||||
|
||||
SOURCE = """---
|
||||
litellm:
|
||||
bond: 0.5
|
||||
enabled: "{{ 'svc-ai-litellm' in group_names }}"
|
||||
# nocheck: dockerfile-custom
|
||||
sso:
|
||||
bond: 1 # nocheck: dynamic-flag
|
||||
flavor: oidc
|
||||
ldap:
|
||||
enabled: true
|
||||
"""
|
||||
|
||||
|
||||
class TestSetBond(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self._tmp = TemporaryDirectory()
|
||||
self.roles = Path(self._tmp.name)
|
||||
self.path = self.roles / "web-app-demo" / ROLE_FILE_META_SERVICES
|
||||
self.path.parent.mkdir(parents=True)
|
||||
self.path.write_text(SOURCE, encoding="utf-8")
|
||||
self.addCleanup(self._tmp.cleanup)
|
||||
|
||||
def read(self) -> str:
|
||||
return self.path.read_text(encoding="utf-8") # nocheck: cache-read — re-reads the file set_bond just rewrote
|
||||
|
||||
def test_rewrites_the_value_in_place(self) -> None:
|
||||
self.assertEqual(set_bond(self.roles, "web-app-demo", "litellm", 0.25), "0.25")
|
||||
self.assertIn(" bond: 0.25\n", self.read())
|
||||
self.assertIn("# nocheck: dockerfile-custom\n", self.read())
|
||||
|
||||
def test_keeps_a_trailing_comment(self) -> None:
|
||||
set_bond(self.roles, "web-app-demo", "sso", 0.5)
|
||||
self.assertIn(" bond: 0.5 # nocheck: dynamic-flag\n", self.read())
|
||||
|
||||
def test_inserts_a_missing_bond(self) -> None:
|
||||
set_bond(self.roles, "web-app-demo", "ldap", 1)
|
||||
self.assertIn("ldap:\n bond: 1\n enabled: true\n", self.read())
|
||||
|
||||
def test_removes_on_none(self) -> None:
|
||||
self.assertEqual(set_bond(self.roles, "web-app-demo", "litellm", None), "")
|
||||
self.assertNotIn("bond: 0.5", self.read())
|
||||
self.assertIn("litellm:\n enabled:", self.read())
|
||||
|
||||
def test_rejects_an_unknown_key(self) -> None:
|
||||
with self.assertRaises(EditError):
|
||||
set_bond(self.roles, "web-app-demo", "nope", 1)
|
||||
|
||||
def test_does_not_match_a_nested_key(self) -> None:
|
||||
with self.assertRaises(EditError):
|
||||
set_bond(self.roles, "web-app-demo", "flavor", 1)
|
||||
|
||||
|
||||
class TestParseBond(unittest.TestCase):
|
||||
def test_blank_clears(self) -> None:
|
||||
self.assertIsNone(parse_bond(""))
|
||||
self.assertIsNone(parse_bond(" - "))
|
||||
|
||||
def test_rejects_out_of_range(self) -> None:
|
||||
for raw in ("2", "-0.1", "abc"):
|
||||
with self.assertRaises(EditError):
|
||||
parse_bond(raw)
|
||||
|
||||
def test_accepts_the_edges(self) -> None:
|
||||
self.assertEqual(parse_bond("0"), 0.0)
|
||||
self.assertEqual(parse_bond("1"), 1.0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user