Skip to content

fix(core): preserve backward-compatible Loader.Watch interface#3313

Open
pflynn-virtru wants to merge 3 commits intomainfrom
fix/config-loader-watch-backward-compat
Open

fix(core): preserve backward-compatible Loader.Watch interface#3313
pflynn-virtru wants to merge 3 commits intomainfrom
fix/config-loader-watch-backward-compat

Conversation

@pflynn-virtru
Copy link
Copy Markdown
Member

@pflynn-virtru pflynn-virtru commented Apr 16, 2026

Summary

  • Reverts Loader.Watch to its original 3-arg signature (ctx, cfg, onChange) to maintain backward compatibility for downstream consumers that implement Loader directly
  • Introduces an optional NamespaceAwareLoader interface for loaders that want namespace/service information during watch, added via WatchWithNamespaces(ctx, cfg, onChange, namespaces)
  • Config.WatchWithNamespaces type-asserts each loader and routes to WatchWithNamespaces when the loader opts in, otherwise falls back to plain Watch

Guidance for downstream implementers

If your custom loader does not need namespace/service info, implement config.Loader as before — no change required.

If your custom loader wants namespace/service info (e.g. a remote config source scoped to enabled services), implement config.NamespaceAwareLoader:

type MyLoader struct{ /* ... */ }

// Required by Loader
func (l *MyLoader) Watch(ctx context.Context, cfg *config.Config, onChange func(context.Context) error) error {
    // fallback path — used when the caller invokes cfg.Watch(ctx) without namespace info
    return nil
}

// Required by NamespaceAwareLoader
func (l *MyLoader) WatchWithNamespaces(
    ctx context.Context,
    cfg *config.Config,
    onChange func(context.Context) error,
    namespaces []config.NamespaceInfo,
) error {
    // preferred path — invoked when the host calls cfg.WatchWithNamespaces(ctx, ns)
    return nil
}

Callers that need namespace-aware behavior must invoke cfg.WatchWithNamespaces(ctx, namespaces)cfg.Watch(ctx) passes an empty slice and will not deliver namespace info to NamespaceAwareLoader implementers.

Test plan

  • go build ./service/... passes
  • go test ./service/pkg/config/... passes
  • CI checks pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Enhanced configuration loading system architecture by separating standard configuration loading from namespace-aware functionality, improving code organization and enabling more efficient configuration management across loader implementations.

The Loader.Watch method signature was changed to include a namespaces
parameter, breaking downstream consumers that implement the interface.
Revert Watch to the original signature and introduce a separate
NamespaceAwareLoader interface for loaders that need namespace info.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 16, 2026

📝 Walkthrough

Walkthrough

The changes refactor the config loader architecture by extracting namespace-aware watching into a separate interface. The base Loader.Watch method signature is simplified to remove namespace parameters, while a new NamespaceAwareLoader interface provides namespace-aware watching via a WatchWithNamespaces method. The orchestration logic detects and routes to the appropriate watching method.

Changes

Cohort / File(s) Summary
Interface definitions
service/pkg/config/loader.go
Simplified Loader.Watch signature by removing namespaces []NamespaceInfo parameter. Added new NamespaceAwareLoader interface that extends Loader with WatchWithNamespaces(ctx, cfg, onChange, namespaces) method.
Orchestration logic
service/pkg/config/config.go
Updated Config.WatchWithNamespaces to detect loaders implementing NamespaceAwareLoader and call namespace-aware watch method; falls back to standard Watch for other loaders.
Loader implementations
service/pkg/config/config_file_loader.go, service/pkg/config/default_settings_loader.go, service/pkg/config/environment_value_loader.go, service/pkg/config/legacy_loader.go
Updated Watch method signatures across all implementations to remove []NamespaceInfo parameter. Method behavior remains unchanged.
Tests
service/pkg/config/config_test.go
Updated MockLoader.Watch signature to match new interface. Added MockNamespaceAwareLoader type to test namespace-aware watching. Modified TestConfig_Watch to use namespace-aware loader for namespace-specific test case.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • elizabethhealy

Poem

🐰 Hops of joy through interfaces neat,
Where loaders dance to namespaced beat,
Aware or not, they answer the call,
Refactored grace serves one and all!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: restoring backward compatibility to the Loader.Watch interface by reverting it to a simpler signature.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/config-loader-watch-backward-compat

Warning

Review ran into problems

🔥 Problems

Timed out fetching pipeline failures after 30000ms


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request restores backward compatibility for the Loader.Watch interface by decoupling it from namespace-specific requirements. It introduces a new interface for loaders that explicitly need namespace context, ensuring that existing implementations remain functional while providing a clean path for future extensions.

Highlights

  • Backward Compatibility: Reverted the Loader.Watch interface to its original signature by removing the namespaces parameter.
  • New Interface: Introduced the NamespaceAwareLoader interface to support loaders that require namespace information during watch operations.
  • Logic Update: Updated Config.WatchWithNamespaces to perform type assertion on loaders, enabling conditional execution of the new interface.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.


