Repository navigation
refactor(slm): derive the role list's names, units, health and ports from the role manifests (#16025) - #16441
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ SSOT Configuration Compliance: Passing🎉 No new hardcoded values of either class — Known backlog in |
e3bebb2 to
d490c68
Compare
…st changes (#16441) Review (37): four reds, three fixed here. - runner_exit_code_test.py's reachability regression still named add_role_permission_audit_log_timestamps as MIGRATIONS' last entry. #16025 appended widen_systemd_service_to_sequence after it; the test named the wrong migration as the one seed_agents must stay reachable before. - test_role_manifest_shape_16025.py's real-manifest test expected ["autobot-backend", "autobot-celery"] for the "backend" role. Per find_role_manifest's own docstring, direct ownership (role_name == the manifest's own role) wins every systemd service the manifest declares, slm_roles tags or not -- so adding autobot-celery-beat to autobot-backend/manifest.yml means "backend" now owns it too, same as it already owns autobot-celery despite that service's own narrower slm_roles: [celery] tag. Renamed the test off "both" now that there are three. - conftest.py stubs models.database wholesale, so role_registry.Role is a bare MagicMock; role_registry.seed_default_roles's new Role.__table__.columns filter auto-vivified through a MagicMock's own __contains__/__bool__, which never rejects a field. Gave the stub the real Role model's actual column names instead, so the filter is exercised against something that behaves like the real table would. Not touched here: the drift-mirror finding (role_detector_16025_test.py) -- couldn't locate the specific check it refers to despite looking at drift_checker.py's *_test.py exclusion pattern (generic, already covers new test files by suffix) and repo_tests/collection_coverage_test.py (no reference to this file at all). Flagging rather than guessing at a fix for a mechanism I haven't found.
…-set (#16441) Previous commit crashed exactly the tests it was meant to fix: AttributeError: __table__ autobot-slm-backend/conftest.py:177: in <module> sys.modules["models.database"].Role.__table__.columns = frozenset(...) Mock's __getattr__ refuses to auto-vivify a dunder-shaped name it does not implement as a real magic method -- it raises AttributeError rather than fake one, and __table__ matches that shape. Reading Role.__table__ (to then set .columns on whatever came back) never got past that raise. Fixed by assigning __table__ itself, a plain attribute set that does not go through __getattr__ at all, to a types.SimpleNamespace carrying the real columns.
|
37's review addressed, pushed at d490c68 (two rounds — the pre-push hook caught a bug in my own first fix):
|
d490c68 to
d9103dd
Compare
…st changes (#16441) Review (37): four reds, three fixed here. - runner_exit_code_test.py's reachability regression still named add_role_permission_audit_log_timestamps as MIGRATIONS' last entry. #16025 appended widen_systemd_service_to_sequence after it; the test named the wrong migration as the one seed_agents must stay reachable before. - test_role_manifest_shape_16025.py's real-manifest test expected ["autobot-backend", "autobot-celery"] for the "backend" role. Per find_role_manifest's own docstring, direct ownership (role_name == the manifest's own role) wins every systemd service the manifest declares, slm_roles tags or not -- so adding autobot-celery-beat to autobot-backend/manifest.yml means "backend" now owns it too, same as it already owns autobot-celery despite that service's own narrower slm_roles: [celery] tag. Renamed the test off "both" now that there are three. - conftest.py stubs models.database wholesale, so role_registry.Role is a bare MagicMock; role_registry.seed_default_roles's new Role.__table__.columns filter auto-vivified through a MagicMock's own __contains__/__bool__, which never rejects a field. Gave the stub the real Role model's actual column names instead, so the filter is exercised against something that behaves like the real table would. Not touched here: the drift-mirror finding (role_detector_16025_test.py) -- couldn't locate the specific check it refers to despite looking at drift_checker.py's *_test.py exclusion pattern (generic, already covers new test files by suffix) and repo_tests/collection_coverage_test.py (no reference to this file at all). Flagging rather than guessing at a fix for a mechanism I haven't found.
…-set (#16441) Previous commit crashed exactly the tests it was meant to fix: AttributeError: __table__ autobot-slm-backend/conftest.py:177: in <module> sys.modules["models.database"].Role.__table__.columns = frozenset(...) Mock's __getattr__ refuses to auto-vivify a dunder-shaped name it does not implement as a real magic method -- it raises AttributeError rather than fake one, and __table__ matches that shape. Reading Role.__table__ (to then set .columns on whatever came back) never got past that raise. Fixed by assigning __table__ itself, a plain attribute set that does not go through __getattr__ at all, to a types.SimpleNamespace carrying the real columns.
…d copy (#16441) 37's 4th finding: pipeline-scripts/detect-agent-code-drift.sh requires autobot-slm-backend/slm/agent/ (canonical) and autobot-slm-backend/ansible/roles/slm_agent/files/slm/agent/ (mirror) to stay byte-identical, *_test.py files included since #14584 removed the blanket test exclude -- the mirror copies exist so CI can run the test suite against the exact bytes the role ships, not just diff them. role_detector_16025_test.py existed only on the canonical side. Added a byte-identical copy at the mirror path (verified with cmp); role_detector.py itself was already identical on both sides.
|
4th finding fixed and pushed at d9103dd: added a byte-identical copy at |
|
Delta on the four CI reds from the red-PR sweep, at
Scope: this covers these four fixes only; the PR's main content is 37's review. I did not check that the stub's column set matches the real |
|
Verified 37's open point: diffed the stub's 17 column names in |
|
Follow-up on my open point: resolved. I checked it independently at |
…m the role manifests (#16025) Phase 1 of #16025 (a full single-PR fix is not honest -- see the recorded gate decision): role_registry.DEFAULT_ROLES now derives its identifier (canonical_name), systemd units (a sequence, AC4), health check and ports from autobot-infrastructure/*/manifest.yml via the new services/role_manifest_shape.py, instead of hand-declaring them a second time. display_name, sync_type, auto_restart, source_paths, target_path, post_sync_cmd, required, degraded_without and ansible_playbook stay registry-only, pinned in REGISTRY_ONLY_FIELDS (a shrink-only ratchet) -- none has a manifest field today, and two roles' manifests were found to disagree with a registry value role_registry.py's own comments prove correct, so deriving them blind would have shipped a regression under the banner of removing one. Resolved two divergences from on-base SSOT, not by picking a side blind: - browser-service health 3000/status -> 9001/health (ansible/roles/browser's own defaults declare 9001, list 3000 as legacy, #4662) - chromadb health 8000/v1 -> 8100/v2 (autobot_shared's ssot_config, #3094, and the ai-stack ansible role's own defaults) systemd_service becomes a sequence end to end: models/database.py's Role.systemd_service column (JSON, migrations/widen_systemd_service_to_sequence.py converts an existing deployed column), services/role_units.py, services/sync_orchestrator.py, api/roles.py, api/nodes.py, and slm/agent/role_detector.py (+ its ansible-deployed mirror copy) all updated to restart/detect every unit a role owns, not just one. Also fixed: autobot-ai-stack/manifest.yml's deploy.source pointed at a placeholder README dir (#14275) -- corrected to match what role_registry.py and the ansible role already deploy from. Not done here, and reported rather than guessed: the autobot-slm-agent manifest vs. registry source/destination divergence (tracked in the phase 2 issue), manifests for tts-worker/vnc/docker (#16436), and the SLM frontend's RoleResponse.systemd_service type (str -> list[str], noted in #16435). Refs #16025. Phase 2 tracked as #16435 and #16436, filed as its sub-issues.
api/roles.py's RoleResponse/RoleCreate/RoleUpdate.systemd_service became list[str] | None in the #16025 phase-1 commit; the SLM frontend still read it as a string. Every consumer under autobot-slm-frontend/src now handles the list: - useRoles.ts: Role.systemd_service -> string[] | null, plus shared joinSystemdUnits/parseSystemdUnits helpers so RolesView.vue and OrchestrationView.vue's comma-separated-text-field <-> array conversion (same pattern already used for source_paths/degraded_without) is not reimplemented, and diverging, in each view. - RolesView.vue, OrchestrationView.vue: local RoleDefinition/Role field type, edit-form population, submit payload, and every display site (table cells, role cards, migration summary) now join the units instead of interpolating an array. useOrchestration.ts's ServiceDefinition.systemd_service and the generated api.ts are untouched -- they back api/orchestration.py's unrelated service_orchestrator.py catalog (always one service per definition), not role_registry. api.ts is bot-regenerated on the PR and checked by verify-generated-types, so it is not hand-edited here. Added: joinSystemdUnits/parseSystemdUnits unit tests in useRoles.test.ts (join, single-unit parity, null/empty, split with ragged whitespace, blank-input null, round-trip) -- fixtures built at run time, no snapshot. Updates #16435 to drop the frontend item (now fixed here) from its list of what phase 2 still owes. Refs #16025.
Auto-regenerated (py3.14) to match the backend and/or SLM backend schema so the required verify-generated-types gate(s) pass. Triggered by auto-fix-generated-types.yml.
…st changes (#16441) Review (37): four reds, three fixed here. - runner_exit_code_test.py's reachability regression still named add_role_permission_audit_log_timestamps as MIGRATIONS' last entry. #16025 appended widen_systemd_service_to_sequence after it; the test named the wrong migration as the one seed_agents must stay reachable before. - test_role_manifest_shape_16025.py's real-manifest test expected ["autobot-backend", "autobot-celery"] for the "backend" role. Per find_role_manifest's own docstring, direct ownership (role_name == the manifest's own role) wins every systemd service the manifest declares, slm_roles tags or not -- so adding autobot-celery-beat to autobot-backend/manifest.yml means "backend" now owns it too, same as it already owns autobot-celery despite that service's own narrower slm_roles: [celery] tag. Renamed the test off "both" now that there are three. - conftest.py stubs models.database wholesale, so role_registry.Role is a bare MagicMock; role_registry.seed_default_roles's new Role.__table__.columns filter auto-vivified through a MagicMock's own __contains__/__bool__, which never rejects a field. Gave the stub the real Role model's actual column names instead, so the filter is exercised against something that behaves like the real table would. Not touched here: the drift-mirror finding (role_detector_16025_test.py) -- couldn't locate the specific check it refers to despite looking at drift_checker.py's *_test.py exclusion pattern (generic, already covers new test files by suffix) and repo_tests/collection_coverage_test.py (no reference to this file at all). Flagging rather than guessing at a fix for a mechanism I haven't found.
…-set (#16441) Previous commit crashed exactly the tests it was meant to fix: AttributeError: __table__ autobot-slm-backend/conftest.py:177: in <module> sys.modules["models.database"].Role.__table__.columns = frozenset(...) Mock's __getattr__ refuses to auto-vivify a dunder-shaped name it does not implement as a real magic method -- it raises AttributeError rather than fake one, and __table__ matches that shape. Reading Role.__table__ (to then set .columns on whatever came back) never got past that raise. Fixed by assigning __table__ itself, a plain attribute set that does not go through __getattr__ at all, to a types.SimpleNamespace carrying the real columns.
…d copy (#16441) 37's 4th finding: pipeline-scripts/detect-agent-code-drift.sh requires autobot-slm-backend/slm/agent/ (canonical) and autobot-slm-backend/ansible/roles/slm_agent/files/slm/agent/ (mirror) to stay byte-identical, *_test.py files included since #14584 removed the blanket test exclude -- the mirror copies exist so CI can run the test suite against the exact bytes the role ships, not just diff them. role_detector_16025_test.py existed only on the canonical side. Added a byte-identical copy at the mirror path (verified with cmp); role_detector.py itself was already identical on both sides.
…#16441) This file removes the conftest's models.database stub and builds a fresh one before importing the real services.role_registry (see its own module docstring), so the earlier conftest.py fix for Role.__table__ never reaches seed_default_roles() here -- it reads this file's own separately-constructed Role mock instead. Second pass on this finding: give this file's own stub the same __table__ columns directly (not read-then-set, since Mock's __getattr__ rejects dunder-shaped names). Bumps both file-size ratchet baselines by the 28 net new lines (846 -> 874).
d9103dd to
7772bd5
Compare
|
Delta Verified:
CI had 26 checks pending when I posted. |
|
For reference on "CI will not catch this": the gap is tracked in #16393 (open), "nothing rejects a raised ratchet pin". I added this PR's 846→874 there as a concrete case. #14498's two-copy lock rejects a raise in one file alone, but a coordinated raise of both copies to the file's new size satisfies every current check. Until #16393 lands, keeping ceilings from rising is a review rule, which is why it blocks here. |
…#16441) 37's review on 7772bd5: raising test_role_registry.py's file-size ceiling 846->874 to fit a second copy of the Role column set violates the ratchet's own rule (no ceiling may ever rise) and defeats the two-copy-baseline design that exists to catch exactly that. Root fix: conftest.py now registers the column set once, under sys.modules["_role_cols"], instead of only setting it as a local attribute. test_role_registry.py's own separately-built Role stub reads that same value back (via a MagicMock __table__ constructor kwarg, which -- like a direct attribute assignment -- bypasses Mock.__getattr__'s dunder-name rejection) instead of carrying its own literal copy. One source of truth, and the file lands back at exactly 846 lines -- the ceiling is restored in both baseline copies rather than raised.
|
Delta
The branch is 0 behind main. CI had 24 checks pending when I posted. |
Refs #16025
Single-issue rationale: this is phase 1 of #16025, delivering the owner's decision that the role manifests are the system of record. Phase 2 (#16435) and the missing manifests (#16436) are filed as sub-issues. #16025 stays open until they land.
Thinking Path
The SLM described role shape twice:
role_registry.DEFAULT_ROLES, read by the deploy and inventory path, andautobot-infrastructure/<role>/manifest.yml, read by the reconcile and health path. The owner decided on #16025 that the manifests are canonical andDEFAULT_ROLESa derived projection.Mapping each consumer's fields to a manifest source showed that nine registry fields have no manifest equivalent yet:
display_name,sync_type,auto_restart,source_paths,target_path,post_sync_cmd,required,degraded_withoutandansible_playbook. Deriving those would mean inventing values. So this phase derives the four fields the manifests do carry, and pins the rest in an allowlist that can only shrink.What Changed
services/role_manifest_shape.py:default_roles_forderives each role's canonical name, its systemd units (as a sequence), its health endpoint and its ports from the loaded manifests.REGISTRY_ONLY_FIELDSnames the nine fields still declared by hand.Role.systemd_servicebecomes a JSON list, with migrationmigrations/widen_systemd_service_to_sequence.py.services/role_units.py,services/sync_orchestrator.py(restarts all units),api/roles.py,api/nodes.py, andslm/agent/role_detector.pyplus its Ansible mirror (a role is running only when all its units are active).autobot-backendandautobot-celery.autobot-backend, exposed ascanonical_name. The existingnamestays, for now: renaming it touches NodeRole rows, the Ansible group keys and the frontend, which is phase 2.slm_rolestags so one manifest can narrow to or fan out across registry roles; the ai-stack deploy source fixed.playwright_port, bug(playwright): Chromium binary missing — browser automation broken after Ansible deploy #4662).ssot_config'schromadbfield, Knowledge base unavailable after single-host deployment — ChromaDB connected but KB not initialized #3094).useRoles.ts,RolesView.vueandOrchestrationView.vueread a role's units as a list. They use the newjoinSystemdUnitsandparseSystemdUnitshelpers, which have tests.Verification
tests/services/test_role_manifest_shape_16025.py(16 tests):autobot-backend/manifest.yml, checking both units.slm/agent/role_detector_16025_test.py(4 tests) covers multi-unit detection.useRoles.test.tscovers the list helpers.test_role_registry.py: three string pins onsystemd_servicebecome list equality.api/code_sync.pychanged, which is the file fix(code-sync): delete files removed from source through the updater, and check drift across every file (#16310, #16322) #16351 and feat(pricing): live cross-checked prices, refreshed on every install, at first boot and on a set cadence (#16229, #16231) #16236 edit.py_compile,ast.parse); repo code isn't run locally, so CI is the runtime check.Follow-ups, filed as sub-issues of #16025:
namerename.Model Used
Claude Opus 5 (coordinator) with a Claude Sonnet 5 subagent for the implementation.
🤖 Generated with Claude Code