Review: Salt: Stalwart install + systemd service state
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 dirsalt/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 dirsalt/pillar/exists. No existing stalwart pillar. - [x]
salt/states/top.sls-- to modify. File exists. Currently assignscaddytoedge-proxyminion. Will addstalwartentry.
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-applyinstalls binary -- verifiable viawhich stalwartorstalwart --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.