Skip to content

[UI-1491] Expose cluster identity on /info endpoint#5569

Open
George-Payne wants to merge 1 commit intomasterfrom
cluster-id
Open

[UI-1491] Expose cluster identity on /info endpoint#5569
George-Payne wants to merge 1 commit intomasterfrom
cluster-id

Conversation

@George-Payne
Copy link
Member

Currently, Navigator uses the server address as database identity, but this isn't ideal as when a different database starts on the same address (e.g. dev environments, reprovisioning), Navigator reuses stale credentials and cached state for what is actually a different database.

Without admin credentials, there is currently no way of knowing if a cluster you are speaking to is the same one you were speaking to previously, as instanceId is only for the uptime of the individual nodes, and any attempt at fingerprinting is slow and fragile.

This PR adds clusterId to /info endpoint response, which acts as a stable identifier for the database, persists across restarts, and is consistent across all nodes in a cluster.

The clusterId is epoch 0's EpochId, as it already does everything we need:

  • Already shared across all nodes via replication - no additional sync mechanism needed
  • Persisted in the transaction log, survives restarts
  • Generated once at cluster creation
  • A fresh database (wiped data dir) produces a new epoch 0, correctly treated as a different database

The main addition, other than wiring up epoch manager to the info endpoint, is adding a simple way to read epoch 0 from EpochManager:

EpochManager stores epoch 0 separately from the rolling epoch cache (which only holds the 10 most recent epochs). It's populated in three places:

  • Init - if found during the epoch chain walk, otherwise read directly from log position 0 if the cache fills before reaching it.
  • WriteNewEpoch - captured when the leader writes epoch 0
  • CacheEpoch - captured when epoch 0 is received via replication

InfoController reads it via GetFirstEpoch() on each request.

Example response

{
  "dbVersion": "26.1.0-prerelease",
  "state": "leader",
  "clusterId": "8e739e5f-3f1e-412b-b42c-e032afc4b4ae",
  "features": { ... }
}

@George-Payne George-Payne self-assigned this Mar 26, 2026
@George-Payne George-Payne requested a review from a team as a code owner March 26, 2026 12:10
@linear
Copy link

linear bot commented Mar 26, 2026

@qodo-code-review
Copy link
Contributor

Review Summary by Qodo

Expose cluster identity on /info endpoint via epoch 0

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Expose cluster identity via clusterId field on /info endpoint
• Add GetFirstEpoch() method to EpochManager for stable cluster identification
• Capture epoch 0 during initialization, writes, and replication caching
• Add comprehensive tests for first epoch retrieval across scenarios
Diagram
flowchart LR
  A["EpochManager"] -- "GetFirstEpoch()" --> B["First Epoch Record"]
  B -- "EpochId as ClusterId" --> C["InfoController"]
  C -- "JSON Response" --> D["/info Endpoint"]
  E["Init/WriteNewEpoch/CacheEpoch"] -- "Capture epoch 0" --> A
Loading

Grey Divider

File Changes

1. src/KurrentDB.Core.Tests/Http/Info/when_getting_info.cs 🧪 Tests +39/-0

Add HTTP tests for /info endpoint cluster ID

• New test fixture for /info endpoint HTTP behavior
• Validates endpoint returns HTTP 200 OK
• Verifies clusterId field is present and contains valid GUID

src/KurrentDB.Core.Tests/Http/Info/when_getting_info.cs


2. src/KurrentDB.Core.Tests/Services/ElectionsService/FakeEpochManager.cs 🧪 Tests +1/-0

Add GetFirstEpoch to fake epoch manager

• Add GetFirstEpoch() method to fake implementation
• Returns first epoch from internal collection

src/KurrentDB.Core.Tests/Services/ElectionsService/FakeEpochManager.cs


3. src/KurrentDB.Core.Tests/Services/Storage/EpochManager/when_getting_first_epoch.cs 🧪 Tests +290/-0

Add comprehensive first epoch retrieval tests

• Three comprehensive test fixtures covering first epoch retrieval
• Test lifecycle: null before epochs, available after write, persists across writes
• Test caching: epoch 0 available when received via replication
• Test initialization: epoch 0 available in cache and when read directly from log position 0