The interface was wide and deep, With parameters we could not keep. We split the path in two, To keep the old code true, And let the new logic leap.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the configuration loading system by introducing a NamespaceAwareLoader interface and simplifying the base Loader interface's Watch method. The Watch method in the Loader interface no longer requires namespace information, while loaders needing that context now implement NamespaceAwareLoader.WatchWithNamespaces. The core configuration logic was updated to use type assertions to handle both loader types, and all existing loader implementations and tests were updated accordingly. I have no feedback to provide.

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 188.361678ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 99.14514ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 417.921449ms
Throughput 239.28 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 41.839423567s
Average Latency 416.828641ms
Throughput 119.50 requests/second

pflynn-virtru and others added 2 commits April 16, 2026 12:56
No in-tree loader implemented NamespaceAwareLoader, and the namespace
info plumbed through WatchWithNamespaces was never consumed. Remove the
interface, NamespaceInfo/ServiceInfo types, and the WatchWithNamespaces
entry point; callers now use the simpler Loader.Watch path directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Paul Flynn <pflynn-virtru@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 149.002063ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 71.85058ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 346.083522ms
Throughput 288.95 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 35.33402139s
Average Latency 352.180282ms
Throughput 141.51 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 184.885601ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 104.422899ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 402.892781ms
Throughput 248.20 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 42.965103301s
Average Latency 427.341252ms
Throughput 116.37 requests/second

@github-actions
Copy link
Copy Markdown
Contributor

⚠️ Govulncheck found vulnerabilities ⚠️

The following modules have known vulnerabilities:

  • examples
  • sdk
  • service
  • lib/fixtures
  • tests-bdd

See the workflow run for details.

@pflynn-virtru pflynn-virtru marked this pull request as ready for review April 16, 2026 17:17
@pflynn-virtru pflynn-virtru requested a review from a team as a code owner April 16, 2026 17:17
Copy link
Copy Markdown

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
service/pkg/config/config_test.go (1)

171-195: ⚠️ Potential issue | 🟡 Minor

Add a Config.Watch(ctx) test for namespace-aware loaders receiving an empty namespace list.

Current coverage validates WatchWithNamespaces with explicit namespaces, but it does not lock the documented behavior that Watch(ctx) routes namespace-aware loaders with an empty slice.

Proposed test addition
 func TestConfig_Watch(t *testing.T) {
 	ctx := t.Context()
@@
 	t.Run("Loader receives NamespaceInfo", func(t *testing.T) {
 		config := &Config{}
 		loader := &MockNamespaceAwareLoader{}
@@
 		require.NoError(t, err)
 		assert.Equal(t, testNamespaces, receivedNamespaces)
 	})
+
+	t.Run("NamespaceAwareLoader via Watch receives empty namespaces", func(t *testing.T) {
+		config := &Config{}
+		loader := &MockNamespaceAwareLoader{}
+
+		var receivedNamespaces []NamespaceInfo
+		loader.watchWithNamespacesFn = func(_ context.Context, _ *Config, _ func(context.Context) error, namespaces []NamespaceInfo) error {
+			receivedNamespaces = namespaces
+			return nil
+		}
+		config.AddLoader(loader)
+
+		err := config.Watch(ctx)
+		require.NoError(t, err)
+		assert.Empty(t, receivedNamespaces)
+	})
 }

As per coding guidelines "**/*_test.go: ... add tests for new functionality".

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

In `@service/pkg/config/config_test.go` around lines 171 - 195, Add a test that
verifies Config.Watch(ctx) routes namespace-aware loaders with an empty
namespace slice: create a Config and a MockNamespaceAwareLoader, set its
watchWithNamespacesFn to capture the namespaces argument into
receivedNamespaces, call config.AddLoader(loader) and then err :=
config.Watch(ctx), assert no error and that receivedNamespaces is an empty slice
(i.e., []NamespaceInfo{}). Reference Config.Watch, MockNamespaceAwareLoader, and
the mock's watchWithNamespacesFn to locate where to hook the capture.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@service/pkg/config/config_test.go`:
- Around line 171-195: Add a test that verifies Config.Watch(ctx) routes
namespace-aware loaders with an empty namespace slice: create a Config and a
MockNamespaceAwareLoader, set its watchWithNamespacesFn to capture the
namespaces argument into receivedNamespaces, call config.AddLoader(loader) and
then err := config.Watch(ctx), assert no error and that receivedNamespaces is an
empty slice (i.e., []NamespaceInfo{}). Reference Config.Watch,
MockNamespaceAwareLoader, and the mock's watchWithNamespacesFn to locate where
to hook the capture.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4ffc8e11-f4f7-40ff-ba9d-956672896106

📥 Commits

Reviewing files that changed from the base of the PR and between 9221cac and 5a24d3b.

📒 Files selected for processing (7)
  • service/pkg/config/config.go
  • service/pkg/config/config_file_loader.go
  • service/pkg/config/config_test.go
  • service/pkg/config/default_settings_loader.go
  • service/pkg/config/environment_value_loader.go
  • service/pkg/config/legacy_loader.go
  • service/pkg/config/loader.go

Copy link
Copy Markdown
Contributor

@ntrevino-virtru ntrevino-virtru left a comment

Choose a reason for hiding this comment

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

Should we consider a standard Go pattern with variadic arguments so we do not keep hitting this?

Comment on lines +184 to +185
}
if err := loader.Watch(ctx, c, onChangeCallback); err != nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this should be else if

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants