diff --git a/docs/architecture/dal-domain-alignment.md b/docs/architecture/dal-domain-alignment.md
new file mode 100644
index 0000000..1261cc9
--- /dev/null
+++ b/docs/architecture/dal-domain-alignment.md
@@ -0,0 +1,13 @@
+# Thalos DAL Domain Alignment
+
+## Goal
+Align DAL with thalos-domain abstractions while keeping DAL technical.
+
+## DAL Responsibilities
+- Identity persistence and retrieval
+- Technical data translation
+- Provider/repository boundaries
+
+## Prohibited
+- Identity policy decision ownership
+- Service orchestration concerns
diff --git a/docs/dal/identity-provider-boundaries.md b/docs/dal/identity-provider-boundaries.md
index f57bc23..d47bf45 100644
--- a/docs/dal/identity-provider-boundaries.md
+++ b/docs/dal/identity-provider-boundaries.md
@@ -16,3 +16,7 @@
- Provider boundaries remain internal to Thalos DAL.
- DAL interfaces expose only transport-neutral contracts and read ports.
- Identity abstractions remain Thalos-owned.
+- Runtime provider routes currently support:
+ - `InternalJwt`
+ - `AzureAd`
+ - `Google`
diff --git a/docs/migration/dal-port-alignment-map.md b/docs/migration/dal-port-alignment-map.md
new file mode 100644
index 0000000..dbbc0be
--- /dev/null
+++ b/docs/migration/dal-port-alignment-map.md
@@ -0,0 +1,6 @@
+# Thalos DAL Port Alignment Map
+
+## Alignment Areas
+- DAL read/write ports map to domain contracts.
+- Technical DTO translation remains in DAL adapters.
+- Domain policy semantics are not reimplemented in DAL.
diff --git a/docs/migration/technical-mapping-rules.md b/docs/migration/technical-mapping-rules.md
new file mode 100644
index 0000000..98161ed
--- /dev/null
+++ b/docs/migration/technical-mapping-rules.md
@@ -0,0 +1,6 @@
+# Thalos DAL Technical Mapping Rules
+
+## Rules
+- Mapping logic remains technical and deterministic.
+- No policy evaluation branching in DAL mapping layer.
+- Correlation and metadata pass-through remains unchanged.
diff --git a/src/Thalos.DAL/Adapters/IdentityDalGrpcContractAdapter.cs b/src/Thalos.DAL/Adapters/IdentityDalGrpcContractAdapter.cs
new file mode 100644
index 0000000..f07d917
--- /dev/null
+++ b/src/Thalos.DAL/Adapters/IdentityDalGrpcContractAdapter.cs
@@ -0,0 +1,47 @@
+using Core.Blueprint.Common.Runtime;
+using Thalos.DAL.Contracts;
+using Thalos.DAL.Grpc;
+
+namespace Thalos.DAL.Adapters;
+
+///
+/// Default adapter implementation for DAL gRPC contract translation.
+///
+public sealed class IdentityDalGrpcContractAdapter(IBlueprintSystemClock clock) : IIdentityDalGrpcContractAdapter
+{
+ ///
+ public IdentityPolicyDalGrpcContract ToGrpcPolicyRequest(IdentityPolicyLookupRequest request)
+ {
+ return new IdentityPolicyDalGrpcContract(request.SubjectId, request.TenantId, request.PermissionCode);
+ }
+
+ ///
+ public IdentityPolicyLookupRequest FromGrpcPolicyRequest(IdentityPolicyDalGrpcContract contract)
+ {
+ return new IdentityPolicyLookupRequest(
+ CreateEnvelope(),
+ contract.SubjectId,
+ contract.TenantId,
+ contract.PermissionCode);
+ }
+
+ ///
+ public IdentityTokenDalGrpcContract ToGrpcTokenRequest(IdentityTokenLookupRequest request)
+ {
+ return new IdentityTokenDalGrpcContract(request.SubjectId, request.TenantId);
+ }
+
+ ///
+ public IdentityTokenLookupRequest FromGrpcTokenRequest(IdentityTokenDalGrpcContract contract)
+ {
+ return new IdentityTokenLookupRequest(
+ CreateEnvelope(),
+ contract.SubjectId,
+ contract.TenantId);
+ }
+
+ private IdentityContractEnvelope CreateEnvelope()
+ {
+ return new IdentityContractEnvelope("1.0.0", $"corr-{clock.UtcNow:yyyyMMddHHmmssfff}");
+ }
+}
diff --git a/src/Thalos.DAL/Contracts/IdentityPermissionRecord.cs b/src/Thalos.DAL/Contracts/IdentityPermissionRecord.cs
index 8ee10f2..10f8083 100644
--- a/src/Thalos.DAL/Contracts/IdentityPermissionRecord.cs
+++ b/src/Thalos.DAL/Contracts/IdentityPermissionRecord.cs
@@ -1,3 +1,5 @@
+using BuildingBlock.Identity.Contracts.Conventions;
+
namespace Thalos.DAL.Contracts;
///
@@ -6,7 +8,9 @@ namespace Thalos.DAL.Contracts;
/// Contract envelope metadata.
/// Permission code identifier.
/// Role code that grants the permission.
+/// Auth provider for the permission grant.
public sealed record IdentityPermissionRecord(
IdentityContractEnvelope Envelope,
string PermissionCode,
- string SourceRoleCode);
+ string SourceRoleCode,
+ IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt);
diff --git a/src/Thalos.DAL/Contracts/IdentityPermissionSetLookupRequest.cs b/src/Thalos.DAL/Contracts/IdentityPermissionSetLookupRequest.cs
index d41d316..78dff81 100644
--- a/src/Thalos.DAL/Contracts/IdentityPermissionSetLookupRequest.cs
+++ b/src/Thalos.DAL/Contracts/IdentityPermissionSetLookupRequest.cs
@@ -1,3 +1,5 @@
+using BuildingBlock.Identity.Contracts.Conventions;
+
namespace Thalos.DAL.Contracts;
///
@@ -6,7 +8,9 @@ namespace Thalos.DAL.Contracts;
/// Contract envelope metadata.
/// Identity subject identifier.
/// Tenant scope identifier.
+/// Auth provider for the lookup flow.
public sealed record IdentityPermissionSetLookupRequest(
IdentityContractEnvelope Envelope,
string SubjectId,
- string TenantId);
+ string TenantId,
+ IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt);
diff --git a/src/Thalos.DAL/Contracts/IdentityPolicyLookupRequest.cs b/src/Thalos.DAL/Contracts/IdentityPolicyLookupRequest.cs
index c6a338c..dcac245 100644
--- a/src/Thalos.DAL/Contracts/IdentityPolicyLookupRequest.cs
+++ b/src/Thalos.DAL/Contracts/IdentityPolicyLookupRequest.cs
@@ -1,3 +1,5 @@
+using BuildingBlock.Identity.Contracts.Conventions;
+
namespace Thalos.DAL.Contracts;
///
@@ -7,8 +9,10 @@ namespace Thalos.DAL.Contracts;
/// Identity subject identifier.
/// Tenant scope identifier.
/// Permission code to evaluate.
+/// Auth provider for the lookup flow.
public sealed record IdentityPolicyLookupRequest(
IdentityContractEnvelope Envelope,
string SubjectId,
string TenantId,
- string PermissionCode);
+ string PermissionCode,
+ IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt);
diff --git a/src/Thalos.DAL/Contracts/IdentityPolicyRecord.cs b/src/Thalos.DAL/Contracts/IdentityPolicyRecord.cs
index 4a47f3e..84f80d9 100644
--- a/src/Thalos.DAL/Contracts/IdentityPolicyRecord.cs
+++ b/src/Thalos.DAL/Contracts/IdentityPolicyRecord.cs
@@ -1,3 +1,5 @@
+using BuildingBlock.Identity.Contracts.Conventions;
+
namespace Thalos.DAL.Contracts;
///
@@ -7,8 +9,10 @@ namespace Thalos.DAL.Contracts;
/// Identity subject identifier.
/// Permission code evaluated.
/// Indicates whether policy context is satisfied.
+/// Auth provider used for policy evaluation.
public sealed record IdentityPolicyRecord(
IdentityContractEnvelope Envelope,
string SubjectId,
string PermissionCode,
- bool ContextSatisfied);
+ bool ContextSatisfied,
+ IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt);
diff --git a/src/Thalos.DAL/Contracts/IdentityTokenLookupRequest.cs b/src/Thalos.DAL/Contracts/IdentityTokenLookupRequest.cs
index 034118d..0d1d78e 100644
--- a/src/Thalos.DAL/Contracts/IdentityTokenLookupRequest.cs
+++ b/src/Thalos.DAL/Contracts/IdentityTokenLookupRequest.cs
@@ -1,3 +1,5 @@
+using BuildingBlock.Identity.Contracts.Conventions;
+
namespace Thalos.DAL.Contracts;
///
@@ -6,4 +8,11 @@ namespace Thalos.DAL.Contracts;
/// Contract envelope metadata.
/// Identity subject identifier.
/// Tenant scope identifier.
-public sealed record IdentityTokenLookupRequest(IdentityContractEnvelope Envelope, string SubjectId, string TenantId);
+/// Auth provider for the lookup flow.
+/// External provider token when applicable.
+public sealed record IdentityTokenLookupRequest(
+ IdentityContractEnvelope Envelope,
+ string SubjectId,
+ string TenantId,
+ IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt,
+ string ExternalToken = "");
diff --git a/src/Thalos.DAL/Contracts/IdentityTokenRecord.cs b/src/Thalos.DAL/Contracts/IdentityTokenRecord.cs
index e932ff4..2958a22 100644
--- a/src/Thalos.DAL/Contracts/IdentityTokenRecord.cs
+++ b/src/Thalos.DAL/Contracts/IdentityTokenRecord.cs
@@ -1,3 +1,5 @@
+using BuildingBlock.Identity.Contracts.Conventions;
+
namespace Thalos.DAL.Contracts;
///
@@ -8,9 +10,11 @@ namespace Thalos.DAL.Contracts;
/// Tenant scope identifier.
/// Issued access token value.
/// Token expiration in seconds.
+/// Auth provider used for token issuance.
public sealed record IdentityTokenRecord(
IdentityContractEnvelope Envelope,
string SubjectId,
string TenantId,
string Token,
- int ExpiresInSeconds);
+ int ExpiresInSeconds,
+ IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt);
diff --git a/src/Thalos.DAL/Contracts/IdentityUserLookupRequest.cs b/src/Thalos.DAL/Contracts/IdentityUserLookupRequest.cs
index 8e6d4e4..3e77f55 100644
--- a/src/Thalos.DAL/Contracts/IdentityUserLookupRequest.cs
+++ b/src/Thalos.DAL/Contracts/IdentityUserLookupRequest.cs
@@ -1,3 +1,5 @@
+using BuildingBlock.Identity.Contracts.Conventions;
+
namespace Thalos.DAL.Contracts;
///
@@ -5,4 +7,12 @@ namespace Thalos.DAL.Contracts;
///
/// Contract envelope metadata.
/// Identity subject identifier.
-public sealed record IdentityUserLookupRequest(IdentityContractEnvelope Envelope, string SubjectId);
+/// Tenant identifier.
+/// Auth provider for the lookup flow.
+/// External provider token when applicable.
+public sealed record IdentityUserLookupRequest(
+ IdentityContractEnvelope Envelope,
+ string SubjectId,
+ string TenantId,
+ IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt,
+ string ExternalToken = "");
diff --git a/src/Thalos.DAL/Contracts/IdentityUserRecord.cs b/src/Thalos.DAL/Contracts/IdentityUserRecord.cs
index aa364ac..095f3c5 100644
--- a/src/Thalos.DAL/Contracts/IdentityUserRecord.cs
+++ b/src/Thalos.DAL/Contracts/IdentityUserRecord.cs
@@ -7,8 +7,14 @@ namespace Thalos.DAL.Contracts;
/// Identity subject identifier.
/// Tenant scope identifier.
/// Current user status.
+/// Persisted token projection for subject/tenant.
+/// Persisted token expiration in seconds.
+/// Persisted policy context projection.
public sealed record IdentityUserRecord(
IdentityContractEnvelope Envelope,
string SubjectId,
string TenantId,
- string Status);
+ string Status,
+ string Token,
+ int ExpiresInSeconds,
+ bool ContextSatisfied);
diff --git a/src/Thalos.DAL/DependencyInjection/ThalosDalServiceCollectionExtensions.cs b/src/Thalos.DAL/DependencyInjection/ThalosDalServiceCollectionExtensions.cs
new file mode 100644
index 0000000..caf339b
--- /dev/null
+++ b/src/Thalos.DAL/DependencyInjection/ThalosDalServiceCollectionExtensions.cs
@@ -0,0 +1,46 @@
+using Core.Blueprint.Common.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Thalos.DAL.Adapters;
+using Thalos.DAL.Health;
+using Thalos.DAL.Providers;
+using Thalos.DAL.Providers.InMemory;
+using Thalos.DAL.Repositories;
+
+namespace Thalos.DAL.DependencyInjection;
+
+///
+/// Registers thalos dal runtime provider, repository, and adapter implementations.
+///
+public static class ThalosDalServiceCollectionExtensions
+{
+ ///
+ /// Adds thalos dal runtime implementations aligned with blueprint runtime core.
+ ///
+ /// Service collection.
+ /// Service collection for fluent chaining.
+ public static IServiceCollection AddThalosDalRuntime(this IServiceCollection services)
+ {
+ services.AddBlueprintRuntimeCore();
+
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+
+ return services;
+ }
+}
diff --git a/src/Thalos.DAL/Health/DalDependencyHealthCheck.cs b/src/Thalos.DAL/Health/DalDependencyHealthCheck.cs
new file mode 100644
index 0000000..e6a9f0e
--- /dev/null
+++ b/src/Thalos.DAL/Health/DalDependencyHealthCheck.cs
@@ -0,0 +1,27 @@
+using Core.Blueprint.Common.Runtime;
+using Thalos.DAL.Contracts;
+
+namespace Thalos.DAL.Health;
+
+///
+/// Default DAL dependency health check implementation.
+///
+public sealed class DalDependencyHealthCheck(IBlueprintSystemClock clock) : IDalDependencyHealthCheck
+{
+ ///
+ public Task CheckAsync(CancellationToken cancellationToken = default)
+ {
+ var envelope = new IdentityContractEnvelope("1.0.0", $"corr-{clock.UtcNow:yyyyMMddHHmmssfff}");
+ IReadOnlyList dependencyNames =
+ [
+ "IUserDataProvider",
+ "IRoleDataProvider",
+ "IPermissionDataProvider",
+ "IModuleDataProvider",
+ "ITenantDataProvider"
+ ];
+
+ var status = new DalDependencyHealthStatus(envelope, true, dependencyNames);
+ return Task.FromResult(status);
+ }
+}
diff --git a/src/Thalos.DAL/Providers/InMemory/InMemoryModuleDataProvider.cs b/src/Thalos.DAL/Providers/InMemory/InMemoryModuleDataProvider.cs
new file mode 100644
index 0000000..db80e20
--- /dev/null
+++ b/src/Thalos.DAL/Providers/InMemory/InMemoryModuleDataProvider.cs
@@ -0,0 +1,22 @@
+using Thalos.DAL.Contracts;
+
+namespace Thalos.DAL.Providers.InMemory;
+
+///
+/// In-memory provider for identity module lookup contracts.
+///
+public sealed class InMemoryModuleDataProvider : IModuleDataProvider
+{
+ ///
+ public Task> ReadModulesAsync(
+ IdentityModuleLookupRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ IReadOnlyList records =
+ [
+ new IdentityModuleRecord(request.Envelope, "identity", true)
+ ];
+
+ return Task.FromResult(records);
+ }
+}
diff --git a/src/Thalos.DAL/Providers/InMemory/InMemoryPermissionDataProvider.cs b/src/Thalos.DAL/Providers/InMemory/InMemoryPermissionDataProvider.cs
new file mode 100644
index 0000000..5cb7626
--- /dev/null
+++ b/src/Thalos.DAL/Providers/InMemory/InMemoryPermissionDataProvider.cs
@@ -0,0 +1,23 @@
+using Thalos.DAL.Contracts;
+
+namespace Thalos.DAL.Providers.InMemory;
+
+///
+/// In-memory provider for identity permission lookup contracts.
+///
+public sealed class InMemoryPermissionDataProvider : IPermissionDataProvider
+{
+ ///
+ public Task> ReadPermissionsAsync(
+ IdentityPermissionSetLookupRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ IReadOnlyList records =
+ [
+ new IdentityPermissionRecord(request.Envelope, "identity.token.issue", "identity.admin"),
+ new IdentityPermissionRecord(request.Envelope, "identity.policy.evaluate", "identity.admin")
+ ];
+
+ return Task.FromResult(records);
+ }
+}
diff --git a/src/Thalos.DAL/Providers/InMemory/InMemoryRoleDataProvider.cs b/src/Thalos.DAL/Providers/InMemory/InMemoryRoleDataProvider.cs
new file mode 100644
index 0000000..2b7f3ac
--- /dev/null
+++ b/src/Thalos.DAL/Providers/InMemory/InMemoryRoleDataProvider.cs
@@ -0,0 +1,22 @@
+using Thalos.DAL.Contracts;
+
+namespace Thalos.DAL.Providers.InMemory;
+
+///
+/// In-memory provider for identity role lookup contracts.
+///
+public sealed class InMemoryRoleDataProvider : IRoleDataProvider
+{
+ ///
+ public Task> ReadRolesAsync(
+ IdentityRoleLookupRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ IReadOnlyList records =
+ [
+ new IdentityRoleRecord(request.Envelope, "identity.admin", request.TenantId)
+ ];
+
+ return Task.FromResult(records);
+ }
+}
diff --git a/src/Thalos.DAL/Providers/InMemory/InMemoryTenantDataProvider.cs b/src/Thalos.DAL/Providers/InMemory/InMemoryTenantDataProvider.cs
new file mode 100644
index 0000000..e61cef3
--- /dev/null
+++ b/src/Thalos.DAL/Providers/InMemory/InMemoryTenantDataProvider.cs
@@ -0,0 +1,23 @@
+using Thalos.DAL.Contracts;
+
+namespace Thalos.DAL.Providers.InMemory;
+
+///
+/// In-memory provider for identity tenant lookup contracts.
+///
+public sealed class InMemoryTenantDataProvider : ITenantDataProvider
+{
+ ///
+ public Task ReadTenantAsync(
+ IdentityTenantLookupRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var record = new IdentityTenantRecord(
+ request.Envelope,
+ request.TenantId,
+ $"tenant-{request.TenantId}",
+ true);
+
+ return Task.FromResult(record);
+ }
+}
diff --git a/src/Thalos.DAL/Providers/InMemory/InMemoryUserDataProvider.cs b/src/Thalos.DAL/Providers/InMemory/InMemoryUserDataProvider.cs
new file mode 100644
index 0000000..66cd15d
--- /dev/null
+++ b/src/Thalos.DAL/Providers/InMemory/InMemoryUserDataProvider.cs
@@ -0,0 +1,31 @@
+using Thalos.DAL.Contracts;
+
+namespace Thalos.DAL.Providers.InMemory;
+
+///
+/// In-memory provider for identity user lookup contracts.
+///
+public sealed class InMemoryUserDataProvider : IUserDataProvider
+{
+ ///
+ public Task ReadUserAsync(
+ IdentityUserLookupRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ if (request.SubjectId.StartsWith("missing-", StringComparison.OrdinalIgnoreCase))
+ {
+ return Task.FromResult(null);
+ }
+
+ var record = new IdentityUserRecord(
+ request.Envelope,
+ request.SubjectId,
+ request.TenantId,
+ "active",
+ $"{request.SubjectId}:{request.TenantId}:token",
+ 1800,
+ true);
+
+ return Task.FromResult(record);
+ }
+}
diff --git a/src/Thalos.DAL/Providers/ProviderPermissionDataProviders.cs b/src/Thalos.DAL/Providers/ProviderPermissionDataProviders.cs
new file mode 100644
index 0000000..ba59994
--- /dev/null
+++ b/src/Thalos.DAL/Providers/ProviderPermissionDataProviders.cs
@@ -0,0 +1,89 @@
+using BuildingBlock.Identity.Contracts.Conventions;
+using Thalos.DAL.Contracts;
+
+namespace Thalos.DAL.Providers;
+
+///
+/// Internal JWT permission provider implementation.
+///
+public sealed class InternalJwtPermissionDataProvider : IPermissionDataProvider
+{
+ ///
+ public Task> ReadPermissionsAsync(
+ IdentityPermissionSetLookupRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ IReadOnlyList records =
+ [
+ new IdentityPermissionRecord(request.Envelope, "identity.token.issue", "identity.admin", IdentityAuthProvider.InternalJwt),
+ new IdentityPermissionRecord(request.Envelope, "identity.policy.evaluate", "identity.admin", IdentityAuthProvider.InternalJwt)
+ ];
+
+ return Task.FromResult(records);
+ }
+}
+
+///
+/// Azure AD permission provider implementation.
+///
+public sealed class AzureAdPermissionDataProvider : IPermissionDataProvider
+{
+ ///
+ public Task> ReadPermissionsAsync(
+ IdentityPermissionSetLookupRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ IReadOnlyList records =
+ [
+ new IdentityPermissionRecord(request.Envelope, "identity.token.issue", "identity.azure.user", IdentityAuthProvider.AzureAd),
+ new IdentityPermissionRecord(request.Envelope, "identity.policy.evaluate", "identity.azure.user", IdentityAuthProvider.AzureAd),
+ new IdentityPermissionRecord(request.Envelope, "identity.oauth.exchange", "identity.azure.user", IdentityAuthProvider.AzureAd)
+ ];
+
+ return Task.FromResult(records);
+ }
+}
+
+///
+/// Google permission provider implementation.
+///
+public sealed class GooglePermissionDataProvider : IPermissionDataProvider
+{
+ ///
+ public Task> ReadPermissionsAsync(
+ IdentityPermissionSetLookupRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ IReadOnlyList records =
+ [
+ new IdentityPermissionRecord(request.Envelope, "identity.token.issue", "identity.google.user", IdentityAuthProvider.Google),
+ new IdentityPermissionRecord(request.Envelope, "identity.policy.evaluate", "identity.google.user", IdentityAuthProvider.Google),
+ new IdentityPermissionRecord(request.Envelope, "identity.oauth.exchange", "identity.google.user", IdentityAuthProvider.Google)
+ ];
+
+ return Task.FromResult(records);
+ }
+}
+
+///
+/// Routes permission lookups to the matching provider implementation.
+///
+public sealed class RoutedPermissionDataProvider(
+ InternalJwtPermissionDataProvider internalJwtProvider,
+ AzureAdPermissionDataProvider azureProvider,
+ GooglePermissionDataProvider googleProvider) : IPermissionDataProvider
+{
+ ///
+ public Task> ReadPermissionsAsync(
+ IdentityPermissionSetLookupRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ return request.Provider switch
+ {
+ IdentityAuthProvider.InternalJwt => internalJwtProvider.ReadPermissionsAsync(request, cancellationToken),
+ IdentityAuthProvider.AzureAd => azureProvider.ReadPermissionsAsync(request, cancellationToken),
+ IdentityAuthProvider.Google => googleProvider.ReadPermissionsAsync(request, cancellationToken),
+ _ => Task.FromResult>([])
+ };
+ }
+}
diff --git a/src/Thalos.DAL/Providers/ProviderUserDataProviders.cs b/src/Thalos.DAL/Providers/ProviderUserDataProviders.cs
new file mode 100644
index 0000000..3905fd2
--- /dev/null
+++ b/src/Thalos.DAL/Providers/ProviderUserDataProviders.cs
@@ -0,0 +1,143 @@
+using BuildingBlock.Identity.Contracts.Conventions;
+using Thalos.DAL.Contracts;
+
+namespace Thalos.DAL.Providers;
+
+///
+/// Internal JWT provider implementation for identity user reads.
+///
+public sealed class InternalJwtUserDataProvider : IUserDataProvider
+{
+ ///
+ public Task ReadUserAsync(
+ IdentityUserLookupRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ if (request.SubjectId.StartsWith("missing-", StringComparison.OrdinalIgnoreCase))
+ {
+ return Task.FromResult(null);
+ }
+
+ var record = new IdentityUserRecord(
+ request.Envelope,
+ request.SubjectId,
+ request.TenantId,
+ "active",
+ $"{request.SubjectId}:{request.TenantId}:token",
+ 1800,
+ true);
+
+ return Task.FromResult(record);
+ }
+}
+
+///
+/// Azure AD provider implementation for identity user reads.
+///
+public sealed class AzureAdUserDataProvider : IUserDataProvider
+{
+ ///
+ public Task ReadUserAsync(
+ IdentityUserLookupRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var subjectId = ResolveSubjectId(request, "azure-sub");
+ if (string.IsNullOrWhiteSpace(subjectId))
+ {
+ return Task.FromResult(null);
+ }
+
+ var record = new IdentityUserRecord(
+ request.Envelope,
+ subjectId,
+ request.TenantId,
+ "active",
+ $"azure:{subjectId}:{request.TenantId}:token",
+ 3600,
+ true);
+
+ return Task.FromResult(record);
+ }
+
+ private static string ResolveSubjectId(IdentityUserLookupRequest request, string prefix)
+ {
+ if (!string.IsNullOrWhiteSpace(request.SubjectId))
+ {
+ return request.SubjectId;
+ }
+
+ if (string.IsNullOrWhiteSpace(request.ExternalToken))
+ {
+ return string.Empty;
+ }
+
+ return $"{prefix}-{Math.Abs(request.ExternalToken.GetHashCode(StringComparison.Ordinal))}";
+ }
+}
+
+///
+/// Google provider implementation for identity user reads.
+///
+public sealed class GoogleUserDataProvider : IUserDataProvider
+{
+ ///
+ public Task ReadUserAsync(
+ IdentityUserLookupRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var subjectId = ResolveSubjectId(request, "google-sub");
+ if (string.IsNullOrWhiteSpace(subjectId))
+ {
+ return Task.FromResult(null);
+ }
+
+ var record = new IdentityUserRecord(
+ request.Envelope,
+ subjectId,
+ request.TenantId,
+ "active",
+ $"google:{subjectId}:{request.TenantId}:token",
+ 3000,
+ true);
+
+ return Task.FromResult(record);
+ }
+
+ private static string ResolveSubjectId(IdentityUserLookupRequest request, string prefix)
+ {
+ if (!string.IsNullOrWhiteSpace(request.SubjectId))
+ {
+ return request.SubjectId;
+ }
+
+ if (string.IsNullOrWhiteSpace(request.ExternalToken))
+ {
+ return string.Empty;
+ }
+
+ return $"{prefix}-{Math.Abs(request.ExternalToken.GetHashCode(StringComparison.Ordinal))}";
+ }
+}
+
+///
+/// Routes user lookups to the matching provider implementation.
+///
+public sealed class RoutedUserDataProvider(
+ InternalJwtUserDataProvider internalJwtProvider,
+ AzureAdUserDataProvider azureProvider,
+ GoogleUserDataProvider googleProvider) : IUserDataProvider
+{
+ ///
+ public Task ReadUserAsync(
+ IdentityUserLookupRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ return request.Provider switch
+ {
+ IdentityAuthProvider.InternalJwt => internalJwtProvider.ReadUserAsync(request, cancellationToken),
+ IdentityAuthProvider.AzureAd => azureProvider.ReadUserAsync(request, cancellationToken),
+ IdentityAuthProvider.Google => googleProvider.ReadUserAsync(request, cancellationToken),
+ _ => Task.FromResult(null)
+ };
+ }
+}
diff --git a/src/Thalos.DAL/Repositories/IdentityRepository.cs b/src/Thalos.DAL/Repositories/IdentityRepository.cs
new file mode 100644
index 0000000..75ea161
--- /dev/null
+++ b/src/Thalos.DAL/Repositories/IdentityRepository.cs
@@ -0,0 +1,70 @@
+using Thalos.DAL.Contracts;
+using Thalos.DAL.Providers;
+
+namespace Thalos.DAL.Repositories;
+
+///
+/// Default identity repository implementation composed from DAL providers.
+///
+public sealed class IdentityRepository(
+ IUserDataProvider userDataProvider,
+ IPermissionDataProvider permissionDataProvider) : IIdentityRepository
+{
+ ///
+ public async Task ReadIdentityTokenAsync(
+ IdentityTokenLookupRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var userRequest = new IdentityUserLookupRequest(
+ request.Envelope,
+ request.SubjectId,
+ request.TenantId,
+ request.Provider,
+ request.ExternalToken);
+ var userRecord = await userDataProvider.ReadUserAsync(userRequest, cancellationToken);
+ if (userRecord is null)
+ {
+ return null;
+ }
+
+ return new IdentityTokenRecord(
+ request.Envelope,
+ userRecord.SubjectId,
+ request.TenantId,
+ userRecord.Token,
+ userRecord.ExpiresInSeconds,
+ request.Provider);
+ }
+
+ ///
+ public async Task ReadIdentityPolicyAsync(
+ IdentityPolicyLookupRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ var userRequest = new IdentityUserLookupRequest(
+ request.Envelope,
+ request.SubjectId,
+ request.TenantId,
+ request.Provider);
+ var userRecord = await userDataProvider.ReadUserAsync(userRequest, cancellationToken);
+ if (userRecord is null)
+ {
+ return null;
+ }
+
+ return new IdentityPolicyRecord(
+ request.Envelope,
+ userRecord.SubjectId,
+ request.PermissionCode,
+ userRecord.ContextSatisfied,
+ request.Provider);
+ }
+
+ ///
+ public Task> ReadPermissionSetAsync(
+ IdentityPermissionSetLookupRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ return permissionDataProvider.ReadPermissionsAsync(request, cancellationToken);
+ }
+}
diff --git a/src/Thalos.DAL/Thalos.DAL.csproj b/src/Thalos.DAL/Thalos.DAL.csproj
index 04a4bbc..4eb2668 100644
--- a/src/Thalos.DAL/Thalos.DAL.csproj
+++ b/src/Thalos.DAL/Thalos.DAL.csproj
@@ -5,6 +5,8 @@
enable
+
+
diff --git a/tests/Thalos.DAL.UnitTests/ContractShapeTests.cs b/tests/Thalos.DAL.UnitTests/ContractShapeTests.cs
index 58949ba..358e158 100644
--- a/tests/Thalos.DAL.UnitTests/ContractShapeTests.cs
+++ b/tests/Thalos.DAL.UnitTests/ContractShapeTests.cs
@@ -16,6 +16,7 @@ public class ContractShapeTests
Assert.Equal("user-1", request.SubjectId);
Assert.Equal("tenant-1", request.TenantId);
Assert.Equal("identity.token.issue", request.PermissionCode);
+ Assert.Equal(BuildingBlock.Identity.Contracts.Conventions.IdentityAuthProvider.InternalJwt, request.Provider);
}
[Fact]
@@ -30,6 +31,7 @@ public class ContractShapeTests
Assert.Equal("tenant-1", record.TenantId);
Assert.Equal("token-xyz", record.Token);
Assert.Equal(1800, record.ExpiresInSeconds);
+ Assert.Equal(BuildingBlock.Identity.Contracts.Conventions.IdentityAuthProvider.InternalJwt, record.Provider);
}
[Fact]
diff --git a/tests/Thalos.DAL.UnitTests/RuntimeWiringTests.cs b/tests/Thalos.DAL.UnitTests/RuntimeWiringTests.cs
new file mode 100644
index 0000000..a07cb27
--- /dev/null
+++ b/tests/Thalos.DAL.UnitTests/RuntimeWiringTests.cs
@@ -0,0 +1,88 @@
+using Microsoft.Extensions.DependencyInjection;
+using Thalos.DAL.Adapters;
+using Thalos.DAL.Contracts;
+using Thalos.DAL.DependencyInjection;
+using Thalos.DAL.Health;
+using Thalos.DAL.Repositories;
+
+namespace Thalos.DAL.UnitTests;
+
+public class RuntimeWiringTests
+{
+ [Fact]
+ public async Task AddThalosDalRuntime_WhenResolved_WiresRepositoryAndProviders()
+ {
+ var services = new ServiceCollection();
+ services.AddThalosDalRuntime();
+
+ using var provider = services.BuildServiceProvider();
+ var repository = provider.GetRequiredService();
+ var request = new IdentityTokenLookupRequest(
+ new IdentityContractEnvelope("1.0.0", "corr-123"),
+ "user-1",
+ "tenant-1");
+
+ var response = await repository.ReadIdentityTokenAsync(request);
+
+ Assert.NotNull(response);
+ Assert.Equal("user-1", response.SubjectId);
+ Assert.Equal("tenant-1", response.TenantId);
+ Assert.Equal(1800, response.ExpiresInSeconds);
+ Assert.Equal(BuildingBlock.Identity.Contracts.Conventions.IdentityAuthProvider.InternalJwt, response.Provider);
+ }
+
+ [Fact]
+ public async Task AddThalosDalRuntime_WhenExternalProviderUsed_ResolvesProviderSpecificToken()
+ {
+ var services = new ServiceCollection();
+ services.AddThalosDalRuntime();
+
+ using var provider = services.BuildServiceProvider();
+ var repository = provider.GetRequiredService();
+ var request = new IdentityTokenLookupRequest(
+ new IdentityContractEnvelope("1.0.0", "corr-ext"),
+ string.Empty,
+ "tenant-2",
+ BuildingBlock.Identity.Contracts.Conventions.IdentityAuthProvider.AzureAd,
+ "external-azure-token");
+
+ var response = await repository.ReadIdentityTokenAsync(request);
+
+ Assert.NotNull(response);
+ Assert.Equal(BuildingBlock.Identity.Contracts.Conventions.IdentityAuthProvider.AzureAd, response.Provider);
+ Assert.StartsWith("azure:", response.Token);
+ }
+
+ [Fact]
+ public void AddThalosDalRuntime_WhenResolved_WiresGrpcContractAdapter()
+ {
+ var services = new ServiceCollection();
+ services.AddThalosDalRuntime();
+
+ using var provider = services.BuildServiceProvider();
+ var adapter = provider.GetRequiredService();
+ var grpcContract = new Thalos.DAL.Grpc.IdentityTokenDalGrpcContract("user-2", "tenant-2");
+
+ var request = adapter.FromGrpcTokenRequest(grpcContract);
+
+ Assert.Equal("user-2", request.SubjectId);
+ Assert.Equal("tenant-2", request.TenantId);
+ Assert.NotEmpty(request.Envelope.CorrelationId);
+ }
+
+ [Fact]
+ public async Task AddThalosDalRuntime_WhenResolved_WiresDependencyHealthCheck()
+ {
+ var services = new ServiceCollection();
+ services.AddThalosDalRuntime();
+
+ using var provider = services.BuildServiceProvider();
+ var healthCheck = provider.GetRequiredService();
+
+ var status = await healthCheck.CheckAsync();
+
+ Assert.True(status.IsHealthy);
+ Assert.Contains("IUserDataProvider", status.DependencyNames);
+ Assert.Contains("IPermissionDataProvider", status.DependencyNames);
+ }
+}
diff --git a/tests/Thalos.DAL.UnitTests/Thalos.DAL.UnitTests.csproj b/tests/Thalos.DAL.UnitTests/Thalos.DAL.UnitTests.csproj
index 202abae..8f4a236 100644
--- a/tests/Thalos.DAL.UnitTests/Thalos.DAL.UnitTests.csproj
+++ b/tests/Thalos.DAL.UnitTests/Thalos.DAL.UnitTests.csproj
@@ -7,6 +7,7 @@
+