Skip to content

get_alert_history/set_mute_rule_enabled/create_mute_rule/set_notification_route_enabled move reading guidance off tools/list (#3898) - #4077

Merged
erikdarlingdata merged 4 commits into
devfrom
feature/3898-content-alerting-c
Sep 23, 2026
Merged

erikdarlingdata merged 4 commits into
devfrom
feature/3898-content-alerting-c

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Refs #3898.

Why

Darling's tools/list answer carries long reading guidance inside four alerting-family tool descriptions. Under
#3898, that guidance moves off tools/list into each tool's get_tool_guide tail. Nothing is deleted. A short
head stays on the wire with the facts a model needs to pick the right tool. The full original text becomes
available on request, so a model can still read it before it acts on the answer.

What changes

Converts four tools to the two-tier head/tail shape:

  • get_alert_history: a twin tool, converted on both Darling and Lite with a byte-identical head (D6).
  • set_notification_route_enabled, create_mute_rule, set_mute_rule_enabled: Darling only (Lite has no
    mute-rule or notification-route write tools).

Each head states what the tool returns or changes, plus the empty, zero and null semantics a caller needs. For
the three write tools, it also states what changes for everyone. It names any confirm step, and what is
reversible or easy to get wrong.

Before/after served characters

Tool Product Before After (head + pointer)
get_alert_history Darling 4,740 589
get_alert_history Lite 3,573 589
set_notification_route_enabled Darling 841 512
create_mute_rule Darling 959 528
set_mute_rule_enabled Darling 1,477 483

get_alert_history's include_dismissed parameter also dropped, per D2 (see below).

Corrections

None. The four tools' original prose was checked against their implementations (field names, default values,
branch conditions) and nothing was found to be wrong.

D4 removals (issue references stripped from the wire)

  • get_alert_history (Darling tail): removed (#3712) (two occurrences) and (#3598) (one occurrence). The
    sentences that carried them are unchanged otherwise.
  • get_alert_history (Lite tail): removed (#3712) (two occurrences).
  • The three write tools carried no issue references in their original text. Nothing was removed there.

D2: parameter over 200 characters

get_alert_history's include_dismissed parameter was 360 characters on Darling and 357 on Lite. On each
product, the guardrail sentence (what the parameter does, and its default) stays on the parameter, at 114
(Darling) and 111 (Lite) characters. The rest of the original sentence moved, verbatim, to that tool's own tail
(Dismissal is an acknowledgement, not a verdict...), rather than being duplicated.

Re-pointed pins

None needed. Every existing test class that mentions one of these four tools was run and passed unchanged. On
Darling that is DarlingMcpAlertToolsTests, DarlingPgOperationalAlertTests, DarlingSelfAlertTests,
DarlingWebEndpointsTests, McpPageContractTests, and NotificationRoutesRungTests. On Lite that is
CrossAppMcpToolInventoryPinTests, McpPageContractTests, and ToolRecommendationsTests. None of them asserted
a specific head or tail substring for these four tools. NotificationRoutesRungTests does pin substrings, but
for get_notification_routes, which this PR does not touch.

New pins were added instead, in the existing McpToolGuideHeads.Alerting.cs on both products. The four tool
names were added to the file's own AlertingTools roster, which the file's existing, unedited
EveryAlertingHead_StaysAtOrUnder620Characters_AndPointsToTheGuide test already loops over. One new head test
and one new tail test were added per tool, plus one shared tail test for get_alert_history's Lite-only facts.
No existing test method was edited.

D9 traps

Each trap below names a question the served head text above, not the tail, must answer correctly.

Q: Does a row in get_alert_history mean the alert was delivered (emailed or paged)?
Correct: No. A row means the alert FIRED. notification_type is the separate field that says whether it was
delivered.
Prevents it: "notification_type says whether it was DELIVERED, a separate question from DISMISSED"

Q: get_alert_history returns fewer rows than the hours_back window covers. Does raising hours_back help?
Correct: No. The page is bounded by limit, not hours_back. Raise limit, or narrow hours_back if
truncated is true.
Prevents it: "THE PAGE IS BOUNDED BY limit, NOT hours_back"

Q: Does an empty get_alert_history page always mean no alerts fired in the window?
Correct: No. It can also mean every alert in the window was dismissed by the default filter.
Prevents it: "An EMPTY page can mean no alerts fired, or that every alert here was dismissed"

Q: Are dismissed alerts included in get_alert_history by default?
Correct: No. Dismissed rows are excluded unless include_dismissed = true.
Prevents it: "DISMISSED (UI-acknowledged), excluded by default"

Q: If a row's send_error is null, does that prove the alert delivered successfully?
Correct: No. A null send_error proves nothing about delivery either way.
Prevents it: "A null send_error proves nothing about delivery."

Q: Does set_notification_route_enabled delete the route when disabling it?
Correct: No. It disables the route WITHOUT deleting it, and re-enabling restores it exactly.
Prevents it: "Reversible: re-enabling restores it exactly."

Q: Does calling set_notification_route_enabled twice with the same enabled value write twice?
Correct: No. The second call returns unchanged and is safe to retry.
Prevents it: "unchanged (already that value, safe to retry)"

Q: Can set_notification_route_enabled re-point a route to a new destination?
Correct: No. It can only enable or disable an existing route. Authoring or re-pointing a route happens only
in the Settings window.
Prevents it: "Cannot author or re-point a route; that is Settings-window only."

Q: If create_mute_rule is called with no scope or pattern fields at all, what happens?
Correct: It creates a rule that mutes EVERY alert across the whole fleet. That is a whole-fleet silence, not
a no-op.
Prevents it: "a rule with NO fields set matches and mutes EVERY alert across the whole fleet"

Q: Does a muted alert stop being logged in get_alert_history?
Correct: No. Matching alerts made by create_mute_rule are still logged, just not delivered.
Prevents it: "matching alerts are still logged, just not delivered"

Q: Is there a confirmation step before a new mute rule from create_mute_rule takes effect?
Correct: No. There is no confirm step. It takes effect on the running service's next collection sweep.
Prevents it: "No confirm step: the rule takes effect on the running service's next collection sweep."

Q: Is delete_mute_rule followed by create_mute_rule an equally good way to temporarily silence a rule as
set_mute_rule_enabled?
Correct: No. Delete-then-create loses the rule's id and resets the Stale Mute Rules age clock.
set_mute_rule_enabled keeps both.
Prevents it: "unlike delete+create, which loses those and resets the Stale Mute Rules age clock"

Q: Does disabling a mute rule with set_mute_rule_enabled clear its scope, reason, or creation date?
Correct: No. It keeps the rule's id, scope, reason and creation date. Only the enabled flag changes.
Prevents it: "keeps its id, scope, reason and creation date"

Test plan

  • Darling/Darling.Tests/Darling.Tests.csproj builds with 0 warnings.
  • Lite.Tests/Lite.Tests.csproj builds with 0 warnings.
  • Targeted classes on Darling: McpToolGuide*, McpDescription*, McpZeroIsAMeasurement*,
    McpPayloadContractCensus*, plus every class that mentions one of the four tools. 498 total, 0 failed, 7
    skipped (live-Postgres, no rig).
  • Targeted classes on Lite: the same families, plus the tools' own classes, run as two batches. 56 total,
    then 6 total. 0 failed in either.
  • Red-watch: broke get_alert_history's head (each row FIRED -> each row XFIRED). Watched
    GetAlertHistoryHead_StatesFiredVsDeliveredVsDismissedAndThePageBound fail with the expected message.
    Restored it and confirmed green again.
  • Dropped-sentence check (dropcheck.py) against origin/dev, for all four tools. missing=0 on every
    product.
  • Merged origin/dev (clean, no conflicts) and rebuilt both projects (0 warnings each).
  • Full Darling suite, once: 13,232 total, 0 failed, 631 skipped (live-Postgres/pg-runtime gated).
  • Full Lite suite, once: 5,234 total, 1 failed
    (McpToolsListBudgetTests.EveryServedDescription_AndEveryParameterDescription_MatchesItsCeiling).
    Re-ran it alone on the same build right after. It passed. This test measures a pure function of the files
    on disk, cached once per process in a static field. That measurement does not depend on my wording. This
    reads as a pre-existing flake in the full run's test order or parallelism, not a defect in this change.
    The lane rule says a failure that passes alone, on a freshly merged base, is not mine to file. This goes to
    the coordinator's flake census instead of a new issue.
  • Plain-English checker on this PR body: pr-body-alerting-c.md: 39 hit(s), doc mode. Every real hit was
    fixed. The 39 remaining are all **Q:** / **Correct:** / **Prevents it:** bold lead-ins, the D9 label
    format the brief allows.

Coordinator verification

  • Merged origin/dev into the branch (merge commit bc1496b).
  • dropcheck: missing=0 for all four tools, on both products for get_alert_history.
  • D9: a small model answered all 13 traps from the served text alone, with no misreads and no hedges.
  • Full suites on bc1496b: Darling.Tests 13,239 total, 0 failed. Lite.Tests 5,236 total, 0 failed. Both builds had 0 warnings.
  • The lane's Lite budget failure is the known flake tracked in Lite budget test sometimes sees get_alert_history's dataService parameter in a full run #4075. It did not recur here.

Handoff

All four assigned tools are converted and green. Nothing was deferred from this lane's list.

The one open item is the Lite full-suite-only flake noted above in the test plan. It is not specific to this
PR's content. The coordinator can fold it into the next flake census if it recurs on other lanes.

erikdarlingdata and others added 4 commits September 23, 2026 16:51
…tion_route_enabled move reading guidance off tools/list (#3898)

Refs #3898. Converts the remaining alerting-family MCP tool descriptions
to the two-tier head/tail shape: tools/list serves a short guardrail
head plus a pointer, and get_tool_guide serves the full original prose
as the tail. get_alert_history converts on both Darling and Lite with a
byte-identical head (D6); the three mute-rule/route write tools are
Darling-only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M7r1CXmEcBmsrTV34CCLfF
Refs #3898. Extends McpToolGuideHeads.Alerting.cs on both products with
get_alert_history, set_mute_rule_enabled, create_mute_rule and
set_notification_route_enabled: the roster used by the shared
under-620-chars/pointer check, plus per-tool head and tail assertions
following the file's existing pattern. No existing method edited.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M7r1CXmEcBmsrTV34CCLfF
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 23, 2026 21:22
@erikdarlingdata
erikdarlingdata enabled auto-merge (squash) September 23, 2026 21:22
@erikdarlingdata
erikdarlingdata merged commit c159d75 into dev Sep 23, 2026
16 of 18 checks passed
@erikdarlingdata
erikdarlingdata deleted the feature/3898-content-alerting-c branch September 23, 2026 21:29
erikdarlingdata added a commit that referenced this pull request Sep 24, 2026
* Add 48 buffered CHANGELOG entries in one splice

Adds 48 entries and 48 link refs (#4057, #4077, #4079, #4081, #4082, #4083, #4084, #4085, #4086, #4087, #4088, #4089, #4090, #4091, #4092, #4093, #4095, #4096, #4099, #4100, #4101, #4103, #4105, #4106, #4107, #4108, #4109, #4110, #4111, #4113, #4114, #4115, #4116, #4117, #4118, #4119, #4120, #4121, #4122, #4123, #4124, #4125, #4126, #4127, #4131, #4133, #4136, #4141). Each PR's entry was buffered, and this lands every entry whose PR was merged on origin/dev when it ran.

Entries found under a bare section line (none unless the old script ran again) move into the matching ### section, below the new entries.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC

* Changelog splice: add #4137's entry, which merged before the splice but was missed

#4137 (pg_plan_capture reads csvlog) merged at 05:57Z with a CHANGELOG entry section in its
body. It was neither spliced nor listed as needing no entry. Its entry goes under Fixed, right
after #4136's, and #4136's last sentence now points to it instead of saying plan capture is
unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUU29PuGg9TUFBsACgZg2K

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant