Skip to content

feat: add TenantScheduleProvider for cross-tenant schedule discovery#2164

Merged
bjcoombs merged 3 commits intodevelopfrom
per-tenant-scheduling--9--tenant-schedule-provider
Apr 7, 2026
Merged

feat: add TenantScheduleProvider for cross-tenant schedule discovery#2164
bjcoombs merged 3 commits intodevelopfrom
per-tenant-scheduling--9--tenant-schedule-provider

Conversation

@bjcoombs
Copy link
Copy Markdown
Collaborator

@bjcoombs bjcoombs commented Apr 7, 2026

Summary

  • Implements scheduler.ScheduleProvider interface via TenantScheduleProvider that queries tenant_schedule tables across all org_* tenant schemas
  • GormTenantScheduleRepository discovers tenant schemas using information_schema and queries each for enabled schedules
  • Schedule IDs formatted as tenant_id:schedule_name for unique identification across tenants

Changes

  • services/control-plane/internal/scheduling/provider.go - Provider, repository interface, GORM implementation
  • services/control-plane/internal/scheduling/provider_test.go - Unit tests (provider mapping, ID format, error propagation) and integration tests (multi-tenant CockroachDB)

Test plan

  • Unit tests: provider mapping, Schedule.ID format, empty results, error propagation
  • Integration tests: multi-tenant queries, enabled-only filtering, empty tenants (CockroachDB testcontainers)
  • Compile-time interface check (var _ scheduler.ScheduleProvider = ...)

Implements scheduler.ScheduleProvider by querying tenant_schedule tables
across all org_* schemas. Includes GormTenantScheduleRepository for
schema discovery and per-schema querying, with Schedule.ID formatted as
"tenant_id:schedule_name".

Unit tests cover provider mapping, ID format, empty results, and error
propagation. Integration tests verify multi-tenant queries against
CockroachDB testcontainers.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 7, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0878ae58-8371-4335-b941-f829848843be

📥 Commits

Reviewing files that changed from the base of the PR and between d4b293b and 936a2ad.

📒 Files selected for processing (1)
  • services/control-plane/internal/scheduling/provider.go
✅ Files skipped from review due to trivial changes (1)
  • services/control-plane/internal/scheduling/provider.go

📝 Walkthrough

Walkthrough

Adds tenant-backed schedule discovery: a TenantScheduleRepository and TenantSchedule model, a TenantScheduleProvider that converts tenant schedules into scheduler.Schedule (ID formatted as "<tenant_id>:<schedule_name>"), and a GORM-backed repository that enumerates tenant schemas via information_schema and queries per-schema tenant_schedule rows.

Changes

Cohort / File(s) Summary
Core Implementation
services/control-plane/internal/scheduling/provider.go
New tenant scheduling module: adds TenantScheduleRepository interface and TenantSchedule model; TenantScheduleProvider implementing scheduler.ScheduleProvider (maps TenantSchedule -> scheduler.Schedule with ID tenantID:scheduleName); GormTenantScheduleRepository that discovers tenant schemas via information_schema, queries each <schema>.tenant_schedule for enabled = true, handles per-schema errors, and exposes sentinel ErrAllSchemaQueriesFailed.
Tests
services/control-plane/internal/scheduling/provider_test.go
Adds unit tests (mock repo) verifying ID formatting, cron/tenant/metadata propagation, empty results, and error passthrough; adds CockroachDB integration tests that create per-tenant schemas/tables, insert enabled/disabled rows, and verify GormTenantScheduleRepository.ListEnabledSchedules returns enabled schedules with correct tenant attribution.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant TenantScheduleProvider
    participant TenantScheduleRepository
    participant Database as information_schema / <schema>.tenant_schedule

    Caller->>TenantScheduleProvider: ListSchedules(ctx)
    TenantScheduleProvider->>TenantScheduleRepository: ListEnabledSchedules(ctx)
    TenantScheduleRepository->>Database: Query information_schema for tenant schemas
    Database-->>TenantScheduleRepository: tenant schema list
    loop per tenant schema
        TenantScheduleRepository->>Database: Query `<schema>.tenant_schedule` WHERE enabled = true
        Database-->>TenantScheduleRepository: enabled rows
        alt query error
            TenantScheduleRepository->>TenantScheduleRepository: log error / increment failure counter
        end
    end
    TenantScheduleRepository-->>TenantScheduleProvider: []TenantSchedule (or ErrAllSchemaQueriesFailed if all queries failed)
    TenantScheduleProvider->>TenantScheduleProvider: map to []scheduler.Schedule (ID = tenantID:scheduleName)
    TenantScheduleProvider-->>Caller: []scheduler.Schedule
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding TenantScheduleProvider for cross-tenant schedule discovery, which matches the primary implementation in the changeset.
Description check ✅ Passed The description is clearly related to the changeset, providing a summary of the TenantScheduleProvider implementation, repository pattern, and test coverage for the new scheduling functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch per-tenant-scheduling--9--tenant-schedule-provider

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link
Copy Markdown

