diff --git a/Restaurant.Admin.Bff.slnx b/Restaurant.Admin.Bff.slnx
index 0044d68..67a41cc 100644
--- a/Restaurant.Admin.Bff.slnx
+++ b/Restaurant.Admin.Bff.slnx
@@ -4,4 +4,7 @@
+
+
+
diff --git a/docs/api/restaurant-admin-workflows.md b/docs/api/restaurant-admin-workflows.md
new file mode 100644
index 0000000..aca5abf
--- /dev/null
+++ b/docs/api/restaurant-admin-workflows.md
@@ -0,0 +1,27 @@
+# Restaurant Admin Workflow API
+
+## Purpose
+
+This BFF exposes restaurant-admin control-plane workflows over REST while delegating orchestration to `operations-service`.
+
+## Endpoints
+
+- `GET /api/restaurant/admin/config?contextId=`
+ - Returns configuration snapshot, feature flags, service windows, and recent changes.
+- `GET /api/restaurant/admin/changes?contextId=`
+ - Returns recent configuration change history.
+- `POST /api/restaurant/admin/service-window`
+ - Updates a service window and returns the applied snapshot.
+
+## Upstream Dependency
+
+- Base address configuration: `OperationsService:BaseAddress`
+- Default runtime target: `http://operations-service:8080`
+- Internal upstream routes:
+ - `GET /internal/operations/admin/config`
+ - `POST /internal/operations/admin/service-window`
+
+## Notes
+
+- The explicit change-history route is projected from the same admin config snapshot returned by `operations-service`.
+- Correlation IDs are preserved through Thalos session checks and operations-service calls.
diff --git a/docs/architecture/ownership-boundary.md b/docs/architecture/ownership-boundary.md
index 358db0c..093a1e2 100644
--- a/docs/architecture/ownership-boundary.md
+++ b/docs/architecture/ownership-boundary.md
@@ -1,6 +1,6 @@
# Restaurant Admin Ownership Boundary
- Control-plane-facing BFF for policy, schedule, and operational configuration.
-- Owns admin edge contracts for feature flags, service windows, and overrides.
+- Owns admin edge contracts for feature flags, service windows, overrides, and recent change visibility.
- Does not own transactional order execution.
- Consumes service APIs only; no direct DAL access.
diff --git a/docs/roadmap/feature-epics.md b/docs/roadmap/feature-epics.md
index 1f3ec0c..de86199 100644
--- a/docs/roadmap/feature-epics.md
+++ b/docs/roadmap/feature-epics.md
@@ -13,6 +13,7 @@ restaurant-admin-bff
- Kitchen queue and dispatch optimization hooks.
- Operations control-plane policies (flags, service windows, overrides).
- POS closeout and settlement summary alignment.
+- Admin change history and configuration snapshot workflows that stay aligned with operations-service.
## Documentation Contract
Any code change in this repository must include docs updates in the same branch.
diff --git a/docs/runbooks/containerization.md b/docs/runbooks/containerization.md
index a83ae5a..f853461 100644
--- a/docs/runbooks/containerization.md
+++ b/docs/runbooks/containerization.md
@@ -22,7 +22,8 @@ docker run --rm -p 8080:8080 --name restaurant-admin-bff agilewebs/restaurant-ad
## Runtime Notes
-- Exposes REST control-plane endpoints for admin configuration updates.
+- Exposes REST control-plane endpoints for admin configuration snapshots, recent change history, and service-window updates.
+- Requires `OperationsService__BaseAddress` to resolve the upstream operations-service runtime.
- Requires `ThalosAuth__BaseAddress` to resolve Thalos session introspection endpoint.
- Returns standardized auth failures (`401|403|503`) with `x-correlation-id` propagation.
@@ -38,5 +39,5 @@ docker run --rm -p 8080:8080 --name restaurant-admin-bff agilewebs/restaurant-ad
- Integration artifact path: `greenfield/demo/restaurant/docker-compose.yml`
## Known Limitations
-- Current runtime adapters are still predominantly in-memory for deterministic local/demo behavior.
+- Restaurant-admin now delegates control-plane snapshots to `operations-service`, but the upstream operations adapter still serves deterministic demo data rather than database-backed state.
- Demo PostgreSQL seeds validate integration contracts and smoke determinism, but do not yet imply full persistence implementation parity.
diff --git a/docs/security/auth-enforcement.md b/docs/security/auth-enforcement.md
index be6edad..df25a20 100644
--- a/docs/security/auth-enforcement.md
+++ b/docs/security/auth-enforcement.md
@@ -7,6 +7,7 @@ This BFF enforces authenticated access on business endpoints using Thalos sessio
## Protected Endpoints
- `/api/restaurant/admin/config`
+- `/api/restaurant/admin/changes`
- `/api/restaurant/admin/service-window`
## Anonymous Endpoints
diff --git a/src/Restaurant.Admin.Bff.Application/Adapters/DefaultRestaurantAdminServiceClient.cs b/src/Restaurant.Admin.Bff.Application/Adapters/DefaultRestaurantAdminServiceClient.cs
deleted file mode 100644
index 954a424..0000000
--- a/src/Restaurant.Admin.Bff.Application/Adapters/DefaultRestaurantAdminServiceClient.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Restaurant.Admin.Bff.Contracts.Requests;
-using Restaurant.Admin.Bff.Contracts.Responses;
-
-namespace Restaurant.Admin.Bff.Application.Adapters;
-
-public sealed class DefaultRestaurantAdminServiceClient : IRestaurantAdminServiceClient
-{
- public Task FetchAsync(GetRestaurantAdminConfigRequest request, CancellationToken cancellationToken)
- {
- return Task.FromResult(new GetRestaurantAdminConfigResponse(request.ContextId, "Default service-backed response."));
- }
-
- public Task SetServiceWindowAsync(SetServiceWindowRequest request, CancellationToken cancellationToken)
- {
- return Task.FromResult(new SetServiceWindowResponse(request.ContextId, true, "Service window updated by default adapter."));
- }
-}
diff --git a/src/Restaurant.Admin.Bff.Application/Adapters/IRestaurantAdminServiceClient.cs b/src/Restaurant.Admin.Bff.Application/Adapters/IRestaurantAdminServiceClient.cs
index ec3f670..c85c73a 100644
--- a/src/Restaurant.Admin.Bff.Application/Adapters/IRestaurantAdminServiceClient.cs
+++ b/src/Restaurant.Admin.Bff.Application/Adapters/IRestaurantAdminServiceClient.cs
@@ -5,6 +5,7 @@ namespace Restaurant.Admin.Bff.Application.Adapters;
public interface IRestaurantAdminServiceClient
{
- Task FetchAsync(GetRestaurantAdminConfigRequest request, CancellationToken cancellationToken);
+ Task FetchConfigAsync(GetRestaurantAdminConfigRequest request, CancellationToken cancellationToken);
+ Task FetchRecentChangesAsync(GetRestaurantAdminConfigRequest request, CancellationToken cancellationToken);
Task SetServiceWindowAsync(SetServiceWindowRequest request, CancellationToken cancellationToken);
}
diff --git a/src/Restaurant.Admin.Bff.Application/Adapters/OperationsRestaurantAdminServiceClient.cs b/src/Restaurant.Admin.Bff.Application/Adapters/OperationsRestaurantAdminServiceClient.cs
new file mode 100644
index 0000000..866c491
--- /dev/null
+++ b/src/Restaurant.Admin.Bff.Application/Adapters/OperationsRestaurantAdminServiceClient.cs
@@ -0,0 +1,128 @@
+using System.Net.Http.Json;
+using Restaurant.Admin.Bff.Contracts.Contracts;
+using Restaurant.Admin.Bff.Contracts.Requests;
+using Restaurant.Admin.Bff.Contracts.Responses;
+
+namespace Restaurant.Admin.Bff.Application.Adapters;
+
+public sealed class OperationsRestaurantAdminServiceClient(HttpClient httpClient) : IRestaurantAdminServiceClient
+{
+ public async Task FetchConfigAsync(GetRestaurantAdminConfigRequest request, CancellationToken cancellationToken)
+ {
+ var payload = await GetConfigPayloadAsync(request.ContextId, cancellationToken);
+
+ return new GetRestaurantAdminConfigResponse(
+ payload.ContextId,
+ payload.Summary,
+ payload.Version,
+ payload.FeatureFlags
+ .Select(static flag => new FeatureFlagStateContract(flag.Key, flag.Enabled))
+ .ToArray(),
+ payload.ServiceWindows.Select(MapServiceWindow).ToArray(),
+ payload.RecentChanges.Select(MapConfigChange).ToArray());
+ }
+
+ public async Task FetchRecentChangesAsync(GetRestaurantAdminConfigRequest request, CancellationToken cancellationToken)
+ {
+ var payload = await GetConfigPayloadAsync(request.ContextId, cancellationToken);
+
+ return new GetRecentAdminChangesResponse(
+ payload.ContextId,
+ payload.Summary,
+ payload.Version,
+ payload.RecentChanges.Select(MapConfigChange).ToArray());
+ }
+
+ public async Task SetServiceWindowAsync(SetServiceWindowRequest request, CancellationToken cancellationToken)
+ {
+ var payload = await SetServiceWindowPayloadAsync(
+ new SetServiceWindowPayload(
+ request.ContextId,
+ (int)request.Day,
+ request.OpenAt.ToString("HH:mm:ss"),
+ request.CloseAt.ToString("HH:mm:ss"),
+ request.UpdatedBy),
+ cancellationToken);
+
+ return new SetServiceWindowResponse(
+ payload.ContextId,
+ payload.Applied,
+ payload.Message,
+ MapServiceWindow(payload.ServiceWindow));
+ }
+
+ private async Task GetConfigPayloadAsync(string contextId, CancellationToken cancellationToken)
+ {
+ var payload = await httpClient.GetFromJsonAsync(
+ $"internal/operations/admin/config?contextId={Uri.EscapeDataString(contextId)}",
+ cancellationToken);
+
+ return payload ?? throw new InvalidOperationException("Operations service returned an empty restaurant admin config payload.");
+ }
+
+ private async Task SetServiceWindowPayloadAsync(
+ SetServiceWindowPayload request,
+ CancellationToken cancellationToken)
+ {
+ using var response = await httpClient.PostAsJsonAsync("internal/operations/admin/service-window", request, cancellationToken);
+ response.EnsureSuccessStatusCode();
+
+ var payload = await response.Content.ReadFromJsonAsync(cancellationToken);
+ return payload ?? throw new InvalidOperationException("Operations service returned an empty service-window payload.");
+ }
+
+ private static ServiceWindowContract MapServiceWindow(ServiceWindowPayload serviceWindow)
+ {
+ return new ServiceWindowContract(
+ (DayOfWeek)serviceWindow.DayOfWeek,
+ TimeOnly.Parse(serviceWindow.OpenAt),
+ TimeOnly.Parse(serviceWindow.CloseAt),
+ serviceWindow.IsClosed);
+ }
+
+ private static ConfigChangeContract MapConfigChange(ConfigChangePayload change)
+ {
+ return new ConfigChangeContract(
+ change.ChangeId,
+ change.Category,
+ change.Description,
+ change.UpdatedBy,
+ change.UpdatedAtUtc);
+ }
+
+ private sealed record GetRestaurantAdminConfigPayload(
+ string ContextId,
+ string Summary,
+ string Version,
+ IReadOnlyCollection FeatureFlags,
+ IReadOnlyCollection ServiceWindows,
+ IReadOnlyCollection RecentChanges);
+
+ private sealed record FeatureFlagPayload(string Key, bool Enabled);
+
+ private sealed record ServiceWindowPayload(
+ int DayOfWeek,
+ string OpenAt,
+ string CloseAt,
+ bool IsClosed);
+
+ private sealed record ConfigChangePayload(
+ string ChangeId,
+ string Category,
+ string Description,
+ string UpdatedBy,
+ DateTime UpdatedAtUtc);
+
+ private sealed record SetServiceWindowPayload(
+ string ContextId,
+ int DayOfWeek,
+ string OpenAt,
+ string CloseAt,
+ string UpdatedBy);
+
+ private sealed record SetServiceWindowResponsePayload(
+ string ContextId,
+ bool Applied,
+ string Message,
+ ServiceWindowPayload ServiceWindow);
+}
diff --git a/src/Restaurant.Admin.Bff.Application/Handlers/GetRecentAdminChangesHandler.cs b/src/Restaurant.Admin.Bff.Application/Handlers/GetRecentAdminChangesHandler.cs
new file mode 100644
index 0000000..695b5f7
--- /dev/null
+++ b/src/Restaurant.Admin.Bff.Application/Handlers/GetRecentAdminChangesHandler.cs
@@ -0,0 +1,13 @@
+using Restaurant.Admin.Bff.Application.Adapters;
+using Restaurant.Admin.Bff.Contracts.Requests;
+using Restaurant.Admin.Bff.Contracts.Responses;
+
+namespace Restaurant.Admin.Bff.Application.Handlers;
+
+public sealed class GetRecentAdminChangesHandler(IRestaurantAdminServiceClient serviceClient) : IGetRecentAdminChangesHandler
+{
+ public Task HandleAsync(GetRestaurantAdminConfigRequest request, CancellationToken cancellationToken)
+ {
+ return serviceClient.FetchRecentChangesAsync(request, cancellationToken);
+ }
+}
diff --git a/src/Restaurant.Admin.Bff.Application/Handlers/GetRestaurantAdminConfigHandler.cs b/src/Restaurant.Admin.Bff.Application/Handlers/GetRestaurantAdminConfigHandler.cs
index 2daffd8..9dc3f23 100644
--- a/src/Restaurant.Admin.Bff.Application/Handlers/GetRestaurantAdminConfigHandler.cs
+++ b/src/Restaurant.Admin.Bff.Application/Handlers/GetRestaurantAdminConfigHandler.cs
@@ -8,6 +8,6 @@ public sealed class GetRestaurantAdminConfigHandler(IRestaurantAdminServiceClien
{
public Task HandleAsync(GetRestaurantAdminConfigRequest request, CancellationToken cancellationToken)
{
- return serviceClient.FetchAsync(request, cancellationToken);
+ return serviceClient.FetchConfigAsync(request, cancellationToken);
}
}
diff --git a/src/Restaurant.Admin.Bff.Application/Handlers/IGetRecentAdminChangesHandler.cs b/src/Restaurant.Admin.Bff.Application/Handlers/IGetRecentAdminChangesHandler.cs
new file mode 100644
index 0000000..32550a7
--- /dev/null
+++ b/src/Restaurant.Admin.Bff.Application/Handlers/IGetRecentAdminChangesHandler.cs
@@ -0,0 +1,9 @@
+using Restaurant.Admin.Bff.Contracts.Requests;
+using Restaurant.Admin.Bff.Contracts.Responses;
+
+namespace Restaurant.Admin.Bff.Application.Handlers;
+
+public interface IGetRecentAdminChangesHandler
+{
+ Task HandleAsync(GetRestaurantAdminConfigRequest request, CancellationToken cancellationToken);
+}
diff --git a/src/Restaurant.Admin.Bff.Contracts/Contracts/ConfigChangeContract.cs b/src/Restaurant.Admin.Bff.Contracts/Contracts/ConfigChangeContract.cs
new file mode 100644
index 0000000..a5c00a0
--- /dev/null
+++ b/src/Restaurant.Admin.Bff.Contracts/Contracts/ConfigChangeContract.cs
@@ -0,0 +1,8 @@
+namespace Restaurant.Admin.Bff.Contracts.Contracts;
+
+public sealed record ConfigChangeContract(
+ string ChangeId,
+ string Category,
+ string Description,
+ string UpdatedBy,
+ DateTime UpdatedAtUtc);
diff --git a/src/Restaurant.Admin.Bff.Contracts/Contracts/FeatureFlagStateContract.cs b/src/Restaurant.Admin.Bff.Contracts/Contracts/FeatureFlagStateContract.cs
new file mode 100644
index 0000000..f014505
--- /dev/null
+++ b/src/Restaurant.Admin.Bff.Contracts/Contracts/FeatureFlagStateContract.cs
@@ -0,0 +1,3 @@
+namespace Restaurant.Admin.Bff.Contracts.Contracts;
+
+public sealed record FeatureFlagStateContract(string Key, bool Enabled);
diff --git a/src/Restaurant.Admin.Bff.Contracts/Contracts/ServiceWindowContract.cs b/src/Restaurant.Admin.Bff.Contracts/Contracts/ServiceWindowContract.cs
new file mode 100644
index 0000000..c6d2e03
--- /dev/null
+++ b/src/Restaurant.Admin.Bff.Contracts/Contracts/ServiceWindowContract.cs
@@ -0,0 +1,7 @@
+namespace Restaurant.Admin.Bff.Contracts.Contracts;
+
+public sealed record ServiceWindowContract(
+ DayOfWeek Day,
+ TimeOnly OpenAt,
+ TimeOnly CloseAt,
+ bool IsClosed);
diff --git a/src/Restaurant.Admin.Bff.Contracts/Responses/GetRecentAdminChangesResponse.cs b/src/Restaurant.Admin.Bff.Contracts/Responses/GetRecentAdminChangesResponse.cs
new file mode 100644
index 0000000..d309326
--- /dev/null
+++ b/src/Restaurant.Admin.Bff.Contracts/Responses/GetRecentAdminChangesResponse.cs
@@ -0,0 +1,9 @@
+using Restaurant.Admin.Bff.Contracts.Contracts;
+
+namespace Restaurant.Admin.Bff.Contracts.Responses;
+
+public sealed record GetRecentAdminChangesResponse(
+ string ContextId,
+ string Summary,
+ string Version,
+ IReadOnlyCollection RecentChanges);
diff --git a/src/Restaurant.Admin.Bff.Contracts/Responses/GetRestaurantAdminConfigResponse.cs b/src/Restaurant.Admin.Bff.Contracts/Responses/GetRestaurantAdminConfigResponse.cs
index 1e1cfdd..f46e064 100644
--- a/src/Restaurant.Admin.Bff.Contracts/Responses/GetRestaurantAdminConfigResponse.cs
+++ b/src/Restaurant.Admin.Bff.Contracts/Responses/GetRestaurantAdminConfigResponse.cs
@@ -1,3 +1,11 @@
+using Restaurant.Admin.Bff.Contracts.Contracts;
+
namespace Restaurant.Admin.Bff.Contracts.Responses;
-public sealed record GetRestaurantAdminConfigResponse(string ContextId, string Summary);
+public sealed record GetRestaurantAdminConfigResponse(
+ string ContextId,
+ string Summary,
+ string Version,
+ IReadOnlyCollection FeatureFlags,
+ IReadOnlyCollection ServiceWindows,
+ IReadOnlyCollection RecentChanges);
diff --git a/src/Restaurant.Admin.Bff.Contracts/Responses/SetServiceWindowResponse.cs b/src/Restaurant.Admin.Bff.Contracts/Responses/SetServiceWindowResponse.cs
index 78dcb34..4aed9bc 100644
--- a/src/Restaurant.Admin.Bff.Contracts/Responses/SetServiceWindowResponse.cs
+++ b/src/Restaurant.Admin.Bff.Contracts/Responses/SetServiceWindowResponse.cs
@@ -1,6 +1,9 @@
+using Restaurant.Admin.Bff.Contracts.Contracts;
+
namespace Restaurant.Admin.Bff.Contracts.Responses;
public sealed record SetServiceWindowResponse(
string ContextId,
bool Applied,
- string Message);
+ string Message,
+ ServiceWindowContract ServiceWindow);
diff --git a/src/Restaurant.Admin.Bff.Rest/Program.cs b/src/Restaurant.Admin.Bff.Rest/Program.cs
index 6a9c16d..2d6fd94 100644
--- a/src/Restaurant.Admin.Bff.Rest/Program.cs
+++ b/src/Restaurant.Admin.Bff.Rest/Program.cs
@@ -10,8 +10,14 @@ const string SessionAccessCookieName = "thalos_session";
const string SessionRefreshCookieName = "thalos_refresh";
var builder = WebApplication.CreateBuilder(args);
-builder.Services.AddSingleton();
+builder.Services.AddHttpClient((serviceProvider, httpClient) =>
+{
+ var configuration = serviceProvider.GetRequiredService();
+ var operationsBaseAddress = configuration["OperationsService:BaseAddress"] ?? "http://operations-service:8080";
+ httpClient.BaseAddress = new Uri($"{operationsBaseAddress.TrimEnd('/')}/");
+});
builder.Services.AddSingleton();
+builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddHttpClient("ThalosAuth");
@@ -44,6 +50,24 @@ app.MapGet("/api/restaurant/admin/config", async (
return Results.Ok(await handler.HandleAsync(request, ct));
});
+app.MapGet("/api/restaurant/admin/changes", async (
+ string contextId,
+ HttpContext context,
+ IHttpClientFactory httpClientFactory,
+ IConfiguration configuration,
+ IGetRecentAdminChangesHandler handler,
+ CancellationToken ct) =>
+{
+ var authError = await EnforceSessionAsync(context, httpClientFactory, configuration, ct);
+ if (authError is not null)
+ {
+ return authError;
+ }
+
+ var request = new GetRestaurantAdminConfigRequest(contextId);
+ return Results.Ok(await handler.HandleAsync(request, ct));
+});
+
app.MapPost("/api/restaurant/admin/service-window", async (
SetServiceWindowRequest request,
HttpContext context,
diff --git a/tests/Restaurant.Admin.Bff.Application.UnitTests/OperationsRestaurantAdminServiceClientTests.cs b/tests/Restaurant.Admin.Bff.Application.UnitTests/OperationsRestaurantAdminServiceClientTests.cs
new file mode 100644
index 0000000..8c96592
--- /dev/null
+++ b/tests/Restaurant.Admin.Bff.Application.UnitTests/OperationsRestaurantAdminServiceClientTests.cs
@@ -0,0 +1,108 @@
+using System.Net;
+using System.Text;
+using Restaurant.Admin.Bff.Application.Adapters;
+using Restaurant.Admin.Bff.Contracts.Requests;
+
+namespace Restaurant.Admin.Bff.Application.UnitTests;
+
+public sealed class OperationsRestaurantAdminServiceClientTests
+{
+ [Fact]
+ public async Task FetchConfigAsync_MapsFlagsWindowsAndChanges()
+ {
+ var adapter = new OperationsRestaurantAdminServiceClient(CreateClient(ConfigPayload));
+
+ var response = await adapter.FetchConfigAsync(new GetRestaurantAdminConfigRequest("demo-context"), CancellationToken.None);
+
+ Assert.Equal("v2", response.Version);
+ Assert.NotEmpty(response.FeatureFlags);
+ Assert.NotEmpty(response.ServiceWindows);
+ Assert.NotEmpty(response.RecentChanges);
+ }
+
+ [Fact]
+ public async Task FetchRecentChangesAsync_ProjectsChangeHistory()
+ {
+ var adapter = new OperationsRestaurantAdminServiceClient(CreateClient(ConfigPayload));
+
+ var response = await adapter.FetchRecentChangesAsync(new GetRestaurantAdminConfigRequest("demo-context"), CancellationToken.None);
+
+ Assert.Equal(2, response.RecentChanges.Count);
+ }
+
+ [Fact]
+ public async Task SetServiceWindowAsync_MapsUpdatedServiceWindow()
+ {
+ var adapter = new OperationsRestaurantAdminServiceClient(CreateClient("""
+ {
+ "contextId": "demo-context",
+ "applied": true,
+ "message": "Service window updated by admin-operator.",
+ "serviceWindow": {
+ "dayOfWeek": 1,
+ "openAt": "08:00:00",
+ "closeAt": "22:00:00",
+ "isClosed": false
+ }
+ }
+ """));
+
+ var response = await adapter.SetServiceWindowAsync(
+ new SetServiceWindowRequest("demo-context", DayOfWeek.Monday, new TimeOnly(8, 0), new TimeOnly(22, 0), "admin-operator"),
+ CancellationToken.None);
+
+ Assert.True(response.Applied);
+ Assert.Equal(DayOfWeek.Monday, response.ServiceWindow.Day);
+ }
+
+ private static HttpClient CreateClient(string json)
+ {
+ return new HttpClient(new StubHttpMessageHandler(json))
+ {
+ BaseAddress = new Uri("http://operations-service:8080/")
+ };
+ }
+
+ private sealed class StubHttpMessageHandler(string json) : HttpMessageHandler
+ {
+ protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new StringContent(json, Encoding.UTF8, "application/json")
+ });
+ }
+ }
+
+ private const string ConfigPayload = """
+ {
+ "contextId": "demo-context",
+ "summary": "Restaurant admin snapshot includes current flags, service windows, and recent control-plane changes.",
+ "version": "v2",
+ "featureFlags": [
+ { "key": "kitchen.dispatch.enabled", "enabled": true },
+ { "key": "pos.closeout.preview", "enabled": true }
+ ],
+ "serviceWindows": [
+ { "dayOfWeek": 1, "openAt": "08:00:00", "closeAt": "22:00:00", "isClosed": false },
+ { "dayOfWeek": 5, "openAt": "08:00:00", "closeAt": "23:30:00", "isClosed": false }
+ ],
+ "recentChanges": [
+ {
+ "changeId": "CFG-100",
+ "category": "service-window",
+ "description": "Extended Friday dinner service window.",
+ "updatedBy": "admin-operator",
+ "updatedAtUtc": "2026-03-31T10:00:00Z"
+ },
+ {
+ "changeId": "CFG-101",
+ "category": "feature-flag",
+ "description": "Enabled POS closeout preview mode.",
+ "updatedBy": "ops-lead",
+ "updatedAtUtc": "2026-03-31T12:00:00Z"
+ }
+ ]
+ }
+ """;
+}
diff --git a/tests/Restaurant.Admin.Bff.Application.UnitTests/Restaurant.Admin.Bff.Application.UnitTests.csproj b/tests/Restaurant.Admin.Bff.Application.UnitTests/Restaurant.Admin.Bff.Application.UnitTests.csproj
new file mode 100644
index 0000000..bedc03e
--- /dev/null
+++ b/tests/Restaurant.Admin.Bff.Application.UnitTests/Restaurant.Admin.Bff.Application.UnitTests.csproj
@@ -0,0 +1,19 @@
+
+
+ net10.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+