Skip to content

Daily-tier reads now stitch in the interval-honest successor daily (#3653 A6) - #4184

Merged
erikdarlingdata merged 9 commits into
devfrom
fix/3653-a6-daily-stitch
Sep 25, 2026
Merged

erikdarlingdata merged 9 commits into
devfrom
fix/3653-a6-daily-stitch

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Part of #3653 (A6, lane LA-8).

Why

#4181 (lane LB) shipped the real daily-pair registry (TimescaleSupport.SupersededDailyRollups) and the
RollupAvailability flags for the three successor dailies, plus the three interval-honest successor daily
aggregates themselves. #4182 (lane LA) routed every hourly-tier reader through the stitch builder. But the
daily branch of that builder read a private placeholder (s_supersededDailyRollupsStub) that no reader called.
Once the successor dailies are backfilled, nothing would actually read them. Every daily-tier reader would keep
reading the frozen legacy daily forever. This lane (LA-8) wires the daily side up the same way the hourly side
already works.

What changes

Step 1, registry. Deleted s_supersededDailyRollupsStub. RollupCoverage.StitchedRelationSql and
StitchFloor's daily branches now read the real TimescaleSupport.SupersededDailyRollups. They also gate on
_availability.Has(successorDaily) in addition to the floor, the same way the hourly branch already does. An
absent successor daily is never named in SQL, even if a stale floor happens to be cached for it.

Step 2, route the daily FROM-clause sites. One commit per file:

  • ComposeSourceRouter.cs: the daily arm of ResolveFamily now sets CaggFromClause via
    StitchedRelationSql(dailyView, FactAlias, windowStartUtc, StitchTier.Daily), mirroring the hourly arm.
    CaggRelation (the by-name answer) is unchanged.

  • ViewerDataService.FinOps.Workload.cs: the three coverage-aware overloads
    (DatabaseResourceUsageSqlFor, TopResourceConsumersByTotalSqlFor, TopResourceConsumersByAvgSqlFor) had a
    ternary that stitched the hourly branch but spliced the daily rollup by bare name
    ($"collect.{...DailyView} AS f"). The daily branch now calls StitchedRelationSql(..., StitchTier.Daily)
    too.

  • DailySummarySql.cs: RangeSqlFor(tier, coverage, windowStartUtc) only ever stitched the hourly tier.
    It now handles both tiers. StitchFloor is asked on the tier's own legacy/StitchTier pair, and a genuine
    stitch runs QueriesCteForStitchedCagg's not-carried probe twice, split at F_d, with each side's own
    MaterializationHoleTargets source (the successor daily's source is the successor hourly, already wired by
    RollupViews/issue-3653 A6 lane LB: the 3 interval-honest successor dailies + backfill runbook #4181, so no registry change was needed here).

    Extending this avoided a trap. The single-relation branch (legacy-only or successor-only) used to call the
    two-argument RangeSqlFor(tier, relation) overload, whose own daily branch always names the legacy daily.
    Sending the daily tier down that path would have turned a successor-only daily answer back into a legacy
    read. On dev the daily tier never took that branch, so no shipped code had the bug. The branch now builds
    the single-relation SQL itself, with the name the stitch builder chose, for both tiers.

Everything the design calls "name-only" was checked and left alone: DarlingHealthReader.cs,
DarlingMcpTrendTools.cs, DarlingTrendReader.cs, ViewerDataService.QueryTrends.cs, and the remaining
ViewerDataService.FinOps.Workload.cs/DailySummary.cs call sites all pass a daily view name only into
coverage.For(...) for the tier CHOICE, never into a FROM clause.

The trend/query-history family (DarlingTrendReader, DarlingMcpTrendTools, ViewerDataService.QueryTrends.cs)
is stronger than "not yet routed". DurationTrendRouting.ResolveTier can only return Raw or Hourly, never
Daily. Those readers have no daily FROM splice to route: the daily view name is architecturally unreachable as
a FROM target there. Full grep census and disposition:

Site Disposition
ComposeSourceRouter.cs:341 (dailyView!) Routed (step 2)
ViewerDataService.FinOps.Workload.cs:139/161/183 Routed (step 2)
DailySummarySql.cs RangeSqlFor(tier, coverage, windowStart) Routed (step 2)
DarlingHealthReader.cs:420 Name-only (tier choice). FROM splice routed indirectly via DailySummarySql.RangeSqlFor above.
ViewerDataService.DailySummary.cs:83 Name-only (tier choice). FROM splice routed indirectly, same as above.
ViewerDataService.FinOps.Workload.cs:256/417/485 Name-only (tier choice). FROM splice routed indirectly via the step-2 overloads above.
DarlingTrendReader.cs:913/921 Name-only. Daily tier unreachable (ResolveTier never returns it).
DarlingMcpTrendTools.cs:383 Name-only. Same reason.
ViewerDataService.QueryTrends.cs:328/337/508 Name-only. Same reason.

The tier CHOICE stays on the legacy pair everywhere (coverage.For(legacyHourly, legacyDaily)). Only the
relation spliced within the chosen tier can be a stitch.

Step 3, scan test. Added NoReaderOutsideTheBuilder_SplicesADailyViewNameBySubstitution to
RollupCoverageRoutingTests. It is a source scan, with the same carve-out rule as the existing
NoReaderOutsideTheBuilder_CallsHourlyRelationForDirectly, that fails on a literal collect.{...DailyView}
string interpolation outside TimescaleSupport.cs. That is the exact shape the three FinOps splices and
ComposeSourceRouter's daily arm were in before this lane. Proved red once by reverting
ViewerDataService.FinOps.Workload.cs:139 to its old bare splice: the scan caught it at the exact line, then
the revert was undone.

Test plan

  • StitchedRelationSqlTests: 12/12 green (registry + gating, step 1)
  • ComposeSourceRouterTests + DarlingComposeTests: 306/306 green (step 2, ComposeSourceRouter.cs)
  • ViewerFinOpsTests + RetentionTierRouterTests: 31/31 green (step 2, FinOps.Workload.cs)
  • DailySummaryStitchedRangeTests: 8/8 green, plus DailySummaryNotCarriedTests + DailySummaryReadShapeTests
    + IntervalHonestHourlyRollupTests: 14/14 green (step 2, DailySummarySql.cs)
  • RollupCoverageRoutingTests: 41/41 green, new scan proved red once by reverting one site (step 3)
  • Full Darling.Tests suite, once, DARLING_TEST_PG unset. Result: 13738 total, 0 Errors, 0 Failed, 686
    Skipped (live tests that need DARLING_TEST_PG), 1 Not Run.
    No [FAIL] lines. The coordinator's own
    base run on dev (la8-base-suite.log, informational, not a formal base-failures.txt) likewise showed
    zero [FAIL] lines, so there was nothing to diff against. The "1 Not Run" is not a failure. The dev base
    run had the same 1 Not Run. I did not chase its identity given the deadline. It is worth a look if it
    recurs.
  • Live proofs (lane LA-8b2): a real TimescaleDB store, seeded over six fixed-anchor days, proving the
    FinOps, Compose and daily-summary reads all split cleanly at F_d with no gap and no overlap, plus a
    not-carried hole and all six stitch pairs. No trend/query-history read is included.
    DurationTrendRouting.ResolveTier only ever returns Raw or Hourly, so no trend reader can reach a
    daily FROM clause to prove. Numbers below.

CHANGELOG entry

Section: Changed

  • Daily-tier reads now stitch in the interval-honest successor daily ([Brains-review campaign: deferred structural residue (from #3538 / #3539 / #3540 / #3541) #3653]). Every daily-tier read
    (FinOps database and top-consumer panels, the Compose chart builder, and the Performance Calendar / daily
    summary) used to read only the legacy daily rollup. Once the store's successor dailies are backfilled, these
    reads use the legacy daily for days before the successor's floor. They use the interval-honest successor
    daily from that floor on, with no gap and no overlap. A store without the successor dailies, or with empty
    ones, is unaffected: the read is byte-identical to before.

Live proofs (lane LA-8b2)

The new file Darling/Darling.Tests/DailyStitchLiveTests.cs holds two live tests. Each runs on its own scratch TimescaleDB store (#1776 own-store). Both passed with DARLING_TEST_PG set. The main test calls stop_background_workers() before the ensure sweep and cleans up through LiveStoreCleanup.RunAsync. The six-pairs test only reads with LIMIT 0, and the scratch store's own disposal drops its database.

Seed

The main test seeds one server and one database over six fixed UTC days, from D0 = 2026-01-05 to D5 = 2026-01-10. It never reads the wall clock. Each day gets three query_stats rows:

  • A6DAILYQ1: 1,000 worker units and 10 executions, sample_interval_seconds = 3600.
  • A6DAILYQ2: 2,000 worker units and 20 executions, sample_interval_seconds = 3600.
  • A6DAILYRESTART: 7,000 worker units and 1 execution, sample_interval_seconds = 0. This is a restart row. Every successor's WHERE sample_interval_seconds IS DISTINCT FROM 0 leaves it out, and no other row has its query hash.

So each day's legacy total is 10,000 units, 31 executions and 3 distinct hashes. Each day's successor total is 3,000 units, 30 executions and 2 distinct hashes.

Refresh and F_d

  • The legacy hourly and daily rollups (query_stats_* and query_stats_db_*) are refreshed for all six days.
  • The successor hourlies are refreshed from D3.
  • The database-grain successor daily, which FinOps reads, is refreshed for D3 to D5.
  • The query-grain successor daily, which Compose and the daily summary read, is refreshed for D3 and D5 only. D4 is left empty on purpose. Because D5 is materialized, D4 counts as a day the rollup did not carry, not a day it has not reached yet.

coverage.StitchFloor(QueryStatsDailyView, StitchTier.Daily, D0) returned exactly D3, inside the [D0, D6) window.

FinOps

DatabaseResourceUsageSqlFor(RetentionTier.Daily, coverage, D0) reads the database grain, which has no empty day. It returned cpu_time_ms = 39.0 and execution_count = 183. That is the legacy for D0 to D2 (3 x 10,000 units) plus the successor for D3 to D5 (3 x 3,000 units). An all-legacy read gives 60.0 ms and 186 executions, and an all-successor read gives 18.0 ms and 180.

Compose

A query_worker_us panel with a daily bucket went through ComposeSourceRouter and ComposeCompiler. The compiled SQL contains UNION ALL and names both query_stats_daily and query_stats_interval_daily. Five points came back:

  • D0 to D2 each equal D0's total, the legacy value.
  • D3 and D5 each equal 0.3 times D0's total, the successor value (3,000 / 10,000).
  • D4 is missing. The stitched relation has no row for it, and Compose has no not-carried marker of its own.

Daily summary

DailySummarySql.RangeSqlFor(RetentionTier.Daily, coverage, D0) returned six rows, one per day, with no gap and no overlap. unique_queries was 3, 3, 3, 2, NULL, 2 for D0 to D5. D4 is NULL because the successor side's not-carried probe reads the successor hourly. That hourly holds rows for D4, but the successor daily does not.

Contrast proof

The test probes the same store again with the product's TimescaleSupport.DetectRollupCoverageAsync, this time with RollupAvailability.WithoutIntervalDailies, the shape before this PR. StitchFloor then returns null, and the whole window reads the legacy. D3 comes back as 3 instead of 2, and D4 as 3 instead of NULL. The stitched assertions above fail on these numbers. The test keeps both reads side by side as permanent assertions, so it needs no temporary revert.

All six stitch pairs

The second test builds a RollupCoverage by hand, with fixed floors and RollupAvailability.All. Those floors force each of the three hourly pairs and the three daily pairs into the UNION ALL form. The test checks that the SQL contains UNION ALL, then runs SELECT * FROM <StitchedRelationSql(...)> LIMIT 0 on a fresh store. It succeeded for all six pairs, so each s_stitchColumnsByLegacy list exists on both sides.

Totals

  • DailyStitchLiveTests: 2 of 2 passed.
  • With the other ten classes (StitchedRelationSqlTests, DailySummaryStitchedRangeTests, ViewerFinOpsTests, RetentionTierRouterTests, ComposeSourceRouterTests, DarlingComposeTests, IntervalHonestHourlyRollupTests, IntervalHonestHourlyRollupLiveTests, SuccessorDailyLiveTests, DailySummaryNotCarriedTests, DailySummaryReadShapeTests): 375 of 375 passed, 0 skipped.
  • After merging lane LA-8b1's fixes (RollupCoverageRoutingTests.cs, DailySummarySql.cs and ViewerDataService.FinOps.Workload.cs), a rerun of 51 tests in DailyStitchLiveTests, RollupCoverageRoutingTests, ViewerFinOpsTests and DailySummaryStitchedRangeTests passed.
  • The build shows 0 warnings in DailyStitchLiveTests.cs. The full suite was not run here, because CI runs it.

LA-8b1's report on those three files is a separate PR comment. The merge had no conflicts, and the PostgreSQL rig on port 55981 was stopped after the run.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3

erikdarlingdata and others added 5 commits September 24, 2026 20:28
…, gated on availability

StitchedRelationSql and StitchFloor's daily branches used LA's placeholder
s_supersededDailyRollupsStub (name lookup only) instead of LB's real
TimescaleSupport.SupersededDailyRollups (#4181), and never checked
RollupAvailability.Has() for the successor daily the way the hourly branch
already does. Delete the stub and route both methods through the real
registry, gating on Has() as well as the floor so an absent successor daily
is never named in SQL even when a stale floor happens to be cached for it.

Add pure tests for the three daily states: absent from availability (even
with both successor floors cached) gives legacy-only text byte-identical to
today; present but empty gives legacy-only; present with a floor already
gives the F_d stitch (existing test). All 12 StitchedRelationSqlTests green.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
…m through the stitch builder

The daily tier's ResolveFamily arm only ever set ComposeRoute.CaggRelation
(the by-name answer); CaggFromClause stayed null, so ComposeCompiler always
fell back to "collect.<dailyView> AS f" even once a successor daily exists.
Set CaggFromClause the same way the hourly arm already does, via
RollupCoverage.StitchedRelationSql(dailyView, FactAlias, windowStartUtc,
StitchTier.Daily). The tier CHOICE is untouched (still coverage.For on the
legacy pair); only the relation within the chosen daily tier can stitch.
With no successor daily this is byte-identical to today: 306 Compose tests
green (ComposeSourceRouterTests, DarlingComposeTests).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
…s through the stitch builder

DatabaseResourceUsageSqlFor/TopResourceConsumersByTotalSqlFor/
TopResourceConsumersByAvgSqlFor's coverage-aware overloads stitched the
hourly branch but spliced the daily rollup by bare name
("collect.<dailyView> AS f"). Route the daily branch through
RollupCoverage.StitchedRelationSql(..., StitchTier.Daily) the same way,
so a successor daily gets read once LB backfills it. With no successor
daily this is byte-identical to today: 31 tests green (ViewerFinOpsTests,
RetentionTierRouterTests).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
…he daily tier at F_d

DailySummarySql.RangeSqlFor(tier, coverage, windowStartUtc) only ever
stitched the hourly tier; the daily tier fell through to the plain,
unstitched form even once a successor daily exists. Extend it to both
tiers: StitchFloor is asked on the tier's own legacy/StitchTier pair, and
a genuine stitch runs QueriesCteForStitchedCagg's not-carried probe twice,
split at F_d, with each side's own MaterializationHoleTargets source (the
successor daily's source is the successor hourly, per RollupViews/#4181).

Also fixes a latent bug the extension surfaced: the legacy-only/
successor-only branch used to redirect through the two-argument
RangeSqlFor(tier, relation) overload, whose OWN daily branch always names
the legacy regardless of what is passed — so a "successor already covers
the window" daily answer would have been silently downgraded back to the
legacy. Route through QueriesCteForCagg directly with the name the builder
actually chose, for both tiers.

Replace the stale DailyTier_IgnoresTheStitch pin (no longer true) with five
daily-tier tests mirroring the hourly ones: absent from availability,
empty, successor-only, and the F_d stitch itself. All 8
DailySummaryStitchedRangeTests green, 14 more in the surrounding classes
unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
…y-view splice

Add NoReaderOutsideTheBuilder_SplicesADailyViewNameBySubstitution: a source
scan (same carve-out rule as NoReaderOutsideTheBuilder_CallsHourlyRelationForDirectly)
that fails on a literal collect.{...DailyView} interpolation outside
TimescaleSupport.cs — the exact shape the three FinOps splices and
ComposeSourceRouter's daily arm were in before this lane routed them, and
the shape that can never grow a stitch once a successor daily exists.

Proved red by reverting ViewerDataService.FinOps.Workload.cs:139 to its old
bare splice: the scan caught it at the exact line, then restored. 41
RollupCoverageRoutingTests green.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
@erikdarlingdata erikdarlingdata changed the title DO NOT MERGE (A6 daily stitch): route the daily-tier reads through the stitched builder DO NOT MERGE (A6 daily stitch): daily-tier reads now stitch in the interval-honest successor daily Sep 25, 2026
erikdarlingdata and others added 2 commits September 24, 2026 21:01
…ot, route the 3 FinOps daily splices through it

The daily-splice scan (NoReaderOutsideTheBuilder_SplicesADailyViewNameBySubstitution)
matched only a literal collect.{TimescaleSupport.XDailyView} substring, so it missed
the three FinOps one-argument overloads, which splice the same constant from inside a
ternary: collect.{(tier == RetentionTier.Hourly ? ...HourlyView : ...DailyView)} AS f.
Replaced the literal patterns with three regexes over the same comment-stripped text:
an interpolation hole naming the constant anywhere in its expression (catches the
ternary), a "collect." + constant concatenation, and the bare view name after
collect. Confirmed red on exactly the three FinOps sites (FinOps.Workload.cs:119,
148, 170) before routing them.

Routed DatabaseResourceUsageSqlFor(tier), TopResourceConsumersByTotalSqlFor(tier) and
TopResourceConsumersByAvgSqlFor(tier) through their three-argument stitch-builder
overloads with RollupCoverage.Unknown (RollupAvailability.None), which makes
StitchedRelationSql answer collect.<legacy> AS f for both tiers -- the same bytes the
bare splice produced. Fixed their doc comments, which said the one-argument form
"reads the legacy hourly" even on the daily tier.

Scan now green; RetentionTierRouterTests pins (FinOpsReaders_RouteWithoutTrippingTheirDriftGuards)
pass unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
RangeSqlFor(tier, hourlyRelation)'s doc comment said the three-argument
overload "calls this one only with the legacy-only splice's name (never a
daily successor)". Since LA-8a that's no longer true: the three-argument
overload builds its own queries CTE directly off QueriesCteForCagg rather
than calling this one, so a successor-only daily keeps its own name.
Corrected the doc comment.

Renamed the three-argument overload's local legacyOnlySplice to
singleSplice: the surrounding comment already says the builder can answer
either the legacy OR the successor here (a single relation, not always the
legacy), so the old name was misleading.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
@erikdarlingdata

Copy link
Copy Markdown
Owner Author

LA-8b1 (scan hole and doc fixes)

Two commits pushed to this shared branch (8879d55f, on top of f17b6df1): 529153a0 (steps 1+2) and 8879d55f (step 3). PR stays draft. I did not touch the PR body, title, or CHANGELOG.

1. Scan-test hole

NoReaderOutsideTheBuilder_SplicesADailyViewNameBySubstitution in Darling/Darling.Tests/RollupCoverageRoutingTests.cs matched only a literal collect.{TimescaleSupport.XDailyView} substring. It missed the three FinOps one-argument overloads in Darling/PerformanceMonitor.Darling.Viewer/ViewerDataService.FinOps.Workload.cs, which splice the same constant from inside a ternary: collect.{(tier == RetentionTier.Hourly ? ...HourlyView : ...DailyView)} AS f.

I replaced the literal patterns array with three regexes over the same comment-stripped text. One matches an interpolation hole naming a legacy daily constant anywhere in its expression, which catches the ternary. One matches a "collect." + constant concatenation. One matches the bare legacy view name straight after collect.. I also updated the method's doc comment to describe the three shapes.

Before routing the FinOps sites, the rewritten scan went red on exactly those three call sites, confirming it now catches what it used to miss:

Darling\PerformanceMonitor.Darling.Viewer\ViewerDataService.FinOps.Workload.cs:119 (collect.{(tier == RetentionTier.Hourly ? TimescaleSupport.QueryStatsDbHourlyView : TimescaleSupport.QueryStatsDbDailyView)})
Darling\PerformanceMonitor.Darling.Viewer\ViewerDataService.FinOps.Workload.cs:148 (collect.{(tier == RetentionTier.Hourly ? TimescaleSupport.QueryStatsHourlyView : TimescaleSupport.QueryStatsDailyView)})
Darling\PerformanceMonitor.Darling.Viewer\ViewerDataService.FinOps.Workload.cs:170 (collect.{(tier == RetentionTier.Hourly ? TimescaleSupport.QueryStatsHourlyView : TimescaleSupport.QueryStatsDailyView)})

That is 3 offenders, matching the brief. I did not commit that red state.

2. Routed the three FinOps sites

DatabaseResourceUsageSqlFor(tier), TopResourceConsumersByTotalSqlFor(tier) and TopResourceConsumersByAvgSqlFor(tier) now each forward to their three-argument overload, passing RollupCoverage.Unknown and DateTime.MinValue. RollupCoverage.Unknown carries RollupAvailability.None. That makes StitchedRelationSql fall through to collect.<legacy> AS f on both the hourly and the daily branch. I confirmed this by reading StitchedRelationSql's _availability.Has(successor) guard on each branch: it produces the same bytes the old splice produced.

I also fixed each function's <summary>. Two of the three said "over the legacy hourly, unstitched" even for the daily tier. They now say "over the legacy relation for that tier."

The scan is green. The RetentionTierRouterTests pins (FinOpsReaders_RouteWithoutTrippingTheirDriftGuards, FinOpsReaders_RawTier_ReturnTheConstantsUnchanged) pass unchanged, so the SQL text these overloads produce is unchanged for both tiers.

3. DailySummarySql.cs

(a) RangeSqlFor(tier, hourlyRelation)'s doc comment said the three-argument overload "calls this one only with the legacy-only splice's name (never a daily successor)". That has not been true since lane LA-8a: for that path the three-argument overload does not call this one at all. It builds QueriesCteForCagg(relationName) itself, off the stitch builder's own splice, so a successor-only daily keeps its own name. I corrected the comment to say that.

(b) I renamed the three-argument overload's local legacyOnlySplice to singleSplice. The surrounding comment already explains the builder can answer either the legacy or the successor at that point (never a stitch, but not always the legacy), so the old name was misleading.

Both changes are comment and naming only. No SQL text changed.

Build

Darling/Darling.Tests/Darling.Tests.csproj builds clean: 0 errors, 6 warnings, the same 6 the brief's trial build showed. All are pre-existing xUnit2031 warnings ("don't filter with Where before Assert.Single") in files this lane did not touch:

  • SqlServerStoreTileBehaviourTests.cs lines 103, 197, 322, 418
  • SqlServerStoreTileBehaviourBatchQueryMemoryTests.cs lines 98, 197

I left these for whichever lane next touches those two files, since they are outside this lane's files.

Tests

Ran by class, MTP exe, no PG rig, DARLING_TEST_PG left unset. Two class names in the brief named a file, not the class inside it. Darling/Darling.Tests/ViewerFinOpsTests.cs holds a class named ViewerFinOpsSqlTests. Darling/Darling.Tests/DailySummaryReadShapeTests.cs holds three classes: DailySummaryReadShapeSqlTests, ComposeStoreAvailabilitySingleFlightTests, and DailySummaryReadShapeLiveTests. I ran the SQL-shape class in each case, and skipped DailySummaryReadShapeLiveTests (tagged [Collection("live-postgres")]) since this brief calls for no rig.

Class Total
RollupCoverageRoutingTests 41
RetentionTierRouterTests 31
ViewerFinOpsSqlTests 49
StitchedRelationSqlTests 12
DailySummaryStitchedRangeTests 8
DailySummaryNotCarriedTests 6
DailySummaryReadShapeSqlTests 3
IntervalHonestHourlyRollupTests 8
DocCommentHygieneTests 77

Combined run across all 9 classes: Total 235, Failed 0, Errors 0.

I did not run the full suite. CI runs it on push, per the brief.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3

erikdarlingdata and others added 2 commits September 24, 2026 21:17
… FinOps, Compose, daily summary and all six pairs

New DailyStitchLiveTests.cs, own scratch TimescaleDB store. One seeded server over six fixed-anchor
calendar days (D0..D5) with a per-day row the interval-honest successors exclude and the legacy
counts, so legacy/successor totals and unique_queries differ every day. Legacy refreshed for all six
days; successor refreshed from D3 (F_d), with D4's successor DAILY deliberately left unrefreshed
(while its successor HOURLY is) to exercise the not-carried NULL path.

Proves with real numbers: FinOps' DatabaseResourceUsageSqlFor at the daily tier (39.0 ms / 183
executions, neither all-legacy 60.0/186 nor all-successor 18.0/180); a Compose panel through
ComposeSourceRouter + ComposeCompiler (one point per day the stitched relation holds, D4 absent);
DailySummarySql's not-carried probe (3/3/3/2/NULL/2 across the six days, D4 NULL because its source
is the refreshed successor hourly); and a re-probe with RollupAvailability.WithoutIntervalDailies
showing the pre-A6-daily numbers (D3=3, D4=3) the real probe replaced. A second fact forces all six
stitch pairs (three hourly, three daily) into their UNION ALL form with a hand-built RollupCoverage
and confirms SELECT * LIMIT 0 succeeds on each.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
@erikdarlingdata erikdarlingdata changed the title DO NOT MERGE (A6 daily stitch): daily-tier reads now stitch in the interval-honest successor daily Daily-tier reads now stitch in the interval-honest successor daily (#3653 A6) Sep 25, 2026
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 25, 2026 01:45
@erikdarlingdata
erikdarlingdata merged commit 193506c into dev Sep 25, 2026
19 of 20 checks passed
@erikdarlingdata
erikdarlingdata deleted the fix/3653-a6-daily-stitch branch September 25, 2026 01:46
erikdarlingdata added a commit that referenced this pull request Sep 25, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
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