claude Bot commented Apr 7, 2026

Claude Code Review

Commit: 936a2ad | CI: running

Summary

Clean implementation of TenantScheduleProvider (PRD-060 Deliverable B.3). The code correctly implements the scheduler.ScheduleProvider interface by discovering org_* tenant schemas via information_schema and querying each for enabled schedules. Good partial-failure handling (degrades gracefully unless ALL schemas fail), proper schema-qualified SQL preventing tenant data leakage, and solid test coverage including CockroachDB integration tests.

The latest commit (936a2ad) adds context cancellation propagation in ListEnabledSchedules, which correctly short-circuits on context.Canceled/context.DeadlineExceeded rather than treating those as schema-level failures. This addresses the CodeRabbit concern.

One domain-level observation remains: SagaName is queried from the database but dropped during the TenantSchedulescheduler.Schedule mapping. This is a non-blocking suggestion — see previous inline comment.

Risk Assessment

Area Level Detail
Blast radius Low New code, no existing behavior modified
Rollback Safe Two new files only, no migrations
Scale Low Schema discovery is O(tenants), runs at refresh interval not per-request
Cross-system Low Implements existing scheduler.ScheduleProvider interface cleanly
Migration N/A No migration files in this PR

Findings

Severity Location Description Status
Improvement provider.go:65-75 SagaName queried but not mapped to scheduler.Schedule — executor won't know which saga to run Open

Bot Review Notes

  • CodeRabbit (provider.go:128 — context cancellation): Already addressed in commit 936a2ad. The code now checks for context.Canceled/context.DeadlineExceeded and returns immediately instead of continuing to partial results.
  • CodeRabbit (provider_test.go:137 — missing error checks on DB.Exec): Disagree. All Exec calls are wrapped in require.NoError(t, ...), which will fail the test immediately on error.

Previously Flagged

Severity Location Description Status
Improvement provider.go:117 Continue-on-error drops schedules silently Resolved — acceptable with the warn log + total failure check
Note provider_test.go:203 Empty tenants test uses setupTestDB which creates schemas with empty tables Resolved

coderabbitai[bot]
coderabbitai Bot previously requested changes Apr 7, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
services/control-plane/internal/scheduling/provider_test.go (4)

86-88: Minor redundancy with compile-time check in provider.go.

