Repository navigation
get_analysis_facts runs the anomaly detector on both engines, so the facts read stops omitting the detector's ANOMALY_* facts (#3691) - #3714
Merged
Conversation
…, so the facts read shows the detector's ANOMALY_* facts (#3691)
54 of 55 tasks
erikdarlingdata
added a commit
that referenced
this pull request
Sep 19, 2026
…aly facts-read pin takes the measured verdict (1); two co-fire boost summaries stop claiming the fact carries 0
erikdarlingdata
added a commit
that referenced
this pull request
Sep 19, 2026
…the 2026-09-19 read and threshold_lineage flips to 1, so a validated bar stops calling itself a guess (#3691 lineage flip) (#3723) * PostgreSQL bars the fleet measured now say so: lineage comments and threshold_lineage flipped to measured (#3691 lineage flip) The 2026-09-19 calibration read 50 Aurora PostgreSQL clusters of the dogfood fleet (14 d counters, 7 d sessions/statements/waits). Every v1 bar it validated where it stood - temp 1/10 MiB/s, backlog 3/10x/5, deadlocks 5/20 (PG-side re-read), bad-actor busy floor 0.05 (+ shares measured-conditional), wait rollups 0.20/1.0 and io 0.40/2.0 (Aurora), CPU 80/95 (Aurora Serverless v2), sessions 0.8/0.9 (engine-defined ceiling, bands measured against it), TPS floor 50 / fallback 500, CPU floor 40 / fallback 95, deadlock floor 1/h, wait fallback 500 ms/s - carries a measured comment naming its percentile, population and date, and the facts graded only on measured or engine-defined bars stamp threshold_lineage = 1. Still unmeasured and still saying so: wait standout bars and sampling floor, session COUNT floors, the ratio families' firing multiple, every co-fire boost, checkpoint (not applicable on Aurora), buffer cache. No bar value changed - the diff touches no const declaration; the only code lines are the stamps and one bool the detector passes to its z-metadata helper. PgTargetMeasuredLineageTests pins each flipped constant's comment block to the date and population, pins the unmeasured ones to still say so, and pins the flag per file. * Rebase onto dev (#3714 get_analysis_facts anomaly read): its TPS-anomaly facts-read pin takes the measured verdict (1); two co-fire boost summaries stop claiming the fact carries 0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
get_analysis_factsnow runs the anomaly detector on both SKUs, so the facts read shows the detector'sANOMALY_*/ANOMALY_PG_*facts — scored, with the gate metadata they were scored on — instead of a fact set that silently omitted the one family whose metadata an operator most often wants to read. Twin edit:Lite/Analysis/AnalysisService.CollectAndScoreFactsAsyncandDarling/PerformanceMonitor.Darling.Analysis/DarlingAnalysisService.CollectAndScoreFactsAsync, same shape.PARTIAL for #3691. Checklist line completed:
get_analysis_factsnever runs the anomaly detector on EITHER engine (DarlingAnalysisService.CollectAndScoreFactsAsync= collector + scorer; Lite twin) — anomaly lineage/gate metadata is visible only in findings. Pre-existing; twin edit.The lie
The tool's description said "every observation the engine sees", and named
ANOMALY_*facts explicitly ("For ANOMALY_* facts the metadata carries baseline_confidence…"). The read behind it was collector + scorer only; the detectors ran in the full pass and nowhere else. So on both engines the facts read never contained an anomaly fact, and the gate metadata lane 9 and #3584 wrote (deviation_sigmaagainstfire_threshold,baseline_samples,baseline_tier,baseline_low_quality,threshold_lineage) was reachable only through a finding that had already crossed the severity floor. An anomaly that fired and scored under 0.5 was invisible everywhere; an operator asking "what did the detector see" got facts that did not include the detector's.PgTargetAnomalyTests' live e2e pinned the defect in so many words (Assert.Emptyon the anomaly-source facts read, with a comment telling the reader not to look there).The change
In
CollectAndScoreFactsAsync, after collection and before scoring, the same detector the pass would run is run over the sameAnalysisContext, and its facts are appended so they arrive scored with their metadata intact — collector → detector → scorer, the pass's order. On Darling the detector is the RESOLVED engine's (engine.Detector, off the oneResolveEngineAsyncthe read already performs): a PostgreSQL target getsPgTargetAnomalyDetector'sANOMALY_PG_*facts, a SQL Server target getsPgAnomalyDetector's. Nothing is constructed by hand. On Lite it is_anomalyDetector, the service's own.The detector is gated exactly as the pass gates it: it does not run over a window the collector never observed (
context.ObservedDurationMs > 0). A deviation is measured against the window's own rate and an unobserved window has none; running the detector there would hand the tools anomaly facts to render under an envelope whose prose says nothing windowed exists. So both tools' unobserved envelope keeps describing exactly the point-in-time facts it names, byte for byte. A collector that observed the window and emitted no fact does NOT short-circuit past the detector (the #3653 rule the pass follows): the empty-set early return now sits after the detector's say.The tool's
baseline_confidenceprojection (an anomaly-source fact'sconfidencekey renamed at read time, #3538 A6) now has something to project; it is unchanged.Cost honesty
The read now pays the detector's reads on top of the collector's — the same reads the full pass already pays for the same answer:
HasBaselineDataAsyncplus one window read and one baseline lookup per detector family. PostgreSQL target: 5 detector families. SQL Server target (either SKU): 9 detector families. The baseline lookups go through the per-server baseline cache the pass populates, so a facts read shortly after a pass mostly hits it. Noinclude_anomaliesflag existed and none is added: the description says this is the pass's fact set, so it is. This is an on-demand MCP path, not the alert path.audit_config's SQL Server arm also reads throughCollectAndScoreFactsAsync(1-hour window) to pick outCONFIG_*facts and therefore pays the detector too. Disclosed rather than fenced: fencing would mean a service-level switch for one incidental caller, and the config facts it reads are unaffected. The coordinator can ask for a fence if the measured cost warrants it.What it does NOT do
PgTarget*production file, detector or baseline provider; does not change what any detector emits or how the scorer grades it.AnalyzeAsync) — theAnalysisWindowEmptyRuleParityTestspins over it are untouched and green.deprecated/Dashboard(itsAnalysisService.CollectAndScoreFactsAsynchas the same shape); listed for the coordinator, not done here — outside this lane's declared boundary and the frozen twin is on bug-fix support only.Descriptions
Both SKUs'
get_analysis_factsdescriptions gain one shared sentence (before the pinnedbaseline_confidencedisambiguation): the detector runs on this read, which metadata the anomaly facts carry, that sub-floor fires are included, and what it costs. The sentence is added toMcpMissMessageParityPinTests.SharedMissFragmentsso it cannot drift between SKUs. Both instructions files: the tool row now reads "collect+detect+score … the anomaly detector's ANOMALY_* facts included", and theas_ofparagraph no longer reserves anomaly detection foranalyze_server— the anchor reaches both tools' detection now, which is also what the anchored-window prose already promised.Tests
New / changed — all compile-verified here; execution noted per item:
Darling/Darling.Tests/AnalysisFactsReadRunsDetectorParityTests.cs(NEW, 3 tests) — source pins over BOTH services'CollectAndScoreFactsAsyncbodies (theAnalysisWindowEmptyRuleParityTestspattern; no CI test project references both SKU assemblies): collector < witness gate < detector < append < empty-return < scorer; exactly one detector call and one scoring call; the gate is> 0(pass polarity) and no<= 0spelling; no detector constructed inside the read; Darling usesengine.DetectoroffResolveEngineAsync, Lite uses_anomalyDetector; both tool descriptions carry the new sentence on theget_analysis_factsattribute specifically, and both instructions files carry the new row text and have lost the old reservation. Executed on this Mac: 3/3 green; red-first proven by rebuilding against dev'sDarlingAnalysisService.cs(2/3 red, as designed — the description pin stays green because it reads the tool files).Darling/Darling.Tests/PgTargetAnomalyTests.cs(CHANGED, gated live e2e) — theAssert.Emptypin of the defect becomes the exit criterion: the lane-9 planted TPS spike →get_analysis_factsfiltered to the anomaly source returns exactly the detector's key set (ANOMALY_PG_TPS,_SESSION_SPIKE,_CPU_SPIKE,_DEADLOCK_RATE), and the TPS fact carries value 60.0, a positive severity,deviation_sigma= display cap,fire_threshold= the modified-z bar,baseline_samples> 0,threshold_lineage= 0,baseline_confidence= 1.0 and NO rawconfidencekey. Executed here against a throwawaytimescale/timescaledb:2.28.1-pg18(Docker contextdesktop-linux, torn down): 39/39 green; red-first:Assert.NotEmpty() Failure: Collection was emptyon dev's service. Also executed green on the same container:AnalysisCoverageLivePostgresTests,AnalysisAsOfAnchorTests,AnalyzeWindowEmptyLivePostgresTests,DarlingAnalysisPipelineTests,PgTargetFactCollectorTests,DarlingAnalysisServiceEngineRoutingTests,PgTargetMcpSurfaceTests,FactSourceRegistryTests,AnalysisPassCommandTimeoutTests,RepoFileAdoptionTests,DocCommentHygieneTests(77/77, run from inside the repo tree).Lite.Tests/AnomalyDetectorTests.cs(CHANGED, +2 tests) —CollectAndScoreFacts_RunsTheDetector_SoTheSpikeIsAScoredFactOnTheFactsRead: the existing batch-request spike fixture plus a wait_stats series for the coverage witness, driven through the realAnalysisService.CollectAndScoreFactsAsync(anchored by name on the class's_analysisEnd, so the baseline bucket is the seeded hour):ANOMALY_BATCH_REQUESTSis present, sourceanomaly, severity > 0, withdeviation_sigma/fire_threshold/baseline_samples/baseline_tier/baseline_low_quality/confidenceintact, and the collector's ownSOS_SCHEDULER_YIELDfact still beside it.CollectAndScoreFacts_DoesNotRunTheDetector_OverAnUnobservedWindow: same spike seeds without the witness → unobserved coverage, noANOMALY_*fact. First executed in CI —AnalysisServicelives in the WPF project and cannot load on this Mac. Right by construction: the seeds are the spike test's verbatim, the witness series is the 15-minute cadenceAnalysisCoverageTestsproves observed, and the scorer's deviation arm returns ≥ 0.5 × confidence at the fire threshold.Lite.Tests/McpMissMessageParityPinTests.cs(CHANGED) — the new shared description sentence joinsSharedMissFragments. Executed here in a net10.0 harness (the class reads source only): 61/61 green.Census pre-checks:
AnalysisWindowEmptyRuleParityTests(pass-body slice ends at the firstCollectAndScoreFactsAsync(— no new mention added inside the pass),DarlingAnalysisServiceEngineRoutingTests(still exactly 3await ResolveEngineAsync(; resolve precedes collect in the read),PgTargetMcpSurfaceTests(audit_config arm order untouched),FactSourceRegistryTests(source description untouched),McpPayloadContractCensusTests(total_facts = facts.Countuntouched),DocCommentHygieneTests,RepoFileAdoptionTests(new file uses the non-LF reader with single-line anchors — no roster entry owed). NoPgTarget*production file, detector, baseline provider,FactScorer,InferenceEngineor root partial touched.Which component(s) does this affect?
How was this tested?
Builds (zero new warnings):
PerformanceMonitor.Darling.Service,PerformanceMonitorLite(-p:EnableWindowsTargeting=true),Darling.Tests,Lite.Tests(one pre-existing xUnit2000 warning inLogTailOverlapThresholdPinTests, untouched),deprecated/Dashboard.Tests. Live PostgreSQL execution as above against PG18/TimescaleDB 2.28.1 in a throwaway container. Lite behavioural tests are CI-first.Checklist
dotnet build -c Debug)