Skip to content

test(integration): make the llm-log plan and aging tests chunk-boundary proof (Thursday 00:00 UTC hypertable chunk) - #138

Merged
GottZ merged 1 commit into
rootfrom
fix/ci-thursday-chunk-boundary
Nov 5, 2026
Merged

GottZ merged 1 commit into
rootfrom
fix/ci-thursday-chunk-boundary

Conversation

@GottZ

@GottZ GottZ commented Nov 5, 2026 •

Copy link
Copy Markdown
Owner

Fixes #137. Test-only: three integration tests fail deterministically after the Thursday 00:00 UTC chunk boundary of the context_llm_log hypertable (7-day chunks), because the newest chunk is almost empty then. Root 52a35d0b, #134 and #136 are red in the heavy and rest shards for exactly this reason; #135 is not involved (its head was green at 23:51 UTC with identical test files).

What changes

  • TestDistillSpendGuard/WindowAndBackoffExpiryLetItRunAgain (internal/events/distill_spend_integration_test.go): aging the llm log by UPDATE created_at moved rows across the chunk boundary, which TimescaleDB rejects (constraint_1, SQLSTATE 23514). Rows are now deleted and re-inserted 3 h earlier in one transaction via a temp table. The rollup trigger counts them twice on the way; the guard reads the raw log (distill_spend.go:313), so the checked invariant is unchanged.
  • TestMigration158OnPopulatedHypertable (internal/llmlog/consumer_attribution_integration_test.go): seed and window are aligned with the chunk grid via time_bucket('7 days', now(), origin => 'epoch') (without origin the bucket starts on Monday, not Thursday — checked against the live DB). The test asserts that the newest chunk ends exactly there. Scan counting is unchanged; no enable_seqscan.
  • TestQuotaSpendPlan (internal/backends/quota_plan_integration_test.go): the test now runs on a test clock 1–2 h before the chunk boundary through the existing SetClockForTest seam, with the minute still taken from the wall clock (so test(backends): TestQuotaSpendPlan is planner-dependent in CI — custom plan touches 102 raw rows via the created_at chunk index instead of idx_llm_log_apikey (2 of 3 rest-shard runs on Go-untouched PRs) #129 stays covered). Every assertion stays hard, including the index check on every chunk. Rejected alternatives: a page-count threshold for the index assertion (the Thursday chunk already has ~400 rows at 01:40 UTC, so the threshold would have been a guess that weakens the assertion), and filling the current chunk with seed rows (writes into the future and inflates the rollup).

Verification (-tags=integration -p 1, inside the red window 01:42–01:50 UTC)

Test red on 52a35d0b green after
Distill :252 … violates check constraint "constraint_1" (SQLSTATE 23514) 0/3 3/3
Mig158 :411 … and 1 seq scan(s), want idx_llm_log_consumer only 0/3 3/3 (5 index scans on idx_llm_log_consumer each)
Quota 6 findings per run :365 a raw scan reads "_hyper_1_6_chunk_context_llm_log_created_at_idx" 0/3 3/3; rollup numbers identical red and green (rows 3/5/5, ceiling 6, 1441 tenant rows, 10 lookups)

Package suites: events 263 pass / 0 fail / 2 skip, llmlog 44/0/4, backends 102/0/0. golangci-lint with and without the integration tag 0 issues, go vet -tags=integration ./... rc 0, both deadcode gates ok. Not run: the repo-wide go test -short -race ./... (no non-test file changed; CI covers it).

Report: .project/plan-openai-api-2026-11-03/reports/bau/fix-ci-thursday-chunk-boundary.md.

Summary by CodeRabbit

  • Tests
    • Expanded integration coverage for quota spending and consumer attribution using time-based data aligned to storage intervals.
    • Updated distiller log test data handling to support shifting records across storage intervals.
    • Added checks for recent data ranges and chunk boundaries in quota and consumer attribution scenarios. These changes are limited to test coverage and do not describe changes to end-user functionality.

…ry proof (Thursday 00:00 UTC hypertable chunk)

context_llm_log is a TimescaleDB hypertable with 7-day chunks laid on the
Unix-epoch grid, so every chunk starts on a Thursday 00:00 UTC. Right after
that boundary the newest chunk holds only the hours since midnight. Three
integration tests seeded relative to now() and broke deterministically in
that window (local 3/3 each on 52a35d0, CI root 41944930718 and PRs #134,
#136 identical):

1. TestDistillSpendGuard/WindowAndBackoffExpiryLetItRunAgain aged the log
   with UPDATE created_at = created_at - 3 h. A row moved across a chunk
   boundary is refused by the chunk's range CHECK, not re-routed:
   "violates check constraint constraint_1 (SQLSTATE 23514)", 00:00-03:00
   UTC every Thursday, latent since the test landed.
   Fix: dsAgePipeline copies the pipeline's rows into a temp table, deletes
   them and re-inserts them aged, in one transaction, so every row lands in
   the chunk of its new created_at. The guard reads the raw log, not the
   rollup, so the window it sees is the one the UPDATE meant to produce;
   the asserted story (trip, both clocks run out, next tick is ok with
   budget 1) is unchanged.

2. TestMigration158OnPopulatedHypertable/per-consumer window: the seed ended
   at now(), so the newest chunk in the 30-day window was one heap page
   (22 rows) and a Seq Scan is the cheaper plan there: "... and 1 seq
   scan(s), want idx_llm_log_consumer only".
   Fix: seed, consumer rows and window end at anchor, the start of the
   chunk holding now() (time_bucket over the hypertable's own chunk
   interval with origin => epoch; the default origin 2000-01-03 is a
   Monday, three days off the chunk grid). The test asserts that the
   newest chunk ends exactly at anchor. Every chunk the window touches now
   carries the full seed density at every clock; the gate is unchanged:
   one index scan on idx_llm_log_consumer per chunk, no seq scan, no
   enable_seqscan, no loosened count.

3. TestQuotaSpendPlan: the relief lookups take their created_at bound at
   run time, so every chunk is planned; on the Thursday-morning chunk (four
   rows per key) the planner reads the chunk's created_at index - correct
   for a chunk of a few pages, meaningless for the access path at scale:
   "a raw scan reads _hyper_1_6_chunk_context_llm_log_created_at_idx, not
   idx_llm_log_apikey" (6 of 6 plans).
   Fix: the accountant runs on a test clock (SetClockForTest, existing
   seam) one to two hours before anchor, keeping the wall clock's minute
   of the hour so the edges keep every width (#129); the seed and the
   30-day tenant count end at that clock. The newest chunk holds almost
   seven days, the oldest about three, at every wall clock. All checks stay
   hard and unchanged: two-sided created_at bound, raw rows <= ceiling 6,
   rollup via idx_llm_usage_key, exact created_at MCV statistics on every
   chunk, idx_llm_log_apikey on every raw scan of every chunk. No rows lie
   after the clock's next full hour, so the rollup is the same seed moved in
   time: the run logs the same figures as the red run (raw rows 3/5/5,
   ceiling 6, 1441 tenant rows in 30 days, 10 executed relief lookups).

Red -> green, -count=3 each, Thursday 2026-11-05 01:42-01:50 UTC:
  TestDistillSpendGuard              0/3 -> 3/3 (red: SQLSTATE 23514)
  TestMigration158OnPopulatedHypertable 0/3 -> 3/3 (red: 1 seq scan)
  TestQuotaSpendPlan                 0/3 -> 3/3 (red: 6 created_at_idx plans)
Negative probe: the fixed quota test with its clock moved to Thursday
01:xx (anchor + 1 h) is red again with the same 6 findings; the plans show
the chunk-6 created_at_idx scans are the relief-lookup subplans.
Packages with -tags=integration -p 1 -count=1 (top-level pass/fail/skip):
events 263/0/2, llmlog 44/0/4, backends 102/0/0.
golangci-lint (with and without the integration tag) 0 issues,
go vet -tags=integration ./... rc 0, both deadcode gates ok.
Test files only; no production code, migration or config.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 5, 2026 01:57
@coderabbitai

coderabbitai Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

The integration tests now align seeded data and query windows with hypertable chunk boundaries. The distiller test ages rows by deleting and reinserting them through a temporary table in a transaction.

Changes: Chunk-aligned integration tests

Layer / File(s) Summary
Quota plan seed and clock alignment
go/internal/backends/quota_plan_integration_test.go
The test seeds rows and evaluates the 30-day tenant-row count using a test clock aligned with the current chunk anchor. It checks that the newest chunk ends at the anchor.
Distiller row aging
go/internal/events/distill_spend_integration_test.go
The test replaces a direct timestamp update with a transactional temporary-table workflow that deletes and reinserts the selected pipeline’s rows at shifted timestamps.
Consumer attribution seed and window alignment
go/internal/llmlog/consumer_attribution_integration_test.go
The test can seed through a supplied timestamp, derives the current chunk start, and uses that anchor for seeded rows and the 30-day query window. It checks that the newest chunk ends at the anchor.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Other · Severity of issue fixed: Medium

Possibly related issues

Possibly related PRs

  • GottZ/ctx#74 — Adds the test clock seam used by TestQuotaSpendPlan.
  • GottZ/ctx#135 — Updates the same test’s planner assertions; this change also adjusts its chunk layout and test clock.
  • GottZ/ctx#73 — Adds the rollup trigger relevant to reinserting context_llm_log rows in the distiller test.
Merge Risk: Minimal · up to 87a70

The tests use chunk-aligned timestamps and transactional row aging. No merge-blocking risk was identified.

Pre-merge checks: Passed 5

Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly identifies integration-test changes that address hypertable chunk-boundary failures. It accurately describes the main purpose, although it does not name every affected test.
Linked Issues check Passed Issue #137 requires test-only fixes for three boundary failures. The diff changes only the three named integration tests. TestDistillSpendGuard now ages rows by transactional delete and re-insert. `…
Out of Scope Changes check Passed The changed paths are go/internal/events/distill_spend_integration_test.go, go/internal/llmlog/consumer_attribution_integration_test.go, and go/internal/backends/quota_plan_integration_test.go. …
Docstring Coverage Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentati…
Full details: Linked Issues check

Explanation

Issue #137 requires test-only fixes for three boundary failures. The diff changes only the three named integration tests. TestDistillSpendGuard now ages rows by transactional delete and re-insert. TestMigration158OnPopulatedHypertable aligns seed data and the query window to an epoch-origin chunk boundary and checks the newest chunk end. Its per-chunk index and no-sequential-scan gate remains. TestQuotaSpendPlan uses SetClockForTest with a boundary-safe test clock, retains the wall-clock minute, and keeps the row-ceiling, MCV, and hard index checks. The issue's minimum-size condition is a proposed fix; the boundary-safe clock is a valid alternative that preserves the required checks.

Full details: Out of Scope Changes check

Explanation

The changed paths are go/internal/events/distill_spend_integration_test.go, go/internal/llmlog/consumer_attribution_integration_test.go, and go/internal/backends/quota_plan_integration_test.go. Each change supports one of the three failures named in issue #137. No production code or unrelated behavior changed.

Full details: Docstring Coverage

Explanation

Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 5 functions across 3 files.

Finishing Touches

Generate docstrings

  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 77532d78-90e0-466f-9a67-1d2f3b081164
Commits

Reviewing files that changed from the base of the PR and between 52a35d0 and 87a7094.

Files selected for processing: 3

  • go/internal/backends/quota_plan_integration_test.go
  • go/internal/events/distill_spend_integration_test.go
  • go/internal/llmlog/consumer_attribution_integration_test.go

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues remain for this test-only change addressing chunk-boundary sensitivity in three integration tests.

No open issues 🎉

What changed in this PR

This test-only PR addresses #137 by making three integration tests independent of the Thursday 00:00 UTC TimescaleDB chunk boundary. Production code and existing plan assertions remain unchanged.

Changes:

  • Ages distill log rows through transactional delete and reinsert, avoiding cross-chunk update failures.
  • Aligns migration-test seed data and query windows with the hypertable chunk grid.
  • Uses a chunk-relative quota test clock while preserving wall-clock minute variation and strict index checks.
File Description
go/​internal/​llmlog/​consumer_attribution_integration_test.go Aligns seed and consumer-query timestamps with chunk boundaries and verifies the newest chunk’s end.
go/​internal/​events/​distill_spend_integration_test.go Adds transactional row aging that supports movement across chunks.
go/​internal/​backends/​quota_plan_integration_test.go Aligns seeding, row counts, and accountant windows to a test clock before the chunk boundary.

Review effort: Balanced

@GottZ
GottZ merged commit 99080d0 into root Nov 5, 2026
23 checks passed
@GottZ
GottZ deleted the fix/ci-thursday-chunk-boundary branch November 5, 2026 02:10
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.

CI: Thursday 00:00 UTC hypertable chunk boundary makes TestQuotaSpendPlan, TestMigration158 and TestDistillSpendGuard deterministically red

2 participants