This duplicates the interface assertion at provider.go:76. Having it in tests is a valid documentation pattern, but note the redundancy.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/control-plane/internal/scheduling/provider_test.go` around lines 86
- 88, Remove the redundant compile-time interface assertion test: delete the
TestTenantScheduleProvider_ImplementsScheduleProvider function from
provider_test.go since provider.go already contains the same assertion (var _
scheduler.ScheduleProvider = (*TenantScheduleProvider)(nil)); ensure
TenantScheduleProvider still has the single assertion in provider.go and run
tests.

166-188: Test assertions could be strengthened.

  1. Lines 175-176: Missing error checks on Exec (same issue as the multi-tenant test).
  2. Lines 183-187: The loop only checks schedules where TenantID == "alpha" but doesn't verify the total count or fail if unexpected schedules appear.
♻️ Suggested improvements
 	// Insert one enabled and one disabled
-	tc1.DB.Exec(fmt.Sprintf(`INSERT INTO %q.tenant_schedule (schedule_name, saga_name, cron_expr, enabled) VALUES ('enabled-job', 'job.run', '0 0 * * *', true)`, tc1.Tenant.SchemaName()))
-	tc1.DB.Exec(fmt.Sprintf(`INSERT INTO %q.tenant_schedule (schedule_name, saga_name, cron_expr, enabled) VALUES ('disabled-job', 'noop.run', '0 0 * * *', false)`, tc1.Tenant.SchemaName()))
+	require.NoError(t, tc1.DB.Exec(fmt.Sprintf(`INSERT INTO %q.tenant_schedule (schedule_name, saga_name, cron_expr, enabled) VALUES ('enabled-job', 'job.run', '0 0 * * *', true)`, tc1.Tenant.SchemaName())).Error)
+	require.NoError(t, tc1.DB.Exec(fmt.Sprintf(`INSERT INTO %q.tenant_schedule (schedule_name, saga_name, cron_expr, enabled) VALUES ('disabled-job', 'noop.run', '0 0 * * *', false)`, tc1.Tenant.SchemaName())).Error)

 	repo := NewGormTenantScheduleRepository(tc1.DB, nil)
 	schedules, err := repo.ListEnabledSchedules(context.Background())

 	require.NoError(t, err)
+	
+	// Filter to alpha schedules and verify only enabled-job is returned
+	var alphaSchedules []TenantSchedule
+	for _, s := range schedules {
+		if s.TenantID == "alpha" {
+			alphaSchedules = append(alphaSchedules, s)
+		}
+	}
+	require.Len(t, alphaSchedules, 1, "should return exactly one enabled schedule from alpha")
+	assert.Equal(t, "enabled-job", alphaSchedules[0].ScheduleName)

-	for _, s := range schedules {
-		if s.TenantID == "alpha" {
-			assert.Equal(t, "enabled-job", s.ScheduleName, "only enabled schedules should be returned")
-		}
-	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/control-plane/internal/scheduling/provider_test.go` around lines 166
- 188, In TestGormTenantScheduleRepository_ListEnabledSchedules_OnlyEnabled
strengthen the test by checking errors returned from the two tc1.DB.Exec(...)
calls (capture and require.NoError on each Exec) and assert the results of
repo.ListEnabledSchedules more strictly: require that err is nil,
require.Len(schedules, 1) (or assert the exact expected count) and assert that
the single returned schedule has TenantID "alpha" and ScheduleName "enabled-job"
and that no schedule with enabled==false is present; this uses the existing test
function TestGormTenantScheduleRepository_ListEnabledSchedules_OnlyEnabled and
the repository method ListEnabledSchedules to locate where to add the checks.

190-207: Test setup creates unused resources; test name may be misleading.

setupTestDB(t) at line 195 creates tenant schemas that are immediately discarded. The test then uses a fresh database connection without tenant schemas. This tests "no tenant schemas exist" rather than "tenant schemas exist but have no schedules."

Consider either:

  1. Removing the unused setupTestDB call, or
  2. Using the existing tenant contexts without inserting schedules to test truly empty tenant tables
♻️ Option 1: Remove unused setup
 func TestGormTenantScheduleRepository_ListEnabledSchedules_EmptyTenants(t *testing.T) {
 	if testing.Short() {
 		t.Skip("skipping integration test")
 	}

-	_, _, cleanup := setupTestDB(t)
-	defer cleanup()
-
-	// No schedules inserted - both tenants have empty tables
 	db, dbCleanup := testdb.SetupCockroachDB(t, nil)
 	defer dbCleanup()

 	repo := NewGormTenantScheduleRepository(db, nil)
 	schedules, err := repo.ListEnabledSchedules(context.Background())

 	require.NoError(t, err)
 	assert.Empty(t, schedules)
 }
♻️ Option 2: Test empty tenant tables
 func TestGormTenantScheduleRepository_ListEnabledSchedules_EmptyTenants(t *testing.T) {
 	if testing.Short() {
 		t.Skip("skipping integration test")
 	}

 	tc1, _, cleanup := setupTestDB(t)
 	defer cleanup()

 	// No schedules inserted - tenant tables exist but are empty
-	db, dbCleanup := testdb.SetupCockroachDB(t, nil)
-	defer dbCleanup()

-	repo := NewGormTenantScheduleRepository(db, nil)
+	repo := NewGormTenantScheduleRepository(tc1.DB, nil)
 	schedules, err := repo.ListEnabledSchedules(context.Background())

 	require.NoError(t, err)
 	assert.Empty(t, schedules)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/control-plane/internal/scheduling/provider_test.go` around lines 190
- 207, The test
TestGormTenantScheduleRepository_ListEnabledSchedules_EmptyTenants creates
tenant schemas via setupTestDB(t) but then ignores them and uses a fresh DB from
testdb.SetupCockroachDB; remove the unused setupTestDB call (and its deferred
cleanup) so the test only uses the db returned by testdb.SetupCockroachDB, or
alternatively switch to using the tenant DB returned by setupTestDB and drop the
extra testdb.SetupCockroachDB call; update the test to call
NewGormTenantScheduleRepository against the single intended DB and keep the
assertion that ListEnabledSchedules returns no schedules.

111-126: Simplify DDL formatting to avoid double substitution.

Lines 117 and 120 use a confusing double-formatting pattern: fmt.Sprintf(tenantScheduleDDL, "%s", "alpha") leaves a literal %s in the DDL that testdb.CreateTable later fills via its own fmt.Sprintf. While this works because CreateTable handles the remaining substitution on line 89, it's unnecessarily complex.

Simplify by letting CreateTable perform all formatting:

testdb.CreateTable(t, tc1.DB, tc1.Tenant, tenantScheduleDDL)

Then update tenantScheduleDDL constraint to use only one %s placeholder for the schema name, and name constraints consistently per schema. Or, if constraint names must differ, pre-format only the constraint suffix and pass the pre-formatted DDL to CreateTable—but document why the pattern exists.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/control-plane/internal/scheduling/provider_test.go` around lines 111
- 126, The DDL is being double-formatted which leaves a literal `%s` for
testdb.CreateTable to fill; update setupTestDB so it passes tenantScheduleDDL
directly to testdb.CreateTable (remove fmt.Sprintf around tenantScheduleDDL in
setupTestDB), and then adjust tenantScheduleDDL to contain exactly one `%s` for
the schema name (and make constraint names include the schema or a consistent
suffix so they are unique per tenant) so CreateTable's internal fmt.Sprintf can
substitute the schema once; references: setupTestDB, testdb.CreateTable,
tenantScheduleDDL.
services/control-plane/internal/scheduling/provider.go (1)

107-130: Silent partial failures may mask systemic issues.

The current design logs per-schema query errors and continues, which is resilient. However, if all tenant queries fail, the caller receives an empty slice with no indication of widespread failure. Consider tracking failed schemas and returning an error (or at least a warning log) if all or most schemas fail.

♻️ Optional: Track and surface partial failures
 	var allSchedules []TenantSchedule
+	var failedSchemas int
 	for _, schema := range schemas {
 		tenantID := schemaToTenantID(schema)

 		var entities []TenantScheduleEntity
 		err := r.db.WithContext(ctx).
 			Table(fmt.Sprintf("%q.tenant_schedule", schema)).
 			Where("enabled = ?", true).
 			Find(&entities).Error
 		if err != nil {
 			r.logger.Error("failed to query tenant schedules",
 				"schema", schema, "error", err)
+			failedSchemas++
 			continue
 		}

 		for _, e := range entities {
 			allSchedules = append(allSchedules, TenantSchedule{
 				TenantID:     tenantID,
 				ScheduleName: e.ScheduleName,
 				SagaName:     e.SagaName,
 				CronExpr:     e.CronExpr,
 				Metadata:     e.Metadata,
 			})
 		}
 	}

+	if failedSchemas > 0 && failedSchemas == len(schemas) {
+		return nil, fmt.Errorf("all %d tenant schema queries failed", failedSchemas)
+	}
+	if failedSchemas > 0 {
+		r.logger.Warn("some tenant schema queries failed",
+			"failed", failedSchemas, "total", len(schemas))
+	}
+
 	return allSchedules, nil
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/control-plane/internal/scheduling/provider.go` around lines 107 -
130, The loop in the method that gathers schedules silently continues on
per-schema DB errors (the block using
r.db.WithContext(...).Table(...).Where(...).Find(&entities).Error and logging
via r.logger.Error), which can hide systemic failures; modify this routine to
track failedSchemas (or a failure counter) while iterating schemas and after the
loop return an error (or at minimum a warning log) when all schemas failed or
when failures exceed a configurable threshold, otherwise return the collected
allSchedules as before; ensure you reference the TenantScheduleEntity parsing
and preserve existing per-schema logging but also surface an aggregated error
via the function's error return (or a clear r.logger.Warn if the function cannot
return errors).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@services/control-plane/internal/scheduling/provider_test.go`:
- Around line 136-142: The DB.Exec calls in provider_test.go (the tc1.DB.Exec
and tc2.DB.Exec statements inserting into tenant_schedule) ignore returned
errors; update each Exec invocation to capture the result and error and fail the
test on error (e.g., check err and call t.Fatalf / require.NoError) so failed
INSERTs don't go unnoticed. Ensure you change all three Exec calls (the one for
tc1 and both for tc2) to validate the error return and surface it in the test.

