Skip to content

Commit 00462bc

Browse files
committed
fix(showcase): fix pre-existing test failures from smoke-red-tick deletion and e2e-deep timeout
The rename PR (CopilotKit#4388) deleted smoke-red-tick.yml from config/alerts/ and test/fixtures/rules/valid/ but left 16 tests referencing it: - render-red-tick.test.ts: removed smoke-red-tick.yml from YAMLS array and deleted two smoke-specific tests (endpoint link rendering) - rule-loader.test.ts: replaced smoke-red-tick.yml fixture references with agent-red-tick.yml; created matching valid fixture; removed the smoke-red-tick describe block from full YAML contract coverage - e2e-deep.test.ts: increased timeout on failure_turn test from 5s to 15s -- fillAndVerifySend retry loop (3 x 2s) exceeds 5s when stallEvaluate prevents user message count growth
1 parent 795a569 commit 00462bc

4 files changed

Lines changed: 53 additions & 170 deletions

File tree

showcase/harness/src/alerts/render-red-tick.test.ts

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ function ctx(partial: Partial<TemplateContext>): TemplateContext {
5555
}
5656

5757
const YAMLS = [
58-
"smoke-red-tick.yml",
5958
"agent-red-tick.yml",
6059
"chat-red-tick.yml",
6160
"tools-red-tick.yml",
@@ -116,36 +115,6 @@ describe("red-tick YAML rendering — Items 2 & 3", () => {
116115
});
117116
});
118117

119-
// Smoke has the only dimension with `signal.links.*` references — verify
120-
// the present-link path emits the expected Slack-link shape after the guard
121-
// wrap.
122-
it("smoke: renders_link_when_present — <url|label> shape preserved", () => {
123-
const text = loadTemplate("smoke-red-tick.yml");
124-
const r = createRenderer();
125-
const flags = { ...emptyTriggerFlags(), green_to_red: true };
126-
const out = r.render(
127-
{ text },
128-
ctx({
129-
trigger: flags,
130-
signal: {
131-
slug: "mastra",
132-
failCount: 1,
133-
errorDesc: "http 503",
134-
firstFailureAt: "2026-04-20T00:00:00Z",
135-
url: "https://example.test/smoke",
136-
},
137-
}),
138-
);
139-
const rendered = String(out.payload.text);
140-
// A3: smoke template now references `signal.url` directly (driver signal
141-
// has no `links` object). Render label "endpoint" since the linked URL is
142-
// the smoke endpoint that was actually probed.
143-
expect(rendered).toContain("<https://example.test/smoke|endpoint>");
144-
// Guard-wrap must not introduce empty/dangling artifacts even with links
145-
// present.
146-
expect(rendered).not.toMatch(/<\|/);
147-
});
148-
149118
// A2: the dashboard Run link must be wrapped in a `{{#event.runId}}…{{/event.runId}}`
150119
// section so a missing runId doesn't render a broken `/runs/|Run` link every
151120
// alert. Parametrize across all 4 red-tick YAMLs.
@@ -197,29 +166,4 @@ describe("red-tick YAML rendering — Items 2 & 3", () => {
197166
});
198167
});
199168

200-
// A3: the smoke template historically referenced `signal.links.smoke` /
201-
// `signal.links.health` — the DRIVER signal (probes/drivers/liveness.ts) has
202-
// `slug, url, status, errorDesc, latencyMs` and no `links` object, so those
203-
// section-guards were always empty. The template should pull `signal.url`
204-
// directly and render an "endpoint" link.
205-
it("smoke: renders_endpoint_link_from_signal_url (A3)", () => {
206-
const text = loadTemplate("smoke-red-tick.yml");
207-
const r = createRenderer();
208-
const flags = { ...emptyTriggerFlags(), green_to_red: true };
209-
const out = r.render(
210-
{ text },
211-
ctx({
212-
trigger: flags,
213-
signal: {
214-
slug: "mastra",
215-
failCount: 1,
216-
errorDesc: "http 503",
217-
firstFailureAt: "2026-04-20T00:00:00Z",
218-
url: "https://starter.example/smoke",
219-
},
220-
}),
221-
);
222-
const rendered = String(out.payload.text);
223-
expect(rendered).toContain("<https://starter.example/smoke|endpoint>");
224-
});
225169
});

