Review: Salt: Stalwart install + systemd service state

review-1733-2026-07-04 Review

review ready

Verdict: READY

Scope is solid. All file targets verified against codebase. Traceability complete. Work follows established caddy state pattern exactly. Fits in a single agent pass.

Template Completeness

  • [x] Type -- Feature
  • [x] Lineage -- Standalone, part of stalwart-email project
  • [x] Repo -- ldraney/pal-e-platform
  • [x] User Story -- As a platform admin, I want Stalwart installed via SaltStack
  • [x] Context -- Describes Stalwart, edge VPS, why mail server needs this IP
  • [x] File Targets -- 3 create + 1 modify, all paths valid
  • [x] Feature Flag -- none (appropriate for infra)
  • [x] Acceptance Criteria -- 6 items, all testable
  • [x] Test Expectations -- 3 items with real commands
  • [x] Constraints -- 4 items (unprivileged user, interface binding, pattern conformance)
  • [x] Checklist -- Present
  • [x] Related -- stalwart-email project referenced

Traceability

  • [x] story:self-hosted label -- present on board item
  • [x] story note verified -- "self-hosted" entry found in project-stalwart-email user-stories section ("As platform admin, I want zero third-party email dependencies")
  • [x] arch:stalwart label -- present on board item
  • [x] arch note verified -- arch-stalwart note exists (note_type: architecture, project: stalwart-email)
  • [x] arch-edge-vps note also exists -- secondary component, not required as label since arch:stalwart is the primary
  • [x] Forgejo issue -- ldraney/pal-e-platform#498, state: open

File Targets

  • [x] salt/states/stalwart/init.sls -- to create. Parent dir salt/states/ exists. No existing stalwart directory (clean).
  • [x] salt/states/stalwart/config.json.j2 -- to create. Same parent as above.
  • [x] salt/pillar/stalwart.sls -- to create. Parent dir salt/pillar/ exists. No existing stalwart pillar.
  • [x] salt/states/top.sls -- to modify. File exists. Currently assigns caddy to edge-proxy minion. Will add stalwart entry.
Pattern reference verified: salt/states/caddy/ contains init.sls + Caddyfile.j2, pillar at salt/pillar/caddy.sls. Stalwart structure mirrors this exactly.

Repo Placement

OK. Issue filed on pal-e-platform, all file targets are in pal-e-platform's salt/ directory. Single-repo change.

Dependencies

  • Board #1732 (pal-e-platform#497, next_up): "Hetzner: Request port 25 outbound unblock" -- soft dependency. Stalwart can be installed and run before port 25 is unblocked; it just cannot send external mail. Install is not blocked.
  • Board #1736 (pal-e-services#164, backlog): Stalwart domain/account config -- DEPENDS ON this ticket (#1733). Cannot configure Stalwart if it is not installed.
  • Board #1740 (pal-e-services#168, todo): Deliverability validation -- depends on both #1733 and #1736 being complete.
Dependency chain is implicit from board ordering. No explicit blockers prevent this ticket from starting.

Acceptance Criteria

All 6 criteria are agent-verifiable:
  • salt-apply installs binary -- verifiable via which stalwart or stalwart --version
  • systemd service as dedicated user -- systemctl show stalwart --property=User
  • Port 587 listening -- ss -tlnp | grep 587 (referenced in test expectations)
  • Port 8080 listening localhost only -- ss -tlnp | grep 8080
  • Config from pillar -- inspect rendered config file contents
  • Auto-restart on failure -- systemctl show stalwart --property=Restart
Test commands in "Test Expectations" section are real and executable.

Blast Radius

Low. New self-contained Salt state. Only shared file modified is top.sls (adding one line). No port conflicts with existing Caddy service (80/443 vs 587/8080). No downstream consumers affected until subsequent tickets configure Stalwart.

Decomposition Assessment

4 file targets in 1 repo. 6 acceptance criteria. Estimated agent time: 3-5 minutes (pattern-following work with caddy as reference). At upper boundary of 5-minute rule but acceptable -- all files follow one established pattern, no cross-repo coordination needed. No decomposition needed.

Recommendation

No action needed.