Skip to content

provision: setup-ai-stack and setup-npu-worker target sibling group names the deploy gate does not cover #14567

Closed

Description

@mrveiss

Problem

_DECLARED_ONLY_GROUPS (#14552) gates the group names update-all-nodes.yml tests in its when: "'x' in group_names" gates: aiml, npu, browser, backend, frontend, plus Play 1's slm_server.

But _ROLE_TO_GROUPS grants sibling names alongside each of those:

ai-stack    -> {ai_stack, aiml, ai, llm_nodes}
npu-worker  -> {npu, npu_worker, npu_workers}
browser-service -> {browser, browser_worker}

Only the gated name is stripped from an undeclared node. The siblings remain — and two provisioning playbooks target them directly by hosts::

  • ansible/setup-ai-stack.yml:19 — hosts: ai_stack
  • ansible/setup-npu-worker.yml:31 — hosts: npu_worker

Both are reachable from the same /infrastructure/execute surface that runs update-all-nodes.yml. ExecutePlaybookRequest.limit_hosts is optional and requires_confirmation is UI-only, so nothing server-side scopes them.

So a node whose ai-stack or npu-worker membership came only from detection keeps ai_stack/npu_worker, and would receive the full provisioning playbook if either is ever run fleet-wide without --limit.

Not a regression from #14552

This predates that change — #14552 only made the asymmetry visible by gating the sibling of each pair. Worth recording that browser-service does not have this gap: setup-browser-worker.yml:19 targets hosts: browser, the already-gated name, even though api/infrastructure.py:291 labels it target_hosts=["browser_worker"]. That metadata/reality mismatch is harmless today but is its own small inconsistency.

ansible/site.yml also targets hosts: ai_stack / hosts: npu_workers, but appears to be dead: no Python trigger references it and the roles it lists (cuda_drivers, ai_models, python) do not exist under ansible/roles/. Worth confirming before relying on that.

Fix direction

Decide whether group membership should be per-role-name at all, or whether the sibling aliases exist only for historical inventory compatibility and can be collapsed. Collapsing them would remove this whole class rather than adding four more names to a gate list — the enumerate-vs-derive lesson from #14552.

If they must stay, the gate should cover every group a node acquires from a role whose canonical group is gated, derived from _ROLE_TO_GROUPS rather than listed.

Acceptance criteria

  • A node that never declared ai-stack cannot be targeted by setup-ai-stack.yml run without --limit; same for npu-worker.
  • The rule is derived from the role/group mapping, not a hand-maintained list of names.
  • site.yml's status (live or dead) is established rather than assumed.

Related

Activity

  1. mrveiss commented on Aug 21, 2026

    @mrveiss
    OwnerAuthor

    Attempted, reverted — analysis for whoever picks this up

    The gap is confirmed real: setup-ai-stack.yml:19 targets hosts: ai_stack and setup-npu-worker.yml:31 targets hosts: npu_worker, both sibling names _ROLE_TO_GROUPS grants alongside the gated aiml/npu, both reachable from /infrastructure/execute where limit_hosts is optional.

    I attempted a fix, it broke base, and I reverted it (PR #14664). What I learned is worth more than the attempt.

    The derivation that seems right

    Close over _ROLE_TO_GROUPS: if a role can place a node in a deploy-gated group, every group that role grants is equally a deploy target. Enumerating names has now been too short three times (#14513 → #14552 → this), so deriving is the right instinct.

    Refinement 1 — needed, and it is not optional

    The naive closure gates slm/slm_nodes, because the slm- prefix role grants them alongside slm_server. deploy-slm-agent.yml targets slm_nodes. That version would withhold the agent-repair playbook from agent nodes — the remedy the agent's own 401 message names (#14350 / #14351) becomes unreachable for exactly the nodes that need it.

    Fix: a group stays ungated when a non-deploy role also grants it. slm-agent grants slm/slm_nodes and is not a deploy target, so those stay broad.

    Refinement 2 — where it collides with other work, and why I stopped

    Even with refinement 1, the closure breaks two deliberate designs:

    1. inventory_privileged_node_roles_test.py::test_privileged_facts_use_declared_only_node_roles (provision: role_active_facts activates privileged roles from unfiltered node_roles, bypassing the #14552 declaration gate #14560). Gating llm_nodes makes role_llm_active a privileged fact, but it still reads the raw union node_roles. Converting it to node_roles_declared satisfies the guard — I verified that works — but it means this change cannot land without also touching provision: role_active_facts activates privileged roles from unfiltered node_roles, bypassing the #14552 declaration gate #14560's model.

    2. test_wizard_inventory_parity_14286.py::test_deliberately_union_role_keeps_its_non_privileged_group (Fleet health degraded on fresh install: VM agent dies after early-404 heartbeats + optional assigned roles never activate (vnc detection gap) #9965). Gating npu_worker breaks tts-worker's deliberately non-privileged membership in that group. tts-worker maps to {ai_stack, aiml, ai} — it grants the seeded aiml, so any closure classifies it as a deploy role and sweeps its groups in. This one I do not have a clean answer for: tts-worker genuinely is an ai-stack-family role, and npu_worker membership for it is genuinely legacy vocabulary that must survive detection alone.

    The tension is that "deploy role" and "legacy vocabulary group" overlap in tts-worker, and resolving it needs someone who holds the #9965 intent.

    Suggested shape for the next attempt

    Process note

    The reverted attempt reached Dev_new_gui by a direct push from the main tree (my cd into the worktree did not persist), which is how base went red rather than a PR catching this. That is recorded on PR #14664.

  2. github-actions commented on Aug 21, 2026

    @github-actions
    Contributor

    PR #14675 (merged to Dev_new_gui) references this issue with a close keyword.

    fix(slm): derive the deploy gate over the role map, and require declaration for npu_worker (#14567)

    If this issue is fully resolved, close it manually. If work remains, no action is needed.

  3. mrveiss commented on Aug 23, 2026

    @mrveiss
    OwnerAuthor

    Resolved by #14675 (d6505d0210), not by anything filed since. Closing with the evidence, because the squash to Dev_new_gui never auto-closes.

    This issue's claim was that _DECLARED_ONLY_GROUPS gated only the names update-all-nodes.yml tests in its when: gates, leaving sibling group names uncovered. _close_over_role_groups now derives the gate over the role map. Evaluating that closure against the real _ROLE_TO_GROUPS:

    seed    : aiml, backend, browser, frontend, main, npu, slm_server
    derived : ai, ai_stack, browser_worker, llm_nodes, npu_worker, npu_workers
    

    All six siblings named here are in the gate.

    The derived-ness is what this issue asked for and it introduced a second-order risk, since a change to the role map can now shrink the gate silently. That is #14681, fixed in PR #14855 by pinning the whole 13-group set rather than the four names that happened to be listed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions