diff --git a/.gitignore b/.gitignore
index 31c7257..89d521f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,8 @@
.tasks/
.agile/
+bin/
+obj/
+TestResults/
+.vs/
+*.user
+*.suo
diff --git a/Furniture.Domain.slnx b/Furniture.Domain.slnx
new file mode 100644
index 0000000..aa254fa
--- /dev/null
+++ b/Furniture.Domain.slnx
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/docs/migration/behavior-invariants.md b/docs/migration/behavior-invariants.md
index 0233310..ba3889c 100644
--- a/docs/migration/behavior-invariants.md
+++ b/docs/migration/behavior-invariants.md
@@ -4,6 +4,8 @@
- Availability decision outcome remains unchanged for equivalent inputs.
- Correlation propagation behavior remains unchanged.
- Transport contracts stay stable at service boundary.
+- Missing display names map to `Unknown Furniture`.
+- Negative inventory quantities are clamped to `0`.
## Validation Approach
- Compare pre/post extraction contract examples.
diff --git a/src/Furniture.Domain/Contracts/FurnitureAvailabilityDecisionRequest.cs b/src/Furniture.Domain/Contracts/FurnitureAvailabilityDecisionRequest.cs
new file mode 100644
index 0000000..c8ea067
--- /dev/null
+++ b/src/Furniture.Domain/Contracts/FurnitureAvailabilityDecisionRequest.cs
@@ -0,0 +1,8 @@
+namespace Furniture.Domain.Contracts;
+
+///
+/// Domain input for furniture availability decisions.
+///
+/// Furniture identifier.
+/// Correlation identifier.
+public sealed record FurnitureAvailabilityDecisionRequest(string FurnitureId, string CorrelationId);
diff --git a/src/Furniture.Domain/Contracts/FurnitureAvailabilityDecisionResponse.cs b/src/Furniture.Domain/Contracts/FurnitureAvailabilityDecisionResponse.cs
new file mode 100644
index 0000000..e0e8a50
--- /dev/null
+++ b/src/Furniture.Domain/Contracts/FurnitureAvailabilityDecisionResponse.cs
@@ -0,0 +1,12 @@
+namespace Furniture.Domain.Contracts;
+
+///
+/// Domain output for furniture availability decisions.
+///
+/// Furniture identifier.
+/// Furniture display name.
+/// Quantity available.
+public sealed record FurnitureAvailabilityDecisionResponse(
+ string FurnitureId,
+ string DisplayName,
+ int QuantityAvailable);
diff --git a/src/Furniture.Domain/Conventions/FurnitureDomainPackageContract.cs b/src/Furniture.Domain/Conventions/FurnitureDomainPackageContract.cs
new file mode 100644
index 0000000..0f19aec
--- /dev/null
+++ b/src/Furniture.Domain/Conventions/FurnitureDomainPackageContract.cs
@@ -0,0 +1,8 @@
+namespace Furniture.Domain.Conventions;
+
+///
+/// Marker type for furniture-domain package discovery.
+///
+public sealed class FurnitureDomainPackageContract
+{
+}
diff --git a/src/Furniture.Domain/Decisions/FurnitureAvailabilityDecisionService.cs b/src/Furniture.Domain/Decisions/FurnitureAvailabilityDecisionService.cs
new file mode 100644
index 0000000..c9a9a24
--- /dev/null
+++ b/src/Furniture.Domain/Decisions/FurnitureAvailabilityDecisionService.cs
@@ -0,0 +1,63 @@
+using BuildingBlock.Catalog.Contracts.Conventions;
+using BuildingBlock.Catalog.Contracts.Products;
+using BuildingBlock.Catalog.Contracts.Responses;
+using BuildingBlock.Inventory.Contracts.Conventions;
+using BuildingBlock.Inventory.Contracts.Requests;
+using BuildingBlock.Inventory.Contracts.Responses;
+using Furniture.Domain.Contracts;
+
+namespace Furniture.Domain.Decisions;
+
+///
+/// Default domain implementation for furniture availability composition decisions.
+///
+public sealed class FurnitureAvailabilityDecisionService : IFurnitureAvailabilityDecisionService
+{
+ private const string ContractVersion = "1.0.0";
+
+ ///
+ public InventoryItemLookupRequest BuildInventoryRequest(FurnitureAvailabilityDecisionRequest request)
+ {
+ return new InventoryItemLookupRequest(
+ new InventoryContractEnvelope(ContractVersion, ResolveCorrelationId(request.CorrelationId)),
+ request.FurnitureId);
+ }
+
+ ///
+ public ProductContract BuildCatalogRequest(FurnitureAvailabilityDecisionRequest request)
+ {
+ return new ProductContract(
+ new CatalogContractEnvelope(ContractVersion, ResolveCorrelationId(request.CorrelationId)),
+ request.FurnitureId,
+ string.Empty);
+ }
+
+ ///
+ public FurnitureAvailabilityDecisionResponse ComposeResponse(
+ FurnitureAvailabilityDecisionRequest request,
+ ProductContractResponse catalogResponse,
+ InventoryItemLookupResponse inventoryResponse)
+ {
+ var displayName = string.IsNullOrWhiteSpace(catalogResponse.DisplayName)
+ ? "Unknown Furniture"
+ : catalogResponse.DisplayName;
+ var quantityAvailable = inventoryResponse.QuantityAvailable < 0
+ ? 0
+ : inventoryResponse.QuantityAvailable;
+
+ return new FurnitureAvailabilityDecisionResponse(
+ request.FurnitureId,
+ displayName,
+ quantityAvailable);
+ }
+
+ private static string ResolveCorrelationId(string correlationId)
+ {
+ if (!string.IsNullOrWhiteSpace(correlationId))
+ {
+ return correlationId;
+ }
+
+ return $"corr-{Guid.NewGuid():N}";
+ }
+}
diff --git a/src/Furniture.Domain/Decisions/IFurnitureAvailabilityDecisionService.cs b/src/Furniture.Domain/Decisions/IFurnitureAvailabilityDecisionService.cs
new file mode 100644
index 0000000..5a2e6c2
--- /dev/null
+++ b/src/Furniture.Domain/Decisions/IFurnitureAvailabilityDecisionService.cs
@@ -0,0 +1,31 @@
+using BuildingBlock.Catalog.Contracts.Products;
+using BuildingBlock.Catalog.Contracts.Responses;
+using BuildingBlock.Inventory.Contracts.Requests;
+using BuildingBlock.Inventory.Contracts.Responses;
+using Furniture.Domain.Contracts;
+
+namespace Furniture.Domain.Decisions;
+
+///
+/// Defines domain decision boundary for furniture availability composition.
+///
+public interface IFurnitureAvailabilityDecisionService
+{
+ ///
+ /// Creates inventory capability request from furniture availability input.
+ ///
+ InventoryItemLookupRequest BuildInventoryRequest(FurnitureAvailabilityDecisionRequest request);
+
+ ///
+ /// Creates catalog capability request from furniture availability input.
+ ///
+ ProductContract BuildCatalogRequest(FurnitureAvailabilityDecisionRequest request);
+
+ ///
+ /// Composes final furniture availability response from capability responses.
+ ///
+ FurnitureAvailabilityDecisionResponse ComposeResponse(
+ FurnitureAvailabilityDecisionRequest request,
+ ProductContractResponse catalogResponse,
+ InventoryItemLookupResponse inventoryResponse);
+}
diff --git a/src/Furniture.Domain/Furniture.Domain.csproj b/src/Furniture.Domain/Furniture.Domain.csproj
new file mode 100644
index 0000000..911464b
--- /dev/null
+++ b/src/Furniture.Domain/Furniture.Domain.csproj
@@ -0,0 +1,11 @@
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
diff --git a/tests/Furniture.Domain.UnitTests/Furniture.Domain.UnitTests.csproj b/tests/Furniture.Domain.UnitTests/Furniture.Domain.UnitTests.csproj
new file mode 100644
index 0000000..b24e147
--- /dev/null
+++ b/tests/Furniture.Domain.UnitTests/Furniture.Domain.UnitTests.csproj
@@ -0,0 +1,20 @@
+
+
+ net10.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Furniture.Domain.UnitTests/FurnitureAvailabilityDecisionServiceTests.cs b/tests/Furniture.Domain.UnitTests/FurnitureAvailabilityDecisionServiceTests.cs
new file mode 100644
index 0000000..3dbc098
--- /dev/null
+++ b/tests/Furniture.Domain.UnitTests/FurnitureAvailabilityDecisionServiceTests.cs
@@ -0,0 +1,92 @@
+using BuildingBlock.Catalog.Contracts.Conventions;
+using BuildingBlock.Catalog.Contracts.Responses;
+using BuildingBlock.Inventory.Contracts.Conventions;
+using BuildingBlock.Inventory.Contracts.Responses;
+using Furniture.Domain.Contracts;
+using Furniture.Domain.Decisions;
+
+namespace Furniture.Domain.UnitTests;
+
+public class FurnitureAvailabilityDecisionServiceTests
+{
+ [Fact]
+ public void BuildCatalogRequest_WhenCalled_UsesFurnitureIdAsProductId()
+ {
+ var service = new FurnitureAvailabilityDecisionService();
+
+ var request = service.BuildCatalogRequest(new FurnitureAvailabilityDecisionRequest("FUR-001", "corr-001"));
+
+ Assert.Equal("FUR-001", request.ProductId);
+ Assert.Equal("corr-001", request.Envelope.CorrelationId);
+ }
+
+ [Fact]
+ public void BuildInventoryRequest_WhenCorrelationMissing_GeneratesCorrelation()
+ {
+ var service = new FurnitureAvailabilityDecisionService();
+
+ var request = service.BuildInventoryRequest(new FurnitureAvailabilityDecisionRequest("FUR-001", string.Empty));
+
+ Assert.Equal("FUR-001", request.ItemCode);
+ Assert.NotEmpty(request.Envelope.CorrelationId);
+ }
+
+ [Fact]
+ public void ComposeResponse_WhenCalled_UsesCatalogAndInventoryValues()
+ {
+ var service = new FurnitureAvailabilityDecisionService();
+ var request = new FurnitureAvailabilityDecisionRequest("FUR-001", "corr-001");
+ var catalog = new ProductContractResponse(
+ new CatalogContractEnvelope("1.0.0", "corr-001"),
+ "FUR-001",
+ "Chair");
+ var inventory = new InventoryItemLookupResponse(
+ new InventoryContractEnvelope("1.0.0", "corr-001"),
+ "FUR-001",
+ 12);
+
+ var response = service.ComposeResponse(request, catalog, inventory);
+
+ Assert.Equal("FUR-001", response.FurnitureId);
+ Assert.Equal("Chair", response.DisplayName);
+ Assert.Equal(12, response.QuantityAvailable);
+ }
+
+ [Fact]
+ public void ComposeResponse_WhenCatalogDisplayNameMissing_UsesFallbackName()
+ {
+ var service = new FurnitureAvailabilityDecisionService();
+ var request = new FurnitureAvailabilityDecisionRequest("FUR-004", "corr-004");
+ var catalog = new ProductContractResponse(
+ new CatalogContractEnvelope("1.0.0", "corr-004"),
+ "FUR-004",
+ string.Empty);
+ var inventory = new InventoryItemLookupResponse(
+ new InventoryContractEnvelope("1.0.0", "corr-004"),
+ "FUR-004",
+ 5);
+
+ var response = service.ComposeResponse(request, catalog, inventory);
+
+ Assert.Equal("Unknown Furniture", response.DisplayName);
+ }
+
+ [Fact]
+ public void ComposeResponse_WhenInventoryNegative_ClampsQuantityToZero()
+ {
+ var service = new FurnitureAvailabilityDecisionService();
+ var request = new FurnitureAvailabilityDecisionRequest("FUR-005", "corr-005");
+ var catalog = new ProductContractResponse(
+ new CatalogContractEnvelope("1.0.0", "corr-005"),
+ "FUR-005",
+ "Shelf");
+ var inventory = new InventoryItemLookupResponse(
+ new InventoryContractEnvelope("1.0.0", "corr-005"),
+ "FUR-005",
+ -3);
+
+ var response = service.ComposeResponse(request, catalog, inventory);
+
+ Assert.Equal(0, response.QuantityAvailable);
+ }
+}