---

Nitpick comments:
In `@services/control-plane/internal/scheduling/provider_test.go`:
- Around line 86-88: Remove the redundant compile-time interface assertion test:
delete the TestTenantScheduleProvider_ImplementsScheduleProvider function from
provider_test.go since provider.go already contains the same assertion (var _
scheduler.ScheduleProvider = (*TenantScheduleProvider)(nil)); ensure
TenantScheduleProvider still has the single assertion in provider.go and run
tests.
- Around line 166-188: In
TestGormTenantScheduleRepository_ListEnabledSchedules_OnlyEnabled strengthen the
test by checking errors returned from the two tc1.DB.Exec(...) calls (capture
and require.NoError on each Exec) and assert the results of
repo.ListEnabledSchedules more strictly: require that err is nil,
require.Len(schedules, 1) (or assert the exact expected count) and assert that
the single returned schedule has TenantID "alpha" and ScheduleName "enabled-job"
and that no schedule with enabled==false is present; this uses the existing test
function TestGormTenantScheduleRepository_ListEnabledSchedules_OnlyEnabled and
the repository method ListEnabledSchedules to locate where to add the checks.
- Around line 190-207: The test
TestGormTenantScheduleRepository_ListEnabledSchedules_EmptyTenants creates
tenant schemas via setupTestDB(t) but then ignores them and uses a fresh DB from
testdb.SetupCockroachDB; remove the unused setupTestDB call (and its deferred
cleanup) so the test only uses the db returned by testdb.SetupCockroachDB, or
alternatively switch to using the tenant DB returned by setupTestDB and drop the
extra testdb.SetupCockroachDB call; update the test to call
NewGormTenantScheduleRepository against the single intended DB and keep the
assertion that ListEnabledSchedules returns no schedules.
- Around line 111-126: The DDL is being double-formatted which leaves a literal
`%s` for testdb.CreateTable to fill; update setupTestDB so it passes
tenantScheduleDDL directly to testdb.CreateTable (remove fmt.Sprintf around
tenantScheduleDDL in setupTestDB), and then adjust tenantScheduleDDL to contain
exactly one `%s` for the schema name (and make constraint names include the
schema or a consistent suffix so they are unique per tenant) so CreateTable's
internal fmt.Sprintf can substitute the schema once; references: setupTestDB,
testdb.CreateTable, tenantScheduleDDL.