showcase/harness/src/probes/drivers/e2e-deep.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,12 @@ describe("e2e-deep driver", () => {
336336
expect(fsig.failure_turn).toBe(1);
337337
expect(fsig.errorClass).toBe("conversation-error");
338338
expect(fsig.errorDesc).toMatch(/timeout/i);
339-
});
339+
// Timeout budget: fillAndVerifySend retries up to
340+
// SEND_VERIFY_MAX_ATTEMPTS (3) × (SEND_VERIFY_INITIAL_DELAY_MS +
341+
// remaining poll window) ≈ 6s when stallEvaluate prevents the user
342+
// message count from growing, plus the 200ms responseTimeoutMs
343+
// deadline. 15s absorbs CI variance without masking real hangs.
344+
}, 15_000);
340345

341346
it("emits red with goto-error when navigation fails", async () => {
342347
registerD5Script(

showcase/harness/src/rules/rule-loader.test.ts

Lines changed: 11 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
1414
const FIXTURES = path.resolve(__dirname, "../../test/fixtures/rules");
1515

1616
describe("rule-loader: valid fixtures", () => {
17-
it("loads smoke-red-tick.yml with merged defaults + escalation + guards", async () => {
17+
it("loads agent-red-tick.yml with merged defaults + escalation + guards", async () => {
1818
const loader = createRuleLoader({
1919
dir: path.join(FIXTURES, "valid"),
2020
logger,
2121
});
2222
const rules = await loader.load();
2323
expect(rules).toHaveLength(1);
2424
const r = rules[0]!;
25-
expect(r.id).toBe("smoke-red-tick");
25+
expect(r.id).toBe("agent-red-tick");
2626
expect(r.severity).toBe("warn");
2727
expect(r.stringTriggers).toEqual([
2828
"green_to_red",
@@ -579,8 +579,8 @@ describe("rule-loader: invalid fixtures (skipped, not fatal)", () => {
579579
path.join(tmp, "_defaults.yml"),
580580
);
581581
await fs.copyFile(
582-
path.join(FIXTURES, "valid", "smoke-red-tick.yml"),
583-
path.join(tmp, "smoke-red-tick.yml"),
582+
path.join(FIXTURES, "valid", "agent-red-tick.yml"),
583+
path.join(tmp, "agent-red-tick.yml"),
584584
);
585585
// Drop a broken file alongside.
586586
await fs.copyFile(
@@ -590,7 +590,7 @@ describe("rule-loader: invalid fixtures (skipped, not fatal)", () => {
590590
const loader = createRuleLoader({ dir: tmp, logger });
591591
const { rules, errors } = await loader.loadWithErrors();
592592
expect(rules).toHaveLength(1);
593-
expect(rules[0]!.id).toBe("smoke-red-tick");
593+
expect(rules[0]!.id).toBe("agent-red-tick");
594594
expect(errors).toHaveLength(1);
595595
expect(errors[0]!.file).toBe("missing-id.yml");
596596
});
@@ -612,16 +612,16 @@ describe("rule-loader: reload error propagation", () => {
612612
path.join(tmp, "_defaults.yml"),
613613
);
614614
await fs.copyFile(
615-
path.join(FIXTURES, "valid", "smoke-red-tick.yml"),
616-
path.join(tmp, "smoke-red-tick.yml"),
615+
path.join(FIXTURES, "valid", "agent-red-tick.yml"),
616+
path.join(tmp, "agent-red-tick.yml"),
617617
);
618618
await fs.copyFile(
619619
path.join(FIXTURES, "invalid", "missing-id.yml"),
620620
path.join(tmp, "missing-id.yml"),
621621
);
622622
const loader = createRuleLoader({ dir: tmp, logger });
623623
const { rules, errors } = await loader.loadWithErrors();
624-
expect(rules.map((r) => r.id)).toEqual(["smoke-red-tick"]);
624+
expect(rules.map((r) => r.id)).toEqual(["agent-red-tick"]);
625625
expect(errors.map((e) => e.file)).toEqual(["missing-id.yml"]);
626626
});
627627

@@ -1418,108 +1418,6 @@ describe("rule-loader + renderer: full YAML contract coverage", () => {
14181418
});
14191419
});
14201420

1421-
// ---- smoke-red-tick.yml -------------------------------------------
1422-
describe("smoke-red-tick", () => {
1423-
it("green_to_red branch renders slug, errorDesc and endpoint link", async () => {
1424-
const { rules, errors, renderer } = await loadRealRules();
1425-
expect(
1426-
errors.find((e) => e.file.startsWith("smoke-red-tick")),
1427-
).toBeUndefined();
1428-
const rule = rules.find((r) => r.id === "smoke-red-tick");
1429-
expect(rule).toBeDefined();
1430-
expect(rule!.stringTriggers).toEqual([
1431-
"green_to_red",
1432-
"sustained_red",
1433-
"red_to_green",
1434-
]);
1435-
const ctx = makeCtx(
1436-
rule!,
1437-
{
1438-
slug: "coagents-starter",
1439-
errorDesc: "http 503",
1440-
// A3: driver signal carries `url` (endpoint that was probed)
1441-
// instead of a `links` object — the template now renders a single
1442-
// endpoint link rather than separate smoke/health pair.
1443-
url: "https://svc.example/smoke",
1444-
failCount: 1,
1445-
},
1446-
{ trigger: { green_to_red: true, isRedTick: true } },
1447-
);
1448-
const text = (
1449-
renderer.render({ text: rule!.template!.text }, ctx).payload as {
1450-
text: string;
1451-
}
1452-
).text;
1453-
expect(text).toContain("coagents-starter");
1454-
expect(text).toContain("down, error: http 503");
1455-
// Triple-brace signal.url (marked slackSafe in LIVENESS_SLACK_SAFE_FIELDS)
1456-
// preserves the raw URL inside `<URL|label>` Slack link markup; prior
1457-
// double-brace would HTML-escape `/` → `&#x2F;` and break the link at
1458-
// Slack render time.
1459-
expect(text).toContain("<https://svc.example/smoke|endpoint>");
1460-
});
1461-
1462-
it("sustained_red branch renders failCount (attempt: N) and error", async () => {
1463-
const { rules, renderer } = await loadRealRules();
1464-
const rule = rules.find((r) => r.id === "smoke-red-tick")!;
1465-
const ctx = makeCtx(
1466-
rule,
1467-
{
1468-
slug: "mastra-starter",
1469-
errorDesc: "timeout after 15000ms",
1470-
// A3: endpoint URL lives under `signal.url` now.
1471-
url: "https://m.example/smoke",
1472-
failCount: 3,
1473-
},
1474-
{ trigger: { sustained_red: true, isRedTick: true } },
1475-
);
1476-
const text = (
1477-
renderer.render({ text: rule.template!.text }, ctx).payload as {
1478-
text: string;
1479-
}
1480-
).text;
1481-
expect(text).toContain("mastra-starter");
1482-
expect(text).toContain("attempt: 3");
1483-
expect(text).toContain("timeout after 15000ms");
1484-
});
1485-
1486-
it("red_to_green branch renders recovery + firstFailureAt", async () => {
1487-
const { rules, renderer } = await loadRealRules();
1488-
const rule = rules.find((r) => r.id === "smoke-red-tick")!;
1489-
const ctx = makeCtx(
1490-
rule,
1491-
{
1492-
slug: "langgraph-starter",
1493-
firstFailureAt: "2026-04-19T23:00:00Z",
1494-
},
1495-
{ trigger: { red_to_green: true } },
1496-
);
1497-
const text = (
1498-
renderer.render({ text: rule.template!.text }, ctx).payload as {
1499-
text: string;
1500-
}
1501-
).text;
1502-
expect(text).toContain("langgraph-starter");
1503-
expect(text).toContain("recovered");
1504-
expect(text).toContain("was down since 2026-04-19T23:00:00Z");
1505-
});
1506-
1507-
it("fleet rule owns <!channel> escalation (migrated from per-service red-tick)", async () => {
1508-
// Plan Item 4: <!channel> moved off smoke-red-tick onto smoke-red-fleet.
1509-
// The per-service rule still fires per-match via its own targets; the
1510-
// fleet rule is the single pager for cross-service outages. Per-service
1511-
// red-tick template must NOT contain <!channel>; fleet rule template
1512-
// MUST. Both invariants asserted together so a future refactor can't
1513-
// silently drop one without the other surfacing.
1514-
const { rules } = await loadRealRules();
1515-
const perService = rules.find((r) => r.id === "smoke-red-tick")!;
1516-
const fleet = rules.find((r) => r.id === "smoke-red-fleet")!;
1517-
expect(perService.template!.text).not.toContain("<!channel>");
1518-
expect(fleet.aggregation).toBeDefined();
1519-
expect(fleet.aggregation!.template).toContain("<!channel>");
1520-
});
1521-
});
1522-
15231421
// ---- deploy-result.yml (remaining branches R20 didn't cover) ------
15241422
describe("deploy-result (R20 follow-up branches)", () => {
15251423
it("green_to_red partial branch renders failed/succeeded lists", async () => {
@@ -1680,8 +1578,8 @@ describe("rule-loader: initial load emits rules.reload.failed on errors (R24 buc
16801578
path.join(tmp, "_defaults.yml"),
16811579
);
16821580
await fs.copyFile(
1683-
path.join(FIXTURES, "valid", "smoke-red-tick.yml"),
1684-
path.join(tmp, "smoke-red-tick.yml"),
1581+
path.join(FIXTURES, "valid", "agent-red-tick.yml"),
1582+
path.join(tmp, "agent-red-tick.yml"),
16851583
);
16861584
// One malformed rule (missing id, fails schema).
16871585
await fs.copyFile(
@@ -1707,7 +1605,7 @@ describe("rule-loader: initial load emits rules.reload.failed on errors (R24 buc
17071605

17081606
// Good rule still loads.
17091607
expect(rules).toHaveLength(1);
1710-
expect(rules[0]!.id).toBe("smoke-red-tick");
1608+
expect(rules[0]!.id).toBe("agent-red-tick");
17111609
// Bad rule surfaces via errors.
17121610
expect(errors).toHaveLength(1);
17131611
expect(errors[0]!.file).toBe("missing-id.yml");
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
id: agent-red-tick
2+
name: "Starter agent reachability failing"
3+
owner: "@oss"
4+
5+
signal:
6+
dimension: agent
7+
8+
triggers:
9+
- green_to_red
10+
- sustained_red
11+
- red_to_green
12+
13+
conditions:
14+
escalations:
15+
- whenFailCount: 4
16+
mention: "!channel"
17+
severity: critical
18+
guards:
19+
- minDeployAgeMin: 20
20+
rate_limit:
21+
perKey: "{{signal.slug}}:{{triggerName}}"
22+
window: 15m
23+
suppress:
24+
when: 'trigger == "sustained_red" && lastAlertAgeMin < 15'
25+
26+
targets:
27+
- kind: slack_webhook
28+
webhook: oss_alerts
29+
30+
template:
31+
text: |
32+
{{#trigger.green_to_red}}:red_circle: *{{signal.slug}}* — agent unreachable, error: {{signal.errorDesc}}{{/trigger.green_to_red}}
33+
{{#trigger.sustained_red}}:red_circle: *{{signal.slug}}* — agent attempt: {{signal.failCount}}, error: {{signal.errorDesc}}{{/trigger.sustained_red}}
34+
{{#trigger.red_to_green}}:white_check_mark: *{{signal.slug}}* agent recovered (was down since {{signal.firstFailureAt}}){{/trigger.red_to_green}}
35+
36+
<{{env.dashboardUrl}}|Showcase>{{#event.runId}} · <{{env.dashboardUrl}}/runs/{{event.runId}}|Run>{{/event.runId}}

0 commit comments

Comments
 (0)