diff --git a/docs/architecture/bff-service-boundary.md b/docs/architecture/bff-service-boundary.md
new file mode 100644
index 0000000..762e7d0
--- /dev/null
+++ b/docs/architecture/bff-service-boundary.md
@@ -0,0 +1,14 @@
+# Furniture BFF Service Boundary
+
+## Purpose
+Preserve BFF as an edge adapter layer that depends on service contracts only.
+
+## BFF Responsibilities
+- REST edge exposure
+- Service client adaptation
+- Correlation/tracing propagation
+- Single active edge protocol policy enforcement (`rest`)
+
+## Prohibited
+- Direct DAL access
+- Domain business decision ownership
diff --git a/docs/migration/correlation-propagation-checks.md b/docs/migration/correlation-propagation-checks.md
new file mode 100644
index 0000000..3b5ba08
--- /dev/null
+++ b/docs/migration/correlation-propagation-checks.md
@@ -0,0 +1,6 @@
+# Correlation Propagation Checks
+
+## Checks
+- Correlation identifiers are preserved across BFF -> Service calls.
+- No new correlation behavior is introduced at edge.
+- Trace metadata pass-through remains stable.
diff --git a/docs/migration/post-domain-contract-alignment.md b/docs/migration/post-domain-contract-alignment.md
new file mode 100644
index 0000000..cc147eb
--- /dev/null
+++ b/docs/migration/post-domain-contract-alignment.md
@@ -0,0 +1,9 @@
+# Post-Domain Contract Alignment
+
+## Goal
+Align BFF adapter usage with service contracts after domain extraction.
+
+## Steps
+1. Map current BFF contract usage to updated service contracts.
+2. Keep edge contract behavior stable.
+3. Validate adapter compatibility.
diff --git a/src/Furniture.Bff.Application/Adapters/FurnitureAvailabilityEdgeContractAdapter.cs b/src/Furniture.Bff.Application/Adapters/FurnitureAvailabilityEdgeContractAdapter.cs
new file mode 100644
index 0000000..118d160
--- /dev/null
+++ b/src/Furniture.Bff.Application/Adapters/FurnitureAvailabilityEdgeContractAdapter.cs
@@ -0,0 +1,37 @@
+using Furniture.Bff.Contracts.Api;
+using Furniture.Service.Contracts.UseCases;
+
+namespace Furniture.Bff.Application.Adapters;
+
+///
+/// Default adapter implementation between furniture BFF edge and furniture service contracts.
+///
+public sealed class FurnitureAvailabilityEdgeContractAdapter : IFurnitureAvailabilityEdgeContractAdapter
+{
+ ///
+ public GetFurnitureAvailabilityRequest ToServiceRequest(GetFurnitureAvailabilityApiRequest request)
+ {
+ return new GetFurnitureAvailabilityRequest(
+ request.FurnitureId,
+ ResolveCorrelationId(request.CorrelationId));
+ }
+
+ ///
+ public GetFurnitureAvailabilityApiResponse ToApiResponse(GetFurnitureAvailabilityResponse response)
+ {
+ return new GetFurnitureAvailabilityApiResponse(
+ response.FurnitureId,
+ response.DisplayName,
+ response.QuantityAvailable);
+ }
+
+ private static string ResolveCorrelationId(string correlationId)
+ {
+ if (!string.IsNullOrWhiteSpace(correlationId))
+ {
+ return correlationId;
+ }
+
+ return $"corr-{Guid.NewGuid():N}";
+ }
+}
diff --git a/src/Furniture.Bff.Application/Adapters/FurnitureAvailabilityEdgeGrpcContractAdapter.cs b/src/Furniture.Bff.Application/Adapters/FurnitureAvailabilityEdgeGrpcContractAdapter.cs
new file mode 100644
index 0000000..bd2e1e5
--- /dev/null
+++ b/src/Furniture.Bff.Application/Adapters/FurnitureAvailabilityEdgeGrpcContractAdapter.cs
@@ -0,0 +1,36 @@
+using Furniture.Bff.Application.Grpc;
+using Furniture.Bff.Contracts.Api;
+
+namespace Furniture.Bff.Application.Adapters;
+
+///
+/// Default adapter implementation for furniture edge gRPC contract translation.
+///
+public sealed class FurnitureAvailabilityEdgeGrpcContractAdapter : IFurnitureAvailabilityEdgeGrpcContractAdapter
+{
+ ///
+ public GetFurnitureAvailabilityEdgeGrpcContract ToGrpc(GetFurnitureAvailabilityApiRequest request)
+ {
+ return new GetFurnitureAvailabilityEdgeGrpcContract(
+ request.FurnitureId,
+ ResolveCorrelationId(request.CorrelationId));
+ }
+
+ ///
+ public GetFurnitureAvailabilityApiRequest FromGrpc(GetFurnitureAvailabilityEdgeGrpcContract contract)
+ {
+ return new GetFurnitureAvailabilityApiRequest(
+ contract.FurnitureId,
+ ResolveCorrelationId(contract.CorrelationId));
+ }
+
+ private static string ResolveCorrelationId(string correlationId)
+ {
+ if (!string.IsNullOrWhiteSpace(correlationId))
+ {
+ return correlationId;
+ }
+
+ return $"corr-{Guid.NewGuid():N}";
+ }
+}
diff --git a/src/Furniture.Bff.Application/DependencyInjection/FurnitureBffApplicationServiceCollectionExtensions.cs b/src/Furniture.Bff.Application/DependencyInjection/FurnitureBffApplicationServiceCollectionExtensions.cs
new file mode 100644
index 0000000..93d43b4
--- /dev/null
+++ b/src/Furniture.Bff.Application/DependencyInjection/FurnitureBffApplicationServiceCollectionExtensions.cs
@@ -0,0 +1,26 @@
+using Furniture.Bff.Application.Adapters;
+using Furniture.Bff.Application.Handlers;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace Furniture.Bff.Application.DependencyInjection;
+
+///
+/// Registers application-layer runtime wiring for furniture-bff.
+///
+public static class FurnitureBffApplicationServiceCollectionExtensions
+{
+ ///
+ /// Adds furniture-bff application handlers and adapter implementations.
+ ///
+ /// Service collection.
+ /// Service collection for fluent chaining.
+ public static IServiceCollection AddFurnitureBffApplicationRuntime(this IServiceCollection services)
+ {
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+ services.TryAddScoped();
+
+ return services;
+ }
+}
diff --git a/src/Furniture.Bff.Application/Furniture.Bff.Application.csproj b/src/Furniture.Bff.Application/Furniture.Bff.Application.csproj
index 41b768c..85a3ebb 100644
--- a/src/Furniture.Bff.Application/Furniture.Bff.Application.csproj
+++ b/src/Furniture.Bff.Application/Furniture.Bff.Application.csproj
@@ -5,6 +5,7 @@
enable
+
diff --git a/src/Furniture.Bff.Rest/Adapters/FurnitureServiceGrpcClientAdapter.cs b/src/Furniture.Bff.Rest/Adapters/FurnitureServiceGrpcClientAdapter.cs
new file mode 100644
index 0000000..d2f3600
--- /dev/null
+++ b/src/Furniture.Bff.Rest/Adapters/FurnitureServiceGrpcClientAdapter.cs
@@ -0,0 +1,70 @@
+using Furniture.Bff.Application.Adapters;
+using Furniture.Service.Contracts.UseCases;
+using Furniture.Service.Grpc;
+using Grpc.Core;
+using Microsoft.Extensions.Primitives;
+
+namespace Furniture.Bff.Rest.Adapters;
+
+///
+/// gRPC-backed adapter for downstream furniture-service calls.
+///
+public sealed class FurnitureServiceGrpcClientAdapter(
+ FurnitureRuntime.FurnitureRuntimeClient grpcClient,
+ IHttpContextAccessor httpContextAccessor) : IFurnitureServiceClient
+{
+ private const string CorrelationHeaderName = "x-correlation-id";
+
+ ///
+ public async Task GetAvailabilityAsync(GetFurnitureAvailabilityRequest request)
+ {
+ var correlationId = ResolveCorrelationId(request.CorrelationId);
+ var grpcRequest = new GetFurnitureAvailabilityGrpcRequest
+ {
+ FurnitureId = request.FurnitureId,
+ CorrelationId = correlationId
+ };
+
+ var grpcResponse = await grpcClient.GetFurnitureAvailabilityAsync(
+ grpcRequest,
+ headers: CreateHeaders(correlationId),
+ deadline: DateTime.UtcNow.AddSeconds(10));
+
+ return new GetFurnitureAvailabilityResponse(
+ grpcResponse.FurnitureId,
+ grpcResponse.DisplayName,
+ grpcResponse.QuantityAvailable);
+ }
+
+ private string ResolveCorrelationId(string? preferred = null)
+ {
+ if (!string.IsNullOrWhiteSpace(preferred))
+ {
+ return preferred;
+ }
+
+ var context = httpContextAccessor.HttpContext;
+ if (context?.Items.TryGetValue(CorrelationHeaderName, out var itemValue) == true &&
+ itemValue is string itemCorrelationId &&
+ !string.IsNullOrWhiteSpace(itemCorrelationId))
+ {
+ return itemCorrelationId;
+ }
+
+ if (context?.Request.Headers.TryGetValue(CorrelationHeaderName, out var headerValue) == true &&
+ !StringValues.IsNullOrEmpty(headerValue))
+ {
+ return headerValue.ToString();
+ }
+
+ return context?.TraceIdentifier ?? $"corr-{Guid.NewGuid():N}";
+ }
+
+ private static Metadata CreateHeaders(string correlationId)
+ {
+ return
+ [
+ new Metadata.Entry(CorrelationHeaderName, correlationId)
+ ];
+ }
+}
diff --git a/src/Furniture.Bff.Rest/Furniture.Bff.Rest.csproj b/src/Furniture.Bff.Rest/Furniture.Bff.Rest.csproj
index 2c53814..c444ea1 100644
--- a/src/Furniture.Bff.Rest/Furniture.Bff.Rest.csproj
+++ b/src/Furniture.Bff.Rest/Furniture.Bff.Rest.csproj
@@ -4,8 +4,22 @@
enable
enable
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
diff --git a/src/Furniture.Bff.Rest/Program.cs b/src/Furniture.Bff.Rest/Program.cs
index 5dd0bbd..a99f89e 100644
--- a/src/Furniture.Bff.Rest/Program.cs
+++ b/src/Furniture.Bff.Rest/Program.cs
@@ -1,13 +1,76 @@
+using Core.Blueprint.Common.DependencyInjection;
+using Furniture.Bff.Application.Adapters;
+using Furniture.Bff.Application.DependencyInjection;
+using Furniture.Bff.Application.Handlers;
using Furniture.Bff.Contracts.Api;
+using Furniture.Bff.Rest.Adapters;
+using Furniture.Bff.Rest.Endpoints;
+using Furniture.Service.Grpc;
+using Microsoft.Extensions.Primitives;
+
+const string CorrelationHeaderName = "x-correlation-id";
var builder = WebApplication.CreateBuilder(args);
-
-// Stage 3 skeleton: single active external protocol for this deployment is REST.
-var app = builder.Build();
-
-app.MapGet("/api/furniture/{furnitureId}/availability", (string furnitureId) =>
+var edgeProtocol = builder.Configuration["FurnitureBff:EdgeProtocol"] ?? "rest";
+if (!string.Equals(edgeProtocol, "rest", StringComparison.OrdinalIgnoreCase))
{
- return Results.Ok(new GetFurnitureAvailabilityApiResponse(furnitureId, string.Empty, 0));
+ throw new InvalidOperationException(
+ $"Furniture BFF supports one active edge protocol per deployment. Configured: '{edgeProtocol}'. Expected: 'rest'.");
+}
+
+builder.Services.AddHttpContextAccessor();
+builder.Services.AddHealthChecks();
+builder.Services.AddBlueprintRuntimeCore();
+builder.Services.AddFurnitureBffApplicationRuntime();
+builder.Services.AddScoped();
+builder.Services.AddGrpcClient(options =>
+{
+ var serviceAddress = builder.Configuration["FurnitureService:GrpcAddress"] ?? "http://localhost:5252";
+ options.Address = new Uri(serviceAddress);
});
+var app = builder.Build();
+
+app.Use(async (context, next) =>
+{
+ var correlationId = ResolveCorrelationId(context);
+ context.Items[CorrelationHeaderName] = correlationId;
+ context.Request.Headers[CorrelationHeaderName] = correlationId;
+ context.Response.Headers[CorrelationHeaderName] = correlationId;
+ await next();
+});
+
+app.MapGet($"{EndpointConventions.ApiPrefix}/{{furnitureId}}/availability", async (
+ string furnitureId,
+ HttpContext context,
+ IGetFurnitureAvailabilityHandler handler) =>
+{
+ var request = new GetFurnitureAvailabilityApiRequest(
+ furnitureId,
+ ResolveCorrelationId(context));
+
+ var response = await handler.HandleAsync(request);
+ return Results.Ok(response);
+});
+
+app.MapHealthChecks("/healthz");
+
app.Run();
+
+string ResolveCorrelationId(HttpContext context)
+{
+ if (context.Items.TryGetValue(CorrelationHeaderName, out var itemValue) &&
+ itemValue is string itemCorrelationId &&
+ !string.IsNullOrWhiteSpace(itemCorrelationId))
+ {
+ return itemCorrelationId;
+ }
+
+ if (context.Request.Headers.TryGetValue(CorrelationHeaderName, out var headerValue) &&
+ !StringValues.IsNullOrEmpty(headerValue))
+ {
+ return headerValue.ToString();
+ }
+
+ return context.TraceIdentifier;
+}
diff --git a/tests/Furniture.Bff.Application.UnitTests/Furniture.Bff.Application.UnitTests.csproj b/tests/Furniture.Bff.Application.UnitTests/Furniture.Bff.Application.UnitTests.csproj
index 369c362..5092ef0 100644
--- a/tests/Furniture.Bff.Application.UnitTests/Furniture.Bff.Application.UnitTests.csproj
+++ b/tests/Furniture.Bff.Application.UnitTests/Furniture.Bff.Application.UnitTests.csproj
@@ -7,6 +7,7 @@
+
diff --git a/tests/Furniture.Bff.Application.UnitTests/RuntimeWiringTests.cs b/tests/Furniture.Bff.Application.UnitTests/RuntimeWiringTests.cs
new file mode 100644
index 0000000..6e2b68b
--- /dev/null
+++ b/tests/Furniture.Bff.Application.UnitTests/RuntimeWiringTests.cs
@@ -0,0 +1,62 @@
+using Furniture.Bff.Application.Adapters;
+using Furniture.Bff.Application.DependencyInjection;
+using Furniture.Bff.Application.Grpc;
+using Furniture.Bff.Application.Handlers;
+using Furniture.Bff.Contracts.Api;
+using Furniture.Service.Contracts.UseCases;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Furniture.Bff.Application.UnitTests;
+
+public class RuntimeWiringTests
+{
+ [Fact]
+ public async Task AddFurnitureBffApplicationRuntime_WhenResolved_WiresHandler()
+ {
+ var services = new ServiceCollection();
+ services.AddFurnitureBffApplicationRuntime();
+ services.AddSingleton();
+
+ using var provider = services.BuildServiceProvider();
+ var handler = provider.GetRequiredService();
+
+ var response = await handler.HandleAsync(new GetFurnitureAvailabilityApiRequest("FUR-001", "corr-123"));
+
+ Assert.Equal("FUR-001", response.FurnitureId);
+ Assert.Equal("Chair", response.DisplayName);
+ Assert.Equal(7, response.QuantityAvailable);
+ }
+
+ [Fact]
+ public void FurnitureAvailabilityEdgeGrpcContractAdapter_WhenMapped_PreservesValues()
+ {
+ var adapter = new FurnitureAvailabilityEdgeGrpcContractAdapter();
+ var request = new GetFurnitureAvailabilityApiRequest("FUR-002", "corr-456");
+
+ var grpcContract = adapter.ToGrpc(request);
+ var roundtrip = adapter.FromGrpc(grpcContract);
+
+ Assert.Equal("FUR-002", roundtrip.FurnitureId);
+ Assert.Equal("corr-456", roundtrip.CorrelationId);
+ }
+
+ [Fact]
+ public void FurnitureAvailabilityEdgeGrpcContractAdapter_WhenCorrelationMissing_GeneratesCorrelation()
+ {
+ var adapter = new FurnitureAvailabilityEdgeGrpcContractAdapter();
+ var grpcContract = new GetFurnitureAvailabilityEdgeGrpcContract("FUR-003", string.Empty);
+
+ var mapped = adapter.FromGrpc(grpcContract);
+
+ Assert.Equal("FUR-003", mapped.FurnitureId);
+ Assert.NotEmpty(mapped.CorrelationId);
+ }
+
+ private sealed class FakeFurnitureServiceClient : IFurnitureServiceClient
+ {
+ public Task GetAvailabilityAsync(GetFurnitureAvailabilityRequest request)
+ {
+ return Task.FromResult(new GetFurnitureAvailabilityResponse(request.FurnitureId, "Chair", 7));
+ }
+ }
+}