Remove the Hub Self-Node Concept (YAGNI Cleanup)
Date: 2026-08-11 Status: Design approved, pending implementation plan Scope: Delete the hub “self-node” concept introduced in the admin-hardening work (PR #202). It is inert in production, read by no business logic, and not required for alert management.
Why remove it
The self-node was meant to materialize the hub as a first-class Node so the node axis would be “uniform.” In practice:
- It never works in production.
Node.ensure_self()no-ops whensettings.INSTANCE_IDis empty, andINSTANCE_IDis an agent-side setting the hub does not set. So the hub self-node is never created. - Nothing consumes it.
is_selfis read by no business logic — only shown in the Node admin list/filter.ensure_self()has one functional caller:orchestrator._resolve_nodestampsPipelineRun.nodefor the hub’schecker_generatedruns. - Alert management does not need it. Incident/alert grouping already works via
incident_instance_key(instance_id → instance → hostname). Agent alerts group byinstance_id(a registeredNode); hub self-check alerts group byhostname. Both are visible on the alerts page and in the dashboard incident totals today — confirmed as the desired behavior.
Making it work would require inventing a stable hub identity (a new setting or a hostname fallback) to serve a convenience nobody consumes. Not worth it.
Considered and rejected: a per-source dashboard view
We considered building a dashboard panel that groups incidents/alerts by source node (which would give the hub a real reason to have an identity). Decision: not now. Hub alerts already appear in the dashboard incident totals and on the alerts page; a per-source breakdown is a separate, larger feature that is not currently wanted. This design is a pure deletion.
Removed
Node.is_selffield (apps/alerts/models.py) + aRemoveFieldmigration.Node.ensure_self()classmethod (and the now-unusedsocketimport if it becomes unused).bootstrap_self_nodemanagement command (apps/alerts/management/commands/bootstrap_self_node.py) and its test.is_selffromNodeAdmin.list_display/list_filter(apps/alerts/admin.py).- The
is_self/ensure_selftests inapps/alerts/_tests/test_models.pyandtest_admin.py. - The
if origin == CHECKER_GENERATED: return Node.ensure_self()branch inorchestrator._resolve_node— all origins now flow through the single label-based resolution path. - Self-node wording in
apps/alerts/AGENTS.mdand the twohelp_textstrings that mention “hub self-node”.
Kept (deliberately — broader than the self-node)
PipelineRun.nodeFK and thePipelineOriginenum.origin(incoming_webhook/checker_generated/manual) still usefully classifies runs;checker_generatedstays a valid label, just no longer node-mapped.- The dashboard nodes-readiness card (uses
Node.count()/last_seen, neveris_self). docs/plans/*design docs (historical record) — left unchanged, including the admin-hardening design that introduced the self-node.
Behavior change
The hub’s own --checks-only pipelines, which would have received node=ensure_self(), now get node=null — via the single label-resolution path (no instance_id in a self-check payload). In production this is a no-op: ensure_self() already returns None there, so those runs are already node=null. Their alerts continue to group by hostname exactly as today.
Testing
- Delete the obsolete tests (
is_self,ensure_self,bootstrap_self_node). - Flip
test_checker_generated_run_gets_self_node→ assertrun.node is None(rename accordingly). - Full suite green; 100% branch coverage on changed lines.
makemigrations --check --dry-runclean after theRemoveFieldmigration.bandit/black/ruffclean.
Acceptance criteria
- No references to
is_self,ensure_self, orbootstrap_self_noderemain inapps/,config/, orbin/(design docs excepted). Nodehas nois_selfcolumn (migration applied);_resolve_nodehas a single resolution path.- Full CI green; coverage 100% on changed lines; no behavior change to deployed alert grouping.