src/KurrentDB.Core.Tests/Services/Storage/EpochManager/when_getting_first_epoch.cs


View more (5)
4. src/KurrentDB.Core/ClusterVNode.cs ✨ Enhancement +2/-1

Wire epoch manager to info controller

• Wire epochManager to InfoController constructor
• Pass epoch manager instance during info controller initialization

src/KurrentDB.Core/ClusterVNode.cs


5. src/KurrentDB.Core/Services/Storage/EpochManager/EpochManager.cs ✨ Enhancement +18/-1

Add first epoch tracking and retrieval

• Add _firstEpoch volatile field to store epoch 0 separately from cache
• Implement GetFirstEpoch() method returning stored first epoch
• Capture epoch 0 during ReadEpochs() initialization
• Read epoch 0 directly from log position 0 if cache fills before reaching it
• Capture epoch 0 in WriteNewEpoch() when epoch number is 0
• Capture epoch 0 in CacheEpoch() when received via replication

src/KurrentDB.Core/Services/Storage/EpochManager/EpochManager.cs


6. src/KurrentDB.Core/Services/Storage/EpochManager/IEpochManager.cs ✨ Enhancement +1/-0

Add GetFirstEpoch interface method

• Add GetFirstEpoch() method signature to interface
• Enables access to stable cluster identifier across all implementations

src/KurrentDB.Core/Services/Storage/EpochManager/IEpochManager.cs


7. src/KurrentDB.Core/Services/Transport/Http/Controllers/InfoController.cs ✨ Enhancement +5/-1

Add cluster ID to info endpoint response

• Add IEpochManager dependency injection
• Include ClusterId field in /info JSON response using epoch 0's EpochId
• Update constructor to accept epoch manager parameter

src/KurrentDB.Core/Services/Transport/Http/Controllers/InfoController.cs


8. src/KurrentDB.Core/Services/Transport/Http/Controllers/InfoControllerBuilder.cs ✨ Enhancement +8/-1

Add epoch manager to info controller builder

• Add _epochManager field to builder
• Add WithEpochManager() fluent builder method
• Pass epoch manager to InfoController constructor in Build()

src/KurrentDB.Core/Services/Transport/Http/Controllers/InfoControllerBuilder.cs


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Mar 26, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📎 Requirement gaps (0) 📐 Spec deviations (0)

Grey Divider


Action required

1. KurrentDB.Core.Tests missing TestEnvironmentWireUp.cs 📘 Rule violation ⛯ Reliability
Description
This PR adds new tests to the KurrentDB.Core.Tests project but the project does not include the
required TestEnvironmentWireUp.cs using ToolkitTestEnvironment.Initialize/Reset. This can lead
to inconsistent test initialization/teardown and shared infrastructure leakage across test runs.
Code

src/KurrentDB.Core.Tests/Http/Info/when_getting_info.cs[R13-22]

