chore(thalos-dal): merge contract integration branch

This commit is contained in:
José René White Enciso 2026-02-22 04:56:58 -06:00
commit 3f83efec42
33 changed files with 430 additions and 0 deletions

View File

@ -2,6 +2,13 @@
skinparam packageStyle rectangle
package "thalos-dal" {
class IdentityPolicyLookupRequest
class IdentityPolicyRecord
class IdentityTokenLookupRequest
class IdentityTokenRecord
class IdentityPermissionSetLookupRequest
class IdentityPermissionRecord
interface IIdentityDalGrpcContractAdapter
interface IUserDataProvider
interface IRoleDataProvider
interface IPermissionDataProvider
@ -10,12 +17,20 @@ package "thalos-dal" {
interface IIdentityRepository
interface IDalDependencyHealthCheck
IIdentityDalGrpcContractAdapter --> IdentityPolicyLookupRequest
IIdentityDalGrpcContractAdapter --> IdentityTokenLookupRequest
IIdentityRepository --> IUserDataProvider
IIdentityRepository --> IRoleDataProvider
IIdentityRepository --> IPermissionDataProvider
IIdentityRepository --> IModuleDataProvider
IIdentityRepository --> ITenantDataProvider
IIdentityRepository --> IDalDependencyHealthCheck
IIdentityRepository --> IdentityPolicyLookupRequest
IIdentityRepository --> IdentityPolicyRecord
IIdentityRepository --> IdentityTokenLookupRequest
IIdentityRepository --> IdentityTokenRecord
IIdentityRepository --> IdentityPermissionSetLookupRequest
IIdentityRepository --> IdentityPermissionRecord
}
package "thalos-service" as ThalosService

View File

@ -5,8 +5,10 @@
- DAL repository boundaries coordinate identity aggregate persistence operations.
- Dependency health checks are defined inside DAL boundaries.
- Storage and cache dependencies are modeled in DAL-owned contracts.
- gRPC translation stays at adapter interfaces and does not include persistence implementation.
## Constraints
- Identity persistence concerns do not leak to non-Thalos repositories.
- Service layer consumes DAL boundaries without owning persistence details.
- This stage defines contracts and ports only; no datastore implementation is introduced.

View File

@ -7,9 +7,12 @@
- `IPermissionDataProvider`: permission aggregate provider boundary.
- `IModuleDataProvider`: module aggregate provider boundary.
- `ITenantDataProvider`: tenant aggregate provider boundary.
- `IIdentityRepository`: DAL composition boundary for policy, token, and permission-set reads.
- `IIdentityDalGrpcContractAdapter`: gRPC translation boundary for DAL contracts.
## Rules
- Providers isolate datastore-specific behavior.
- Provider boundaries remain internal to Thalos DAL.
- DAL interfaces expose only transport-neutral contracts and read ports.
- Identity abstractions remain Thalos-owned.

View File

@ -0,0 +1,38 @@
using Thalos.DAL.Contracts;
using Thalos.DAL.Grpc;
namespace Thalos.DAL.Adapters;
/// <summary>
/// Defines adapter boundary for dal gRPC contract translation.
/// </summary>
public interface IIdentityDalGrpcContractAdapter
{
/// <summary>
/// Maps transport-neutral policy lookup request into gRPC contract shape.
/// </summary>
/// <param name="request">Policy lookup request contract.</param>
/// <returns>gRPC policy contract shape.</returns>
IdentityPolicyDalGrpcContract ToGrpcPolicyRequest(IdentityPolicyLookupRequest request);
/// <summary>
/// Maps gRPC policy contract into transport-neutral policy lookup request.
/// </summary>
/// <param name="contract">gRPC policy contract shape.</param>
/// <returns>Policy lookup request contract.</returns>
IdentityPolicyLookupRequest FromGrpcPolicyRequest(IdentityPolicyDalGrpcContract contract);
/// <summary>
/// Maps transport-neutral token lookup request into gRPC contract shape.
/// </summary>
/// <param name="request">Token lookup request contract.</param>
/// <returns>gRPC token contract shape.</returns>
IdentityTokenDalGrpcContract ToGrpcTokenRequest(IdentityTokenLookupRequest request);
/// <summary>
/// Maps gRPC token contract into transport-neutral token lookup request.
/// </summary>
/// <param name="contract">gRPC token contract shape.</param>
/// <returns>Token lookup request contract.</returns>
IdentityTokenLookupRequest FromGrpcTokenRequest(IdentityTokenDalGrpcContract contract);
}

View File

@ -0,0 +1,12 @@
namespace Thalos.DAL.Contracts;
/// <summary>
/// Contract representing dal dependency health status.
/// </summary>
/// <param name="Envelope">Contract envelope metadata.</param>
/// <param name="IsHealthy">Indicates whether dependencies are healthy.</param>
/// <param name="DependencyNames">Dependency boundaries included in health probe.</param>
public sealed record DalDependencyHealthStatus(
IdentityContractEnvelope Envelope,
bool IsHealthy,
IReadOnlyList<string> DependencyNames);

View File

@ -0,0 +1,8 @@
namespace Thalos.DAL.Contracts;
/// <summary>
/// Defines transport-neutral envelope metadata for thalos dal contract messages.
/// </summary>
/// <param name="ContractVersion">Contract schema version.</param>
/// <param name="CorrelationId">Correlation identifier for cross-layer tracing.</param>
public sealed record IdentityContractEnvelope(string ContractVersion, string CorrelationId);

View File

@ -0,0 +1,8 @@
namespace Thalos.DAL.Contracts;
/// <summary>
/// Request contract for module aggregate lookup.
/// </summary>
/// <param name="Envelope">Contract envelope metadata.</param>
/// <param name="TenantId">Tenant scope identifier.</param>
public sealed record IdentityModuleLookupRequest(IdentityContractEnvelope Envelope, string TenantId);

View File

@ -0,0 +1,9 @@
namespace Thalos.DAL.Contracts;
/// <summary>
/// Contract representing persisted module metadata for tenant scope.
/// </summary>
/// <param name="Envelope">Contract envelope metadata.</param>
/// <param name="ModuleCode">Module code identifier.</param>
/// <param name="IsEnabled">Indicates whether module is enabled.</param>
public sealed record IdentityModuleRecord(IdentityContractEnvelope Envelope, string ModuleCode, bool IsEnabled);

View File

@ -0,0 +1,12 @@
namespace Thalos.DAL.Contracts;
/// <summary>
/// Contract representing a persisted permission grant for identity scope.
/// </summary>
/// <param name="Envelope">Contract envelope metadata.</param>
/// <param name="PermissionCode">Permission code identifier.</param>
/// <param name="SourceRoleCode">Role code that grants the permission.</param>
public sealed record IdentityPermissionRecord(
IdentityContractEnvelope Envelope,
string PermissionCode,
string SourceRoleCode);

View File

@ -0,0 +1,12 @@
namespace Thalos.DAL.Contracts;
/// <summary>
/// Request contract for identity permission set lookup.
/// </summary>
/// <param name="Envelope">Contract envelope metadata.</param>
/// <param name="SubjectId">Identity subject identifier.</param>
/// <param name="TenantId">Tenant scope identifier.</param>
public sealed record IdentityPermissionSetLookupRequest(
IdentityContractEnvelope Envelope,
string SubjectId,
string TenantId);

View File

@ -0,0 +1,14 @@
namespace Thalos.DAL.Contracts;
/// <summary>
/// Request contract for identity policy context lookup.
/// </summary>
/// <param name="Envelope">Contract envelope metadata.</param>
/// <param name="SubjectId">Identity subject identifier.</param>
/// <param name="TenantId">Tenant scope identifier.</param>
/// <param name="PermissionCode">Permission code to evaluate.</param>
public sealed record IdentityPolicyLookupRequest(
IdentityContractEnvelope Envelope,
string SubjectId,
string TenantId,
string PermissionCode);

View File

@ -0,0 +1,14 @@
namespace Thalos.DAL.Contracts;
/// <summary>
/// Response contract representing persisted identity policy context.
/// </summary>
/// <param name="Envelope">Contract envelope metadata.</param>
/// <param name="SubjectId">Identity subject identifier.</param>
/// <param name="PermissionCode">Permission code evaluated.</param>
/// <param name="ContextSatisfied">Indicates whether policy context is satisfied.</param>
public sealed record IdentityPolicyRecord(
IdentityContractEnvelope Envelope,
string SubjectId,
string PermissionCode,
bool ContextSatisfied);

View File

@ -0,0 +1,9 @@
namespace Thalos.DAL.Contracts;
/// <summary>
/// Request contract for role aggregate lookup.
/// </summary>
/// <param name="Envelope">Contract envelope metadata.</param>
/// <param name="SubjectId">Identity subject identifier.</param>
/// <param name="TenantId">Tenant scope identifier.</param>
public sealed record IdentityRoleLookupRequest(IdentityContractEnvelope Envelope, string SubjectId, string TenantId);

View File

@ -0,0 +1,9 @@
namespace Thalos.DAL.Contracts;
/// <summary>
/// Contract representing a persisted identity role assignment.
/// </summary>
/// <param name="Envelope">Contract envelope metadata.</param>
/// <param name="RoleCode">Role code identifier.</param>
/// <param name="TenantId">Tenant scope identifier.</param>
public sealed record IdentityRoleRecord(IdentityContractEnvelope Envelope, string RoleCode, string TenantId);

View File

@ -0,0 +1,8 @@
namespace Thalos.DAL.Contracts;
/// <summary>
/// Request contract for tenant aggregate lookup.
/// </summary>
/// <param name="Envelope">Contract envelope metadata.</param>
/// <param name="TenantId">Tenant scope identifier.</param>
public sealed record IdentityTenantLookupRequest(IdentityContractEnvelope Envelope, string TenantId);

View File

@ -0,0 +1,14 @@
namespace Thalos.DAL.Contracts;
/// <summary>
/// Contract representing persisted tenant metadata.
/// </summary>
/// <param name="Envelope">Contract envelope metadata.</param>
/// <param name="TenantId">Tenant scope identifier.</param>
/// <param name="TenantCode">Tenant code identifier.</param>
/// <param name="IsActive">Indicates whether tenant is active.</param>
public sealed record IdentityTenantRecord(
IdentityContractEnvelope Envelope,
string TenantId,
string TenantCode,
bool IsActive);

View File

@ -0,0 +1,9 @@
namespace Thalos.DAL.Contracts;
/// <summary>
/// Request contract for identity token record lookup.
/// </summary>
/// <param name="Envelope">Contract envelope metadata.</param>
/// <param name="SubjectId">Identity subject identifier.</param>
/// <param name="TenantId">Tenant scope identifier.</param>
public sealed record IdentityTokenLookupRequest(IdentityContractEnvelope Envelope, string SubjectId, string TenantId);

View File

@ -0,0 +1,16 @@
namespace Thalos.DAL.Contracts;
/// <summary>
/// Response contract representing token issuance persistence data.
/// </summary>
/// <param name="Envelope">Contract envelope metadata.</param>
/// <param name="SubjectId">Identity subject identifier.</param>
/// <param name="TenantId">Tenant scope identifier.</param>
/// <param name="Token">Issued access token value.</param>
/// <param name="ExpiresInSeconds">Token expiration in seconds.</param>
public sealed record IdentityTokenRecord(
IdentityContractEnvelope Envelope,
string SubjectId,
string TenantId,
string Token,
int ExpiresInSeconds);

View File

@ -0,0 +1,8 @@
namespace Thalos.DAL.Contracts;
/// <summary>
/// Request contract for user aggregate lookup.
/// </summary>
/// <param name="Envelope">Contract envelope metadata.</param>
/// <param name="SubjectId">Identity subject identifier.</param>
public sealed record IdentityUserLookupRequest(IdentityContractEnvelope Envelope, string SubjectId);

View File

@ -0,0 +1,14 @@
namespace Thalos.DAL.Contracts;
/// <summary>
/// Contract representing a persisted identity user aggregate.
/// </summary>
/// <param name="Envelope">Contract envelope metadata.</param>
/// <param name="SubjectId">Identity subject identifier.</param>
/// <param name="TenantId">Tenant scope identifier.</param>
/// <param name="Status">Current user status.</param>
public sealed record IdentityUserRecord(
IdentityContractEnvelope Envelope,
string SubjectId,
string TenantId,
string Status);

View File

@ -0,0 +1,15 @@
using Core.Blueprint.Common.Contracts;
namespace Thalos.DAL.Contracts;
/// <summary>
/// Defines package descriptor metadata for thalos dal contracts.
/// </summary>
public sealed class ThalosDalPackageContract : IBlueprintPackageContract
{
/// <inheritdoc />
public BlueprintPackageDescriptor Descriptor { get; } = new(
"Thalos.DAL.Contracts",
PackageVersionPolicy.Minor,
["Core.Blueprint.Common"]);
}

View File

@ -0,0 +1,9 @@
namespace Thalos.DAL.Grpc;
/// <summary>
/// Defines minimal gRPC contract shape for identity policy dal adapter translation.
/// </summary>
/// <param name="SubjectId">Identity subject identifier.</param>
/// <param name="TenantId">Tenant scope identifier.</param>
/// <param name="PermissionCode">Permission code to evaluate.</param>
public sealed record IdentityPolicyDalGrpcContract(string SubjectId, string TenantId, string PermissionCode);

View File

@ -0,0 +1,8 @@
namespace Thalos.DAL.Grpc;
/// <summary>
/// Defines minimal gRPC contract shape for identity token dal adapter translation.
/// </summary>
/// <param name="SubjectId">Identity subject identifier.</param>
/// <param name="TenantId">Tenant scope identifier.</param>
public sealed record IdentityTokenDalGrpcContract(string SubjectId, string TenantId);

View File

@ -1,3 +1,5 @@
using Thalos.DAL.Contracts;
namespace Thalos.DAL.Health;
/// <summary>
@ -5,4 +7,10 @@ namespace Thalos.DAL.Health;
/// </summary>
public interface IDalDependencyHealthCheck
{
/// <summary>
/// Probes dependency health for DAL-owned providers.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Health status contract for DAL dependency boundaries.</returns>
Task<DalDependencyHealthStatus> CheckAsync(CancellationToken cancellationToken = default);
}

View File

@ -1,3 +1,5 @@
using Thalos.DAL.Contracts;
namespace Thalos.DAL.Providers;
/// <summary>
@ -5,4 +7,13 @@ namespace Thalos.DAL.Providers;
/// </summary>
public interface IModuleDataProvider
{
/// <summary>
/// Reads module capability metadata for tenant scope.
/// </summary>
/// <param name="request">Module lookup request contract.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Module records matching lookup scope.</returns>
Task<IReadOnlyList<IdentityModuleRecord>> ReadModulesAsync(
IdentityModuleLookupRequest request,
CancellationToken cancellationToken = default);
}

View File

@ -1,3 +1,5 @@
using Thalos.DAL.Contracts;
namespace Thalos.DAL.Providers;
/// <summary>
@ -5,4 +7,13 @@ namespace Thalos.DAL.Providers;
/// </summary>
public interface IPermissionDataProvider
{
/// <summary>
/// Reads permission grants for subject and tenant scope.
/// </summary>
/// <param name="request">Permission lookup request contract.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Permission grant records matching lookup scope.</returns>
Task<IReadOnlyList<IdentityPermissionRecord>> ReadPermissionsAsync(
IdentityPermissionSetLookupRequest request,
CancellationToken cancellationToken = default);
}

View File

@ -1,3 +1,5 @@
using Thalos.DAL.Contracts;
namespace Thalos.DAL.Providers;
/// <summary>
@ -5,4 +7,13 @@ namespace Thalos.DAL.Providers;
/// </summary>
public interface IRoleDataProvider
{
/// <summary>
/// Reads identity role records for subject and tenant scope.
/// </summary>
/// <param name="request">Role lookup request contract.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Role records matching the lookup scope.</returns>
Task<IReadOnlyList<IdentityRoleRecord>> ReadRolesAsync(
IdentityRoleLookupRequest request,
CancellationToken cancellationToken = default);
}

View File

@ -1,3 +1,5 @@
using Thalos.DAL.Contracts;
namespace Thalos.DAL.Providers;
/// <summary>
@ -5,4 +7,13 @@ namespace Thalos.DAL.Providers;
/// </summary>
public interface ITenantDataProvider
{
/// <summary>
/// Reads tenant scope metadata for identity policy and token decisions.
/// </summary>
/// <param name="request">Tenant lookup request contract.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Tenant record when found; otherwise null.</returns>
Task<IdentityTenantRecord?> ReadTenantAsync(
IdentityTenantLookupRequest request,
CancellationToken cancellationToken = default);
}

View File

@ -1,3 +1,5 @@
using Thalos.DAL.Contracts;
namespace Thalos.DAL.Providers;
/// <summary>
@ -5,4 +7,13 @@ namespace Thalos.DAL.Providers;
/// </summary>
public interface IUserDataProvider
{
/// <summary>
/// Reads an identity user record by subject identifier.
/// </summary>
/// <param name="request">User lookup request contract.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>User record when found; otherwise null.</returns>
Task<IdentityUserRecord?> ReadUserAsync(
IdentityUserLookupRequest request,
CancellationToken cancellationToken = default);
}

View File

@ -1,3 +1,5 @@
using Thalos.DAL.Contracts;
namespace Thalos.DAL.Repositories;
/// <summary>
@ -5,4 +7,33 @@ namespace Thalos.DAL.Repositories;
/// </summary>
public interface IIdentityRepository
{
/// <summary>
/// Reads token issuance record data for a subject and tenant scope.
/// </summary>
/// <param name="request">Token lookup request contract.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Token record when found; otherwise null.</returns>
Task<IdentityTokenRecord?> ReadIdentityTokenAsync(
IdentityTokenLookupRequest request,
CancellationToken cancellationToken = default);
/// <summary>
/// Reads policy context data required for authorization checks.
/// </summary>
/// <param name="request">Policy lookup request contract.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Policy record when found; otherwise null.</returns>
Task<IdentityPolicyRecord?> ReadIdentityPolicyAsync(
IdentityPolicyLookupRequest request,
CancellationToken cancellationToken = default);
/// <summary>
/// Reads permission set data for subject and tenant scope.
/// </summary>
/// <param name="request">Permission set lookup request contract.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Permission records for the requested scope.</returns>
Task<IReadOnlyList<IdentityPermissionRecord>> ReadPermissionSetAsync(
IdentityPermissionSetLookupRequest request,
CancellationToken cancellationToken = default);
}

View File

@ -4,4 +4,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\blueprint-platform\src\Core.Blueprint.Common\Core.Blueprint.Common.csproj" />
</ItemGroup>
</Project>

View File

@ -1,6 +1,7 @@
using Thalos.DAL.Health;
using Thalos.DAL.Providers;
using Thalos.DAL.Repositories;
using System.Reflection;
namespace Thalos.DAL.UnitTests;
@ -22,4 +23,26 @@ public class BoundaryShapeTests
Assert.True(typeof(IIdentityRepository).IsInterface);
Assert.True(typeof(IDalDependencyHealthCheck).IsInterface);
}
[Fact]
public void IdentityRepository_WhenReflected_ExposesContractMethodsOnly()
{
var methods = typeof(IIdentityRepository).GetMethods(BindingFlags.Public | BindingFlags.Instance);
var methodNames = methods.Select(method => method.Name).OrderBy(name => name).ToArray();
Assert.Equal(
["ReadIdentityPolicyAsync", "ReadIdentityTokenAsync", "ReadPermissionSetAsync"],
methodNames);
}
[Fact]
public void ProviderBoundaries_WhenReflected_ExposeReadOnlyMethods()
{
Assert.Equal("ReadUserAsync", typeof(IUserDataProvider).GetMethods().Single().Name);
Assert.Equal("ReadRolesAsync", typeof(IRoleDataProvider).GetMethods().Single().Name);
Assert.Equal("ReadPermissionsAsync", typeof(IPermissionDataProvider).GetMethods().Single().Name);
Assert.Equal("ReadModulesAsync", typeof(IModuleDataProvider).GetMethods().Single().Name);
Assert.Equal("ReadTenantAsync", typeof(ITenantDataProvider).GetMethods().Single().Name);
Assert.Equal("CheckAsync", typeof(IDalDependencyHealthCheck).GetMethods().Single().Name);
}
}

