From 5916fab68e84d2cd1f808f7bbea01db0fe1cf49a Mon Sep 17 00:00:00 2001
From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com>
Date: Fri, 18 Sep 2026 19:27:59 -0400
Subject: [PATCH] The automatic force-plan bot judged through the state-less
gate and could not see that automatic plan correction owned, had failed on,
or had withdrawn the plan: it now reads the store's forcing and APC state
once per pass, judges through the whole gate plus two blockers of its own,
fails closed on an unknown state, stands down where FORCE_LAST_GOOD_PLAN is
on, and journals every blocker with its evidence (#3654)
PlanForceBot called FactRemediation.ForcePlanBlockers(target) - the one-argument
overload #3652 kept for compatibility - so the automatic actor's gate was the two
#2138 blockers only, and on the five live targets #3652 cross-referenced it would
have judged all five unblocked. The bot now reads the pass's candidate targets'
ForcePlanTargetState in ONE batched statement through the store seam
(IPlanForceActionStore.TryGetTargetStatesAsync over DarlingForcePlanTargetStateReader,
under the bot's own PostAnalysisForcePlanSeconds deadline) and judges each through
ForcePlanBotPolicy.Blockers: the two-argument gate's list verbatim, then
apc_enabled_for_database (FLGP ON for the database - the engine is the forcer there)
and state_unavailable (read failed, no row for the key, or read ran and observed
nothing - not even the enablement row the collector writes for every database it
sees). The journal's reasons column carries the names; detail carries name: evidence
one per line, so a blocked row is auditable against the snapshot it was judged on.
The Lite.Tests gap pin flips to a closure pin; Lite has no bot.
---
.../Darling.Tests/ForcePlanBotPolicyTests.cs | 180 ++++++++++
Darling/Darling.Tests/PlanForceBotTests.cs | 316 +++++++++++++++++-
.../StragglerCommandTimeoutTests.cs | 8 +-
.../Mcp/DarlingForcePlanTargetStateReader.cs | 12 +-
.../PgPlanForceActionStore.cs | 60 +++-
.../PlanForceBot.cs | 155 ++++++---
Lite.Tests/ForcePlanTargetStateTests.cs | 43 ++-
.../FactRemediation.cs | 11 +-
.../ForcePlanBotPolicy.cs | 122 +++++++
9 files changed, 847 insertions(+), 60 deletions(-)
diff --git a/Darling/Darling.Tests/ForcePlanBotPolicyTests.cs b/Darling/Darling.Tests/ForcePlanBotPolicyTests.cs
index 12c566c0fc..7c6299dab6 100644
--- a/Darling/Darling.Tests/ForcePlanBotPolicyTests.cs
+++ b/Darling/Darling.Tests/ForcePlanBotPolicyTests.cs
@@ -7,6 +7,7 @@
*/
using System;
+using System.Linq;
using PerformanceMonitor.Analysis;
using Xunit;
@@ -198,6 +199,185 @@ public void BothGatesOpen_IsTheOnlyPathToForce()
Assert.Empty(decision.Reasons);
}
+ /* ---------------- the bot's whole blocker list (#3654) ---------------- */
+
+ private static readonly DateTime Observed = new(2026, 9, 18, 12, 0, 0, DateTimeKind.Utc);
+
+ private static ForcePlanTargetState Clean(string? flgp = "OFF", DateTime? enablementAt = null) => new(
+ PlanIsForced: null, PlanForcingType: null, ForceFailureCount: null, LastForceFailureReason: null,
+ PlanObservedAtUtc: null,
+ OtherForcedPlanId: null, OtherForcedPlanForcingType: null, OtherForcedPlanObservedAtUtc: null,
+ ApcState: null, ApcStateReason: null, ApcRegressedPlanId: null, ApcLastGoodPlanId: null,
+ ApcLastGoodPlanForcingType: null, ApcLastGoodPlanIsForced: null, ApcLastGoodPlanForceFailureReason: null,
+ ApcExecuteActionInitiatedBy: null, ApcObservedAtUtc: null,
+ ForceLastGoodPlanActualState: flgp, EnablementObservedAtUtc: enablementAt ?? (flgp is null ? null : Observed));
+
+ [Fact]
+ public void Blockers_IsTheWholeSharedGate_PlusNothing_WhenTheStateIsCleanAndObserved()
+ {
+ /* The happy path: state read, FLGP OFF, nothing forced, no recommendation. The bot's list is
+ exactly the two-argument gate's (empty here) — no bot-only blocker fires on a clean read. */
+ var blockers = ForcePlanBotPolicy.Blockers(Target(), Clean(), stateUnavailableReason: null);
+
+ Assert.Empty(blockers);
+ Assert.Empty(ForcePlanBotPolicy.Names(blockers));
+ Assert.Null(ForcePlanBotPolicy.Evidence(blockers));
+ }
+
+ [Fact]
+ public void Blockers_CarriesTheSharedGatesVerdict_Verbatim()
+ {
+ /* Same function, same names, same evidence strings as structured_remediation — the bot never
+ recomputes the gate (#2146). APC AUTO-forced on the target plan, plus the target's own PSP flag. */
+ var state = Clean() with
+ {
+ PlanIsForced = true, PlanForcingType = "AUTO", ForceFailureCount = 0, PlanObservedAtUtc = Observed,
+ ApcState = "Verifying", ApcLastGoodPlanId = 7, ApcObservedAtUtc = Observed,
+ };
+ var target = Target(psp: true);
+
+ var shared = FactRemediation.ForcePlanBlockers(target, state);
+ var bot = ForcePlanBotPolicy.Blockers(target, state, stateUnavailableReason: null);
+
+ Assert.Equal(shared, bot);
+ Assert.Equal(new[] { "parameter_sensitivity_cofired", "apc_owns_it" }, ForcePlanBotPolicy.Names(bot));
+ }
+
+ [Fact]
+ public void Blockers_FlgpOn_AddsTheStandDown_AfterTheSharedGate()
+ {
+ var state = Clean(flgp: "ON") with
+ {
+ PlanIsForced = true, PlanForcingType = "AUTO", PlanObservedAtUtc = Observed,
+ };
+
+ var blockers = ForcePlanBotPolicy.Blockers(Target(), state, stateUnavailableReason: null);
+
+ Assert.Equal(new[] { "apc_owns_it", ForcePlanBotPolicy.ReasonApcEnabledForDatabase }, ForcePlanBotPolicy.Names(blockers));
+ var standDown = blockers[1];
+ Assert.Contains("force_last_good_plan_actual_state = ON for orders at 2026-09-18T12:00:00Z", standDown.Evidence, StringComparison.Ordinal);
+ Assert.Contains("second forcer", standDown.Evidence, StringComparison.Ordinal);
+ }
+
+ [Theory]
+ [InlineData("ON")]
+ [InlineData("on")]
+ [InlineData("On")]
+ public void Blockers_FlgpOn_IsCaseInsensitive_OnTheStoredSpelling(string spelling)
+ {
+ var blockers = ForcePlanBotPolicy.Blockers(Target(), Clean(flgp: spelling), stateUnavailableReason: null);
+
+ Assert.Equal(new[] { ForcePlanBotPolicy.ReasonApcEnabledForDatabase }, ForcePlanBotPolicy.Names(blockers));
+ }
+
+ [Theory]
+ [InlineData("OFF")]
+ [InlineData("off")]
+ public void Blockers_FlgpOff_FiresNothing(string spelling)
+ {
+ Assert.Empty(ForcePlanBotPolicy.Blockers(Target(), Clean(flgp: spelling), stateUnavailableReason: null));
+ }
+
+ [Fact]
+ public void Blockers_NullState_IsUnavailable_QuotingTheReadersReason()
+ {
+ var blockers = ForcePlanBotPolicy.Blockers(Target(), state: null, stateUnavailableReason: "the read failed (NpgsqlException: timeout)");
+
+ var only = Assert.Single(blockers);
+ Assert.Equal(ForcePlanBotPolicy.ReasonStateUnavailable, only.Name);
+ Assert.StartsWith("the read failed (NpgsqlException: timeout) — an unattended force cannot proceed", only.Evidence, StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public void Blockers_NullState_WithNoReason_IsUnavailable_NamingTheMissingKey()
+ {
+ var blockers = ForcePlanBotPolicy.Blockers(Target(), state: null, stateUnavailableReason: null);
+
+ var only = Assert.Single(blockers);
+ Assert.Equal(ForcePlanBotPolicy.ReasonStateUnavailable, only.Name);
+ Assert.Contains("returned no row for plan 7 of query 42 in orders", only.Evidence, StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public void Blockers_NullState_StillCarriesTheTargetHalf_First()
+ {
+ /* Unknown state does not erase what the target itself says: PSP is named, then the
+ unavailability. */
+ var blockers = ForcePlanBotPolicy.Blockers(Target(psp: true), state: null, stateUnavailableReason: "boom");
+
+ Assert.Equal(new[] { "parameter_sensitivity_cofired", ForcePlanBotPolicy.ReasonStateUnavailable }, ForcePlanBotPolicy.Names(blockers));
+ }
+
+ [Fact]
+ public void Blockers_EmptyState_IsUnavailable_BecauseTheEnablementHalfIsTheCollectorsEveryDatabaseRow()
+ {
+ var empty = Clean(flgp: null);
+ Assert.True(empty.IsEmpty);
+
+ var blockers = ForcePlanBotPolicy.Blockers(Target(), empty, stateUnavailableReason: null);
+
+ var only = Assert.Single(blockers);
+ Assert.Equal(ForcePlanBotPolicy.ReasonStateUnavailable, only.Name);
+ Assert.Contains("observed nothing for this target inside the last 24 hours", only.Evidence, StringComparison.Ordinal);
+ Assert.Contains("no plan_correction capture for orders at all", only.Evidence, StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public void Blockers_APlanNotObservedButADatabaseThatIs_IsNotUnavailable()
+ {
+ /* The ordinary shape for a best plan that is not executing: no query_store_stats row for it, no
+ recommendation — but the enablement row is there (OFF), so the store CAN see the database and
+ the bot proceeds. Null on a half means not observed, not unavailable. */
+ var state = Clean(flgp: "OFF");
+ Assert.False(state.IsEmpty);
+
+ Assert.Empty(ForcePlanBotPolicy.Blockers(Target(), state, stateUnavailableReason: null));
+ }
+
+ [Fact]
+ public void Evidence_IsOneLinePerBlocker_NameColonEvidence()
+ {
+ var blockers = ForcePlanBotPolicy.Blockers(Target(psp: true), Clean(flgp: "ON"), stateUnavailableReason: null);
+
+ var detail = ForcePlanBotPolicy.Evidence(blockers);
+ Assert.NotNull(detail);
+ var lines = detail!.Split('\n');
+ Assert.Equal(2, lines.Length);
+ Assert.StartsWith("parameter_sensitivity_cofired: ", lines[0], StringComparison.Ordinal);
+ Assert.StartsWith("apc_enabled_for_database: ", lines[1], StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public void TheBotsOwnReasons_AreNotInTheSharedVocabulary()
+ {
+ /* They belong to the actor, not the advice: an agent reading structured_remediation must never
+ see state_unavailable or apc_enabled_for_database as a blocker, because for a reader neither
+ IS one. Pinned as strings because both are consumer API. */
+ Assert.Equal("state_unavailable", ForcePlanBotPolicy.ReasonStateUnavailable);
+ Assert.Equal("apc_enabled_for_database", ForcePlanBotPolicy.ReasonApcEnabledForDatabase);
+
+ var shared = typeof(ForcePlanBlockerNames).GetFields()
+ .Select(f => (string)f.GetValue(null)!)
+ .ToArray();
+ Assert.DoesNotContain(ForcePlanBotPolicy.ReasonStateUnavailable, shared);
+ Assert.DoesNotContain(ForcePlanBotPolicy.ReasonApcEnabledForDatabase, shared);
+ }
+
+ [Fact]
+ public void Evaluate_WithTheBotsBlockers_IsBlocked_EvenWithEveryGateOpen()
+ {
+ /* End to end through the decision table: the state names apc_enabled_for_database, and no
+ combination of gates turns that into WouldForce or Force. */
+ var blockers = ForcePlanBotPolicy.Blockers(Target(), Clean(flgp: "ON"), stateUnavailableReason: null);
+
+ var decision = ForcePlanBotPolicy.Evaluate(
+ Target(), ForcePlanBotPolicy.Names(blockers), serverOptedIn: true, Enabled(dryRun: false),
+ ForcePlanBotHistory.Empty, Now);
+
+ Assert.Equal(ForcePlanBotDecisionKind.Blocked, decision.Kind);
+ Assert.Equal(new[] { ForcePlanBotPolicy.ReasonApcEnabledForDatabase }, decision.Reasons);
+ }
+
/* ---------------- settings hygiene ---------------- */
[Fact]
diff --git a/Darling/Darling.Tests/PlanForceBotTests.cs b/Darling/Darling.Tests/PlanForceBotTests.cs
index 8421ef40b5..6c8261c982 100644
--- a/Darling/Darling.Tests/PlanForceBotTests.cs
+++ b/Darling/Darling.Tests/PlanForceBotTests.cs
@@ -30,6 +30,11 @@ namespace Darling.Tests;
/// takes none, and PlanForceNoWritePathTests pins that the build ships no implementation of
/// the seam and no force/unforce/evict statement anywhere in the service assembly. The write path's
/// own orchestration tests arrive with it (#2731).
+///
+/// #3654 added the gate's STATE half to the bot: the fake store answers the batched
+/// forcing/APC state read too, so the five #3652 blockers, the unavailable-state arm and the FLGP
+/// stand-down are driven end to end through the orchestrator and pinned on what it JOURNALS — names in
+/// reasons, evidence in detail.
///
public sealed class PlanForceBotTests
{
@@ -43,6 +48,16 @@ private sealed class FakeStore : IPlanForceActionStore
public ForcePlanBotHistory History { get; set; } = ForcePlanBotHistory.Empty;
private long _nextId = 1;
+ /* The state half (#3654). Default: the read succeeds and every target it is asked about comes
+ back CLEAN and observed (FLGP OFF, nothing forced, no recommendation) — the shape a bot on a
+ healthy, APC-off server sees — so the pre-#3654 tests keep their meaning. A test overrides
+ per key, or fails the read, or returns an empty dictionary to simulate a missing key. */
+ public Dictionary States { get; } = new(ForcePlanTargetKey.Comparer);
+ public string? StateReadFailure { get; set; }
+ public bool ReturnOnlyExplicitStates { get; set; }
+ public int StateReads { get; private set; }
+ public List> StateReadTargets { get; } = new();
+
public Task JournalAsync(PlanForceActionRecord record, CancellationToken ct)
{
var id = _nextId++;
@@ -53,8 +68,97 @@ public Task JournalAsync(PlanForceActionRecord record, CancellationToken c
public Task GetQueryHistoryAsync(
int serverId, string database, long queryId, ForcePlanBotSettings settings, DateTime nowUtc, CancellationToken ct) =>
Task.FromResult(History);
+
+ public Task<(IReadOnlyDictionary? States, string? UnavailableReason)> TryGetTargetStatesAsync(
+ int serverId, IReadOnlyList targets, DateTime nowUtc, CancellationToken ct)
+ {
+ StateReads++;
+ StateReadTargets.Add(targets);
+
+ if (StateReadFailure is not null)
+ {
+ return Task.FromResult<(IReadOnlyDictionary?, string?)>((null, StateReadFailure));
+ }
+
+ var result = new Dictionary(ForcePlanTargetKey.Comparer);
+ foreach (var t in targets)
+ {
+ var key = ForcePlanTargetKey.Of(t);
+ if (States.TryGetValue(key, out var s))
+ {
+ result[key] = s;
+ }
+ else if (!ReturnOnlyExplicitStates)
+ {
+ result[key] = Clean();
+ }
+ }
+
+ return Task.FromResult<(IReadOnlyDictionary?, string?)>((result, null));
+ }
}
+ /* ---------------- state shapes ---------------- */
+
+ private static readonly DateTime Observed = new(2026, 9, 18, 12, 0, 0, DateTimeKind.Utc);
+
+ /// Read, observed, nothing in the way: FLGP OFF for the database, no forced plan, no
+ /// recommendation. The one shape that lets the bot through.
+ private static ForcePlanTargetState Clean(string flgp = "OFF") => new(
+ PlanIsForced: null, PlanForcingType: null, ForceFailureCount: null, LastForceFailureReason: null,
+ PlanObservedAtUtc: null,
+ OtherForcedPlanId: null, OtherForcedPlanForcingType: null, OtherForcedPlanObservedAtUtc: null,
+ ApcState: null, ApcStateReason: null, ApcRegressedPlanId: null, ApcLastGoodPlanId: null,
+ ApcLastGoodPlanForcingType: null, ApcLastGoodPlanIsForced: null, ApcLastGoodPlanForceFailureReason: null,
+ ApcExecuteActionInitiatedBy: null, ApcObservedAtUtc: null,
+ ForceLastGoodPlanActualState: flgp, EnablementObservedAtUtc: Observed);
+
+ /// Read ran, saw nothing at all — not even the enablement row.
+ private static ForcePlanTargetState Empty() => Clean() with
+ {
+ ForceLastGoodPlanActualState = null,
+ EnablementObservedAtUtc = null,
+ };
+
+ /* The five live #3652 shapes, against the test target (plan 7 of query 42 in orders). */
+
+ /// APC mid-verification on exactly the proposed plan: AUTO-forced on plan 7.
+ private static ForcePlanTargetState ApcOwnsIt() => Clean() with
+ {
+ PlanIsForced = true, PlanForcingType = "AUTO", ForceFailureCount = 0, LastForceFailureReason = "NONE",
+ PlanObservedAtUtc = Observed,
+ ApcState = "Verifying", ApcRegressedPlanId = 9, ApcLastGoodPlanId = 7,
+ ApcLastGoodPlanForcingType = "AUTO", ApcLastGoodPlanIsForced = true, ApcObservedAtUtc = Observed,
+ };
+
+ /// An operator already forced plan 7 by hand.
+ private static ForcePlanTargetState AlreadyForced() => Clean() with
+ {
+ PlanIsForced = true, PlanForcingType = "MANUAL", ForceFailureCount = 0, LastForceFailureReason = "NONE",
+ PlanObservedAtUtc = Observed,
+ };
+
+ /// Forcing plan 7 is already failing on this server (the Forced Plan Failing counter).
+ private static ForcePlanTargetState ForcingFailed() => Clean() with
+ {
+ PlanIsForced = true, PlanForcingType = "MANUAL", ForceFailureCount = 12,
+ LastForceFailureReason = "NO_INDEX", PlanObservedAtUtc = Observed,
+ };
+
+ /// The engine recommended plan 7 and withdrew it: Expired / TempTableChanged.
+ private static ForcePlanTargetState ApcWithdrewIt() => Clean() with
+ {
+ ApcState = "Expired", ApcStateReason = "TempTableChanged", ApcRegressedPlanId = 9, ApcLastGoodPlanId = 7,
+ ApcObservedAtUtc = Observed,
+ };
+
+ /// The engine already resolved the query's regression through plan 5, not 7.
+ private static ForcePlanTargetState ApcResolvedDifferently() => Clean() with
+ {
+ ApcState = "Success", ApcStateReason = "LastGoodPlanForced", ApcRegressedPlanId = 9, ApcLastGoodPlanId = 5,
+ ApcLastGoodPlanForcingType = "AUTO", ApcLastGoodPlanIsForced = true, ApcObservedAtUtc = Observed,
+ };
+
/* ---------------- scaffolding ---------------- */
private static ServerRuntime Runtime(string engine = "sqlserver") => new()
@@ -180,6 +284,212 @@ public async Task TargetsAreDedupedAcrossFindings_AndCappedPerPass()
Assert.Equal(store.Journaled.Count, store.Journaled.Select(r => r.QueryId).Distinct().Count());
}
+ /* ---------------- the state half (#3654) ---------------- */
+
+ [Fact]
+ public async Task TheStateIsReadOnce_ForTheWholePass_BeforeAnyTargetIsJudged()
+ {
+ /* One batched statement per pass, sized by the same cap that bounds the history reads — not
+ one per target. Fourteen distinct queries, cap ten: the read is asked about exactly the ten
+ the pass will judge, once. */
+ var (bot, store) = Build(Enabled());
+ var finding = Finding(Enumerable.Range(1, 14).Select(q => Target(queryId: q)).ToArray());
+
+ await bot.RunAfterAnalysisAsync(Runtime(), Config(), new[] { finding }, CancellationToken.None);
+
+ Assert.Equal(1, store.StateReads);
+ var asked = Assert.Single(store.StateReadTargets);
+ Assert.Equal(PlanForceBot.MaxTargetsPerPass, asked.Count);
+ Assert.Equal(Enumerable.Range(1, 10).Select(q => (long)q), asked.Select(t => t.QueryId));
+ }
+
+ [Fact]
+ public async Task NoCandidates_MeansNoStateRead()
+ {
+ /* A pass with no PLAN_REGRESSION targets must not spend the store round trip. */
+ var (bot, store) = Build(Enabled());
+ var other = new AnalysisFinding { ServerId = ServerId, Remediation = new RemediationAction("BLOCKING", "none", Array.Empty()) };
+
+ await bot.RunAfterAnalysisAsync(Runtime(), Config(), new[] { other }, CancellationToken.None);
+
+ Assert.Equal(0, store.StateReads);
+ Assert.Empty(store.Journaled);
+ }
+
+ public static IEnumerable