In `@services/control-plane/internal/scheduling/provider.go`:
- Around line 107-130: The loop in the method that gathers schedules silently
continues on per-schema DB errors (the block using
r.db.WithContext(...).Table(...).Where(...).Find(&entities).Error and logging
via r.logger.Error), which can hide systemic failures; modify this routine to
track failedSchemas (or a failure counter) while iterating schemas and after the
loop return an error (or at minimum a warning log) when all schemas failed or
when failures exceed a configurable threshold, otherwise return the collected
allSchedules as before; ensure you reference the TenantScheduleEntity parsing
and preserve existing per-schema logging but also surface an aggregated error
via the function's error return (or a clear r.logger.Warn if the function cannot
return errors).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e70ffe13-4aad-4d43-90e1-6f68bb6b6d33

📥 Commits

Reviewing files that changed from the base of the PR and between a0c5ab4 and a744ac5.

📒 Files selected for processing (2)
  • services/control-plane/internal/scheduling/provider.go
  • services/control-plane/internal/scheduling/provider_test.go

Comment thread services/control-plane/internal/scheduling/provider_test.go
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

Two suggestions + one note. See summary comment for full review.

Comment thread services/control-plane/internal/scheduling/provider.go
Comment thread services/control-plane/internal/scheduling/provider.go
Comment thread services/control-plane/internal/scheduling/provider_test.go
- Add error checks on all DB.Exec calls in tests
- Track failed schemas and return error when all queries fail
- Simplify DDL to single %s placeholder for CreateTable
- Fix EmptyTenants test to use setupTestDB's DB (tests empty tables)
- Strengthen OnlyEnabled test with explicit count assertion
- Remove redundant interface assertion test (already in provider.go)
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

See summary comment for full review. One inline suggestion on SagaName mapping.

Comment thread services/control-plane/internal/scheduling/provider.go
coderabbitai[bot]
coderabbitai Bot previously requested changes Apr 7, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
services/control-plane/internal/scheduling/provider_test.go (1)

189-203: Add a regression test for full-failure/cancellation paths.

Current integration coverage misses the repository behavior when schema queries all fail (and when context is canceled). Please add one test asserting errors.Is(err, ErrAllSchemaQueriesFailed) and one asserting cancellation is surfaced.

