feat: M11b — ownership tracking, agent groups, interactive renewal approval

Ownership: owners/teams GUI pages, notification email resolution via
resolveRecipient (owner_id → owner.email lookup). Agent groups: dynamic
device grouping by OS/arch/IP CIDR/version with manual include/exclude
membership, migration 000004, full CRUD stack (domain → repo → service →
handler → frontend). Interactive approval: AwaitingApproval job state,
approve/reject API endpoints with reason tracking. Tests: 12 agent group
handler tests, 8 approve/reject job handler tests, integration tests
updated for 13-param RegisterHandlers. Docs updated across architecture,
concepts, and seed data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Shankar
2026-03-20 21:02:35 -04:00
parent 1ef16984eb
commit e445cbef22
27 changed files with 1774 additions and 21 deletions
+4
View File
@@ -0,0 +1,4 @@
-- Rollback migration 000004: Agent Groups
ALTER TABLE renewal_policies DROP COLUMN IF EXISTS agent_group_id;
DROP TABLE IF EXISTS agent_group_members;
DROP TABLE IF EXISTS agent_groups;
+32
View File
@@ -0,0 +1,32 @@
-- Migration 000004: Agent Groups
-- Adds dynamic device grouping by agent metadata criteria with manual override.
CREATE TABLE IF NOT EXISTS agent_groups (
id TEXT PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE,
description TEXT DEFAULT '',
-- Dynamic matching criteria (empty = manual-only group)
match_os VARCHAR(100) DEFAULT '',
match_architecture VARCHAR(100) DEFAULT '',
match_ip_cidr VARCHAR(45) DEFAULT '',
match_version VARCHAR(50) DEFAULT '',
enabled BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Manual group membership overrides (agents explicitly added/excluded)
CREATE TABLE IF NOT EXISTS agent_group_members (
agent_group_id TEXT NOT NULL REFERENCES agent_groups(id) ON DELETE CASCADE,
agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
membership_type VARCHAR(20) NOT NULL DEFAULT 'include',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (agent_group_id, agent_id)
);
-- Optional: scope renewal policies to an agent group
ALTER TABLE renewal_policies ADD COLUMN IF NOT EXISTS agent_group_id TEXT REFERENCES agent_groups(id) ON DELETE SET NULL;
CREATE INDEX IF NOT EXISTS idx_agent_groups_name ON agent_groups(name);
CREATE INDEX IF NOT EXISTS idx_agent_groups_enabled ON agent_groups(enabled);
CREATE INDEX IF NOT EXISTS idx_agent_group_members_agent ON agent_group_members(agent_id);
+16
View File
@@ -190,3 +190,19 @@ INSERT INTO notification_events (id, type, certificate_id, channel, recipient, m
('ne-demo-05', 'renewal_success', 'mc-api-prod', 'email', 'alice@example.com', 'Certificate api-production renewed successfully', NOW() - INTERVAL '15 days', 'sent', NULL),
('ne-demo-06', 'deployment_success', 'mc-api-prod', 'webhook', 'https://hooks.example.com/certctl', 'Certificate api-production deployed to NGINX Production', NOW() - INTERVAL '15 days', 'sent', NULL)
ON CONFLICT (id) DO NOTHING;
-- Agent Groups
INSERT INTO agent_groups (id, name, description, match_os, match_architecture, match_ip_cidr, match_version, enabled, created_at, updated_at) VALUES
('ag-linux-prod', 'Linux Production', 'All Linux agents in production', 'linux', '', '', '', true, NOW(), NOW()),
('ag-linux-amd64', 'Linux AMD64', 'Linux agents on x86_64 architecture', 'linux', 'amd64', '', '', true, NOW(), NOW()),
('ag-windows', 'Windows Agents', 'All Windows-based agents', 'windows', '', '', '', true, NOW(), NOW()),
('ag-datacenter-a', 'Datacenter A', 'Agents in 10.0.1.0/24 subnet', '', '', '10.0.1.0/24', '', true, NOW(), NOW()),
('ag-manual', 'Manual Group', 'Manually managed agent group (no dynamic criteria)', '', '', '', '', false, NOW(), NOW())
ON CONFLICT (id) DO NOTHING;
-- Agent Group Members (manual membership for the manual group)
INSERT INTO agent_group_members (agent_group_id, agent_id, membership_type, created_at) VALUES
('ag-manual', 'agent-web-1', 'include', NOW()),
('ag-manual', 'agent-api-1', 'include', NOW()),
('ag-manual', 'agent-db-1', 'exclude', NOW())
ON CONFLICT (agent_group_id, agent_id) DO NOTHING;