View File

@ -0,0 +1,44 @@
using Core.Blueprint.Common.Contracts;
using Thalos.DAL.Contracts;
namespace Thalos.DAL.UnitTests;
public class ContractShapeTests
{
[Fact]
public void IdentityPolicyLookupRequest_WhenCreated_StoresTransportNeutralData()
{
var envelope = new IdentityContractEnvelope("1.0.0", "corr-123");
var request = new IdentityPolicyLookupRequest(envelope, "user-1", "tenant-1", "identity.token.issue");
Assert.Equal("1.0.0", request.Envelope.ContractVersion);
Assert.Equal("corr-123", request.Envelope.CorrelationId);
Assert.Equal("user-1", request.SubjectId);
Assert.Equal("tenant-1", request.TenantId);
Assert.Equal("identity.token.issue", request.PermissionCode);
}
[Fact]
public void IdentityTokenRecord_WhenCreated_StoresTransportNeutralData()
{
var envelope = new IdentityContractEnvelope("1.0.0", "corr-123");
var record = new IdentityTokenRecord(envelope, "user-1", "tenant-1", "token-xyz", 1800);
Assert.Equal("1.0.0", record.Envelope.ContractVersion);
Assert.Equal("corr-123", record.Envelope.CorrelationId);
Assert.Equal("user-1", record.SubjectId);
Assert.Equal("tenant-1", record.TenantId);
Assert.Equal("token-xyz", record.Token);
Assert.Equal(1800, record.ExpiresInSeconds);
}
[Fact]
public void ThalosDalPackageContract_WhenCreated_UsesBlueprintDescriptorContract()
{
IBlueprintPackageContract contract = new ThalosDalPackageContract();
Assert.Equal("Thalos.DAL.Contracts", contract.Descriptor.PackageId);
Assert.Equal(PackageVersionPolicy.Minor, contract.Descriptor.VersionPolicy);
Assert.Contains("Core.Blueprint.Common", contract.Descriptor.DependencyPackageIds);
}
}