Skip to content

Recs rebuild WS1b-2: wire Apply (consent) + Mute actions#1064

Merged
erikdarlingdata merged 1 commit into
devfrom
feature/recs-ws1b2-apply-mute
Jun 5, 2026
Merged

Recs rebuild WS1b-2: wire Apply (consent) + Mute actions#1064
erikdarlingdata merged 1 commit into
devfrom
feature/recs-ws1b2-apply-mute

Conversation

@erikdarlingdata
Copy link
Copy Markdown
Owner

Scope (Dashboard-only)

Wires the Recommendations tab's Apply and Mute buttons live, replacing the WS1b-1 disabled "Available in the next update" affordances. Apply mirrors the existing AlertDetailWindow path exactly — fail-closed server resolution, the handler-for-fact gate, then RemediationApplyService.ApplyAsync with a confirm callback that news up RemediationConfirmWindow. Destructive fixes (RCSI / clear-plan) set RequiresInformedConsent, so the two-sided acknowledge-each-risk gate renders automatically (no special-casing); the inaction disclosure draws on the persisted action's carried figures (finding: null, exactly as on the alert path). Mute is engine-only via the lower-level story mute. No Lite changes (apply stack is Dashboard-only).

File:line changelog

  • Dashboard/Services/Recommendations/RecommendationItem.cs:133-139 — add StoryPath (operator-facing label for the mute record).
  • Dashboard/Services/Recommendations/RecommendationsReader.cs:123 — populate StoryPath = finding.StoryPath in MapEngineFinding (the store already reads story_path back).
  • Dashboard/Controls/RecommendationsContent.xaml.cs
    • :57-70_remediationApplyService field + public RemediationApplyService property (mirrors AlertsHistoryContent).
    • :9-12, :20 — add using System.Linq; System.Threading; …Services.Remediation;.
    • :273-334ApplyButton_Click / RunApplyAsync: ResolveServer(_serverConnection.Id, ServerName), HasHandlerFor, ApplyAsync(item.Remediation, resolution.Server, item.CopyPasteSql, operatorIdentity, sourceRef, ConfirmAsync, ct, finding: null); refresh on run.
    • :342-352ConfirmAsync: Dispatcher.InvokeAsyncnew RemediationConfirmWindow(request, resolution.ResolvedByName, resolution.Reason) { Owner = Window.GetWindow(this) }.
    • :358-390FormatApplyStatus (compact status-strip summary).
    • :401-445MuteButton_Click / RunMuteAsync: _findingStore.MuteStoryAsync(serverId, item.StoryPathHash, item.StoryPath ?? hash, …); refresh on success.
    • :451-464ResolveOperatorIdentity (mirrors AlertDetailWindow).
  • Dashboard/Controls/RecommendationsContent.xaml:55-78 — Apply button enabled + Click="ApplyButton_Click" (removed IsEnabled="False" + ActionsDisabledReason tooltip); new Mute button bound to ShowMute + Click="MuteButton_Click".
  • Dashboard/Controls/RecommendationsViewModel.cs — refreshed ShowApply/ShowMute doc comments; removed now-dead ActionsDisabledReason property + ActionsDisabledTooltip const.
  • Dashboard/ServerTab.xaml.cs:35-39 — forwarding RemediationApplyService property → RecommendationsTab.
  • Dashboard/MainWindow.xaml.cs:659serverTab.RemediationApplyService = _remediationApplyService; (same instance as the Alerts tab).
  • Dashboard.Tests/RemediationTests.cs:1835-1836 — add ServerTab.xaml.cs + Controls/RecommendationsContent.xaml.cs to the sanctioned RemediationApplyService allowlist guard (GatedEntry_ReferencedOnlyBySanctionedUiPath).
  • Dashboard.Tests/RecommendationDeduperTests.cs:399, :423 — assert StoryPath maps (engine) / is null (legacy).
  • Dashboard.Tests/RecommendationsViewModelTests.cs — builder gains storyPath; replaced the obsolete disabled-tooltip test with EngineCard_ExposesMuteKeyInputs_HashAndPath + LegacyCard_HasNoMuteKeyInputs.

Test counts (real, this machine)

  • dotnet build PerformanceMonitor.sln -c Debug0 errors (1 pre-existing unrelated CS0649 warning in RemediationTests.cs).
  • dotnet test Dashboard.Tests --no-build315 passed, 0 failed, 0 skipped.
  • dotnet test Lite.Tests --no-build360 passed, 0 failed, 0 skipped (incl. the order-dependent Azure-auth test, which passed in-suite here).

Needs visual / integration verification (live DB + WPF)

The unit tests cover the VM enablement predicates (ShowApply/ShowMute) and the reader's StoryPath mapping. The runtime behaviors need the orchestrator to launch the Dashboard:

  • An Apply through the two-sided RCSI consent gate — confirm the acknowledge-each-risk checkboxes render with the persisted figures and Apply stays disabled until all are checked; confirm the config.remediation_action_log row is written and the recommendations refresh.
  • A non-destructive DB-config Apply (single confirm) end-to-end.
  • Mute an engine recommendation → row drops out after refresh; verify the config.analysis_muted row (serverId + story_path_hash + story_path).
  • Fail-closed paths: Apply on an unresolvable/ambiguous server surfaces the reason and runs nothing.

🤖 Generated with Claude Code

Wire the Recommendations tab's Apply and Mute buttons live (Dashboard-only),
replacing the WS1b-1 disabled "Available in the next update" affordances.

Apply mirrors the proven AlertDetailWindow path exactly: fail-closed server
resolution, the handler-for-fact gate, then RemediationApplyService.ApplyAsync
with a confirm callback that news up RemediationConfirmWindow. Destructive
fixes (RCSI / clear-plan) set RequiresInformedConsent, so the two-sided
acknowledge-each-risk gate renders automatically (no special-casing); the
disclosure draws on the persisted action's carried figures (finding: null).
The shared RemediationApplyService is threaded MainWindow -> ServerTab ->
RecommendationsContent, the same instance the Alerts tab uses.

Mute is engine-only: the lower-level story mute keyed on (serverId,
story_path_hash) via SqlServerFindingStore.MuteStoryAsync, since the card
holds a RecommendationItem rather than a full AnalysisFinding. Both Apply and
Mute refresh on success so the action-log outcome / dropped row is reflected.

RecommendationItem gains a StoryPath (populated from finding.StoryPath in the
reader) for the mute record's operator-facing label.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@erikdarlingdata erikdarlingdata merged commit 3cf5529 into dev Jun 5, 2026
2 checks passed
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