[glm-grade=A] fix(notifications): un-gate 7 dead emitters + repair legacy 4-arg send shape (DC-094)
CI / Test & Lint (push) Canceled after 0s
CI / Security audit (push) Canceled after 0s

Two silent-death defects in one class:

1. Seven emitters absent from DEFAULT events, so the send() gate
   (config.events[canonical] !== true) dropped them on every install:
   ssl-cert-expiry, dns-propagation, drift-detected,
   dependency-restart-complete/-failed, recipeRemoved, workflow.
   All now default ON; dependency-restart spellings fold onto one
   canonical toggle; recipeRemoved aliases to recipe-removed in both
   the manager and route alias maps.

2. Nine call sites used a legacy 4-arg send(event, title, message, type)
   against the 3-arg signature: the message string landed in the type
   slot (Discord embed color fell back to info-blue, history.type wrong)
   and providers received the TITLE as the body - deploy-failure
   notifications carried no error text at all. Fixed at source (9 sites)
   plus a type-guarded shim in send() for external legacy callers.

Also: explicit data.title now flows to ntfy Title header, email subject,
Discord embed title, and history; settings UI gains 9 event toggles
(separate Backup Complete/Failed) with defaults-on semantics.

Tests: +24 (new DC-094 suite: defaults, gate pass-through, alias folding
send-time and load-time, stored-config inheritance, shim body/title/
color/subject/history, type-guard, 3-arg no-regression); 4 assertions in
bundled-workflows-health-check updated from the old 4-arg mock contract
to the canonical shape (same behavior asserted). Full suite 116 suites /
2706 tests green.

Judge: GLM-5.3 cold read via delegate_task (deleg_8a7cedd0), grade A,
zero blockers; 2 polish items (Backups toggle conflation, shim
type-guard) folded into this commit. Verdict URN:
urn:ump:uyipjjwdjqjy3alvceqxvlucrymd7hnsben5udpp2bqycoh3l2la
This commit is contained in:
Hermes
2026-08-22 19:42:25 -07:00
parent 5add962178
commit 97672f7e74
11 changed files with 382 additions and 30 deletions
@@ -253,8 +253,8 @@ describe('WorkflowEngine._runActions — DC-044 root-cause (notify-on-failure +
expect(healthResult.failingServices).toEqual(['svc-broken']);
expect(notifyResult.success).toBe(true);
expect(notify).toHaveBeenCalledTimes(1);
// notification.send signature: (category, title, message, level)
const sentMessage = notify.mock.calls[0][2];
// DC-094 notification.send signature: (event, { title, text }, level)
const sentMessage = notify.mock.calls[0][1].text;
expect(sentMessage).toBe('Health check failed for svc-broken');
expect(sentMessage).not.toContain('{{');
});
@@ -269,7 +269,7 @@ describe('WorkflowEngine._runActions — DC-044 root-cause (notify-on-failure +
);
expect(notify).toHaveBeenCalledTimes(1);
expect(notify.mock.calls[0][2]).toBe('always sent');
expect(notify.mock.calls[0][1].text).toBe('always sent');
expect(results[0].success).toBe(true);
});
@@ -313,7 +313,7 @@ describe('WorkflowEngine._runActions — DC-044 root-cause (notify-on-failure +
);
expect(notify).toHaveBeenCalledTimes(1);
const sentMessage = notify.mock.calls[0][2];
const sentMessage = notify.mock.calls[0][1].text;
expect(sentMessage).toBe('Failing: svc-broken-1,svc-broken-2');
expect(results.find(r => r.action === 'notify-on-failure').success).toBe(true);
});
@@ -346,7 +346,7 @@ describe('WorkflowEngine._runActions — DC-044 root-cause (notify-on-failure +
// message) OR every action resolved — but in NO case may a literal
// {{...}} template token leak into notification.send.
if (notify.mock.calls.length > 0) {
const sentMessage = notify.mock.calls[0][2];
const sentMessage = notify.mock.calls[0][1].text;
expect(sentMessage).not.toMatch(/\{\{/);
expect(sentMessage).not.toMatch(/\}\}/);
// The new bundled template substitutes failingServices — make sure