+[TestFixture(typeof(LogFormat.V2), typeof(string))]
+public class when_getting_info<TLogFormat, TStreamId> : HttpBehaviorSpecification<TLogFormat, TStreamId> {
+	private JObject _response;
+
+	protected override Task Given() => Task.CompletedTask;
+
+	protected override async Task When() {
+		await Get("/info", "");
+		_response = _lastResponseBody.ParseJson<JObject>();
+	}
Evidence
PR Compliance ID 6 requires every test project to include a TestEnvironmentWireUp.cs entrypoint
with ToolkitTestEnvironment.Initialize/Reset. This PR adds new test fixtures under
src/KurrentDB.Core.Tests, but no corresponding TestEnvironmentWireUp.cs is present for that test
project.

CLAUDE.md
src/KurrentDB.Core.Tests/Http/Info/when_getting_info.cs[13-39]
src/KurrentDB.Core.Tests/Services/Storage/EpochManager/when_getting_first_epoch.cs[20-60]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`src/KurrentDB.Core.Tests` is being extended with new tests, but it lacks the required `TestEnvironmentWireUp.cs` that configures standard test initialization/teardown via `ToolkitTestEnvironment.Initialize` and `ToolkitTestEnvironment.Reset`.

## Issue Context
Compliance requires consistent assembly-level hooks for test infrastructure in every test project.

## Fix Focus Areas
- src/KurrentDB.Core.Tests/TestEnvironmentWireUp.cs[1-120]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Null epochManager breaks /info 🐞 Bug ⛯ Reliability
Description
InfoController dereferences _epochManager in OnGetInfo without validating the constructor argument,
so constructing it with a null IEpochManager will throw and break the /info endpoint.
InfoControllerBuilder also passes its internal _epochManager field (null unless WithEpochManager was
called), making this failure easy to trigger if the builder is used.
Code

src/KurrentDB.Core/Services/Transport/Http/Controllers/InfoController.cs[63]

+					ClusterId = _epochManager.GetFirstEpoch()?.EpochId,
Evidence
InfoController stores the epochManager constructor parameter without any null check, then
unconditionally calls _epochManager.GetFirstEpoch() when serving GET /info. InfoControllerBuilder
constructs InfoController using its _epochManager field, which is null by default unless explicitly
set, creating a straightforward null-dereference path.

src/KurrentDB.Core/Services/Transport/Http/Controllers/InfoController.cs[32-38]
src/KurrentDB.Core/Services/Transport/Http/Controllers/InfoController.cs[56-73]
src/KurrentDB.Core/Services/Transport/Http/Controllers/InfoControllerBuilder.cs[10-39]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`InfoController` assumes `IEpochManager` is non-null and dereferences it in `OnGetInfo`. If `epochManager` is passed as null (easy to do via `InfoControllerBuilder.Build()` when `WithEpochManager()` was not called), GET `/info` will throw a `NullReferenceException` and fail.

### Issue Context
This PR adds `clusterId` to `/info` by reading epoch 0 via `IEpochManager.GetFirstEpoch()`. The controller now has a hard dependency on an `IEpochManager` instance.

### Fix Focus Areas
- src/KurrentDB.Core/Services/Transport/Http/Controllers/InfoController.cs[32-38]
- src/KurrentDB.Core/Services/Transport/Http/Controllers/InfoController.cs[56-73]
- src/KurrentDB.Core/Services/Transport/Http/Controllers/InfoControllerBuilder.cs[10-39]

### Proposed fix
1. In `InfoController` constructor, add an explicit guard (e.g. `Ensure.NotNull(epochManager, nameof(epochManager))` or `throw new ArgumentNullException(nameof(epochManager))`).
2. In `InfoControllerBuilder.Build()`, fail fast if `_epochManager` is null (e.g. `throw new InvalidOperationException("EpochManager is required")`), or require it via constructor so it cannot be omitted.
3. (Optional defense-in-depth) If you want `/info` to still succeed during very early startup, you can instead treat a null epoch manager as `ClusterId = null` explicitly, but avoid an unhandled exception.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +13 to +22
[TestFixture(typeof(LogFormat.V2), typeof(string))]
public class when_getting_info<TLogFormat, TStreamId> : HttpBehaviorSpecification<TLogFormat, TStreamId> {
private JObject _response;

protected override Task Given() => Task.CompletedTask;

protected override async Task When() {
await Get("/info", "");
_response = _lastResponseBody.ParseJson<JObject>();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Action required

1. kurrentdb.core.tests missing testenvironmentwireup.cs 📘 Rule violation ⛯ Reliability

This PR adds new tests to the KurrentDB.Core.Tests project but the project does not include the
required TestEnvironmentWireUp.cs using ToolkitTestEnvironment.Initialize/Reset. This can lead
to inconsistent test initialization/teardown and shared infrastructure leakage across test runs.
Agent Prompt
## Issue description
`src/KurrentDB.Core.Tests` is being extended with new tests, but it lacks the required `TestEnvironmentWireUp.cs` that configures standard test initialization/teardown via `ToolkitTestEnvironment.Initialize` and `ToolkitTestEnvironment.Reset`.

## Issue Context
Compliance requires consistent assembly-level hooks for test infrastructure in every test project.

## Fix Focus Areas
- src/KurrentDB.Core.Tests/TestEnvironmentWireUp.cs[1-120]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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