🧪 Suggested test additions
+func TestGormTenantScheduleRepository_ListEnabledSchedules_AllSchemaQueriesFail(t *testing.T) {
+    if testing.Short() {
+        t.Skip("skipping integration test")
+    }
+
+    tc1, _, cleanup := setupTestDB(t)
+    defer cleanup()
+
+    // Break every schema query by dropping tenant_schedule from all discovered schemas.
+    require.NoError(t, tc1.DB.Exec(fmt.Sprintf(`DROP TABLE %q.tenant_schedule`, tc1.Tenant.SchemaName())).Error)
+    require.NoError(t, tc1.DB.Exec(`DROP TABLE IF EXISTS "org_beta".tenant_schedule`).Error)
+
+    repo := NewGormTenantScheduleRepository(tc1.DB, nil)
+    _, err := repo.ListEnabledSchedules(context.Background())
+    require.Error(t, err)
+    assert.ErrorIs(t, err, ErrAllSchemaQueriesFailed)
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/control-plane/internal/scheduling/provider_test.go` around lines 189
- 203, Add two regression tests for GormTenantScheduleRepository's
ListEnabledSchedules: one that simulates all schema queries failing and asserts
errors.Is(err, ErrAllSchemaQueriesFailed), and one that cancels the context
before calling ListEnabledSchedules and asserts the cancellation error is
returned; locate tests near
TestGormTenantScheduleRepository_ListEnabledSchedules_EmptyTenants, use
NewGormTenantScheduleRepository(tc.DB, ...) and call
repo.ListEnabledSchedules(ctx) with a crafted DB/driver or transaction that
forces schema query failures for the first test and with a context canceled via
context.WithCancel for the second test, and use require/ assert to check the
specific error conditions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@services/control-plane/internal/scheduling/provider.go`:
- Around line 120-125: The loop that logs query errors (using r.logger.Error and
incrementing failedSchemas) must not swallow context cancellation/timeouts: if
the error is context.Canceled or context.DeadlineExceeded (or ctx.Err() != nil)
you should immediately propagate the context error rather than treating it as a
schema-level failure and continuing; update the error handling in the
QueryTenantSchedules (or the provider method containing failedSchemas and that
return at Line 146) to check errors.Is(err, context.Canceled) || errors.Is(err,
context.DeadlineExceeded) (or check ctx.Err()) and return nil, ctx.Err() (or
wrap and return that context error) instead of incrementing
failedSchemas/continuing; apply the same change to the similar block around
Lines 138–146 so cancellation/timeouts are not hidden when returning partial
schedules.

---

Nitpick comments:
In `@services/control-plane/internal/scheduling/provider_test.go`:
- Around line 189-203: Add two regression tests for
GormTenantScheduleRepository's ListEnabledSchedules: one that simulates all
schema queries failing and asserts errors.Is(err, ErrAllSchemaQueriesFailed),
and one that cancels the context before calling ListEnabledSchedules and asserts
the cancellation error is returned; locate tests near
TestGormTenantScheduleRepository_ListEnabledSchedules_EmptyTenants, use
NewGormTenantScheduleRepository(tc.DB, ...) and call
repo.ListEnabledSchedules(ctx) with a crafted DB/driver or transaction that
forces schema query failures for the first test and with a context canceled via
context.WithCancel for the second test, and use require/ assert to check the
specific error conditions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3f7daf58-a1b9-47f8-8c48-472c33fae8fb

📥 Commits

Reviewing files that changed from the base of the PR and between a744ac5 and d4b293b.

📒 Files selected for processing (2)
  • services/control-plane/internal/scheduling/provider.go
  • services/control-plane/internal/scheduling/provider_test.go

Comment thread services/control-plane/internal/scheduling/provider.go
Return immediately with the context error when a per-schema query fails
due to context.Canceled or context.DeadlineExceeded, rather than
silently treating it as a schema-level failure.
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

LGTM. Context cancellation fix in latest commit addresses the remaining concern. One non-blocking suggestion (SagaName mapping) noted in summary comment.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 7, 2026

Codecov Report

❌ Patch coverage is 30.76923% with 54 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ices/control-plane/internal/scheduling/provider.go 30.76% 54 Missing ⚠️

📢 Thoughts on this report? Let us know!

@bjcoombs bjcoombs dismissed stale reviews from coderabbitai[bot] and coderabbitai[bot] April 7, 2026 09:48

Stale: superseded by latest approval

@bjcoombs bjcoombs merged commit 1ab9bbd into develop Apr 7, 2026
41 of 44 checks passed
@bjcoombs bjcoombs deleted the per-tenant-scheduling--9--tenant-schedule-provider branch April 7, 2026 09:48
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