diff --git a/src/Thalos.Service.Application/Adapters/IdentityCapabilityContractAdapter.cs b/src/Thalos.Service.Application/Adapters/IdentityCapabilityContractAdapter.cs
new file mode 100644
index 0000000..45c8f37
--- /dev/null
+++ b/src/Thalos.Service.Application/Adapters/IdentityCapabilityContractAdapter.cs
@@ -0,0 +1,29 @@
+using Thalos.Service.Identity.Abstractions.Contracts;
+
+namespace Thalos.Service.Application.Adapters;
+
+///
+/// Default adapter implementation for identity policy contract composition.
+///
+public sealed class IdentityCapabilityContractAdapter : IIdentityCapabilityContractAdapter
+{
+ ///
+ public IdentityPolicyContextRequest CreatePolicyContext(EvaluateIdentityPolicyRequest identityRequest)
+ {
+ return new IdentityPolicyContextRequest(
+ identityRequest.SubjectId,
+ identityRequest.TenantId,
+ identityRequest.PermissionCode);
+ }
+
+ ///
+ public EvaluateIdentityPolicyResponse MapPolicyResponse(
+ EvaluateIdentityPolicyRequest identityRequest,
+ IdentityPolicyContextResponse contextResponse)
+ {
+ return new EvaluateIdentityPolicyResponse(
+ identityRequest.SubjectId,
+ identityRequest.PermissionCode,
+ contextResponse.ContextSatisfied);
+ }
+}
diff --git a/src/Thalos.Service.Application/Adapters/IdentityPolicyGrpcContractAdapter.cs b/src/Thalos.Service.Application/Adapters/IdentityPolicyGrpcContractAdapter.cs
new file mode 100644
index 0000000..fe4435d
--- /dev/null
+++ b/src/Thalos.Service.Application/Adapters/IdentityPolicyGrpcContractAdapter.cs
@@ -0,0 +1,22 @@
+using Thalos.Service.Application.Grpc;
+using Thalos.Service.Identity.Abstractions.Contracts;
+
+namespace Thalos.Service.Application.Adapters;
+
+///
+/// Default adapter implementation for identity policy gRPC contract translation.
+///
+public sealed class IdentityPolicyGrpcContractAdapter : IIdentityPolicyGrpcContractAdapter
+{
+ ///
+ public EvaluateIdentityPolicyGrpcContract ToGrpc(EvaluateIdentityPolicyRequest request)
+ {
+ return new EvaluateIdentityPolicyGrpcContract(request.SubjectId, request.TenantId, request.PermissionCode);
+ }
+
+ ///
+ public EvaluateIdentityPolicyRequest FromGrpc(EvaluateIdentityPolicyGrpcContract contract)
+ {
+ return new EvaluateIdentityPolicyRequest(contract.SubjectId, contract.TenantId, contract.PermissionCode);
+ }
+}
diff --git a/src/Thalos.Service.Application/DependencyInjection/ThalosServiceRuntimeServiceCollectionExtensions.cs b/src/Thalos.Service.Application/DependencyInjection/ThalosServiceRuntimeServiceCollectionExtensions.cs
new file mode 100644
index 0000000..9d53fc0
--- /dev/null
+++ b/src/Thalos.Service.Application/DependencyInjection/ThalosServiceRuntimeServiceCollectionExtensions.cs
@@ -0,0 +1,37 @@
+using Core.Blueprint.Common.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Thalos.DAL.DependencyInjection;
+using Thalos.Service.Application.Adapters;
+using Thalos.Service.Application.Ports;
+using Thalos.Service.Application.UseCases;
+
+namespace Thalos.Service.Application.DependencyInjection;
+
+///
+/// Registers thalos-service runtime orchestration and DAL adapters.
+///
+public static class ThalosServiceRuntimeServiceCollectionExtensions
+{
+ ///
+ /// Adds thalos-service runtime wiring aligned with blueprint runtime and thalos-dal runtime.
+ ///
+ /// Service collection.
+ /// Service collection for fluent chaining.
+ public static IServiceCollection AddThalosServiceRuntime(this IServiceCollection services)
+ {
+ services.AddBlueprintRuntimeCore();
+ services.AddThalosDalRuntime();
+
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+
+ return services;
+ }
+}
diff --git a/src/Thalos.Service.Application/Ports/IdentityPolicyContextReadPortDalAdapter.cs b/src/Thalos.Service.Application/Ports/IdentityPolicyContextReadPortDalAdapter.cs
new file mode 100644
index 0000000..1e1fe2e
--- /dev/null
+++ b/src/Thalos.Service.Application/Ports/IdentityPolicyContextReadPortDalAdapter.cs
@@ -0,0 +1,49 @@
+using Core.Blueprint.Common.Runtime;
+using Thalos.DAL.Contracts;
+using Thalos.DAL.Repositories;
+using Thalos.Service.Identity.Abstractions.Contracts;
+
+namespace Thalos.Service.Application.Ports;
+
+///
+/// Default DAL adapter for identity policy context read port.
+///
+public sealed class IdentityPolicyContextReadPortDalAdapter(
+ IIdentityRepository identityRepository,
+ IBlueprintSystemClock clock) : IIdentityPolicyContextReadPort
+{
+ ///
+ public async Task ReadPolicyContextAsync(IdentityPolicyContextRequest request)
+ {
+ var policyLookupRequest = new IdentityPolicyLookupRequest(
+ CreateEnvelope(),
+ request.SubjectId,
+ request.TenantId,
+ request.PermissionCode);
+
+ var policyRecord = await identityRepository.ReadIdentityPolicyAsync(policyLookupRequest);
+ if (policyRecord is null)
+ {
+ return new IdentityPolicyContextResponse(request.SubjectId, request.PermissionCode, false);
+ }
+
+ var permissionSetRequest = new IdentityPermissionSetLookupRequest(
+ policyLookupRequest.Envelope,
+ request.SubjectId,
+ request.TenantId);
+
+ var permissions = await identityRepository.ReadPermissionSetAsync(permissionSetRequest);
+ var permissionMatched = permissions.Any(permission =>
+ string.Equals(permission.PermissionCode, request.PermissionCode, StringComparison.OrdinalIgnoreCase));
+
+ return new IdentityPolicyContextResponse(
+ request.SubjectId,
+ request.PermissionCode,
+ policyRecord.ContextSatisfied && permissionMatched);
+ }
+
+ private IdentityContractEnvelope CreateEnvelope()
+ {
+ return new IdentityContractEnvelope("1.0.0", $"corr-{clock.UtcNow:yyyyMMddHHmmssfff}");
+ }
+}
diff --git a/src/Thalos.Service.Application/Ports/IdentityTokenReadPortDalAdapter.cs b/src/Thalos.Service.Application/Ports/IdentityTokenReadPortDalAdapter.cs
new file mode 100644
index 0000000..0cc98ed
--- /dev/null
+++ b/src/Thalos.Service.Application/Ports/IdentityTokenReadPortDalAdapter.cs
@@ -0,0 +1,36 @@
+using Core.Blueprint.Common.Runtime;
+using Thalos.DAL.Contracts;
+using Thalos.DAL.Repositories;
+using Thalos.Service.Identity.Abstractions.Contracts;
+
+namespace Thalos.Service.Application.Ports;
+
+///
+/// Default DAL adapter for identity token read port.
+///
+public sealed class IdentityTokenReadPortDalAdapter(
+ IIdentityRepository identityRepository,
+ IBlueprintSystemClock clock) : IIdentityTokenReadPort
+{
+ ///
+ public async Task IssueTokenAsync(IssueIdentityTokenRequest request)
+ {
+ var lookupRequest = new IdentityTokenLookupRequest(
+ CreateEnvelope(),
+ request.SubjectId,
+ request.TenantId);
+
+ var tokenRecord = await identityRepository.ReadIdentityTokenAsync(lookupRequest);
+ if (tokenRecord is null)
+ {
+ return new IssueIdentityTokenResponse(string.Empty, 0);
+ }
+
+ return new IssueIdentityTokenResponse(tokenRecord.Token, tokenRecord.ExpiresInSeconds);
+ }
+
+ private IdentityContractEnvelope CreateEnvelope()
+ {
+ return new IdentityContractEnvelope("1.0.0", $"corr-{clock.UtcNow:yyyyMMddHHmmssfff}");
+ }
+}
diff --git a/src/Thalos.Service.Application/Thalos.Service.Application.csproj b/src/Thalos.Service.Application/Thalos.Service.Application.csproj
index 032d28e..bdb0db0 100644
--- a/src/Thalos.Service.Application/Thalos.Service.Application.csproj
+++ b/src/Thalos.Service.Application/Thalos.Service.Application.csproj
@@ -5,6 +5,8 @@
enable
+
+
diff --git a/src/Thalos.Service.Grpc/Program.cs b/src/Thalos.Service.Grpc/Program.cs
index 5af6dbd..f52b4c2 100644
--- a/src/Thalos.Service.Grpc/Program.cs
+++ b/src/Thalos.Service.Grpc/Program.cs
@@ -1,6 +1,15 @@
+using Thalos.Service.Application.DependencyInjection;
+using Thalos.Service.Grpc.Services;
+
var builder = WebApplication.CreateBuilder(args);
-// Stage 3 skeleton: single active internal protocol policy is gRPC-first.
+builder.Services.AddGrpc();
+builder.Services.AddHealthChecks();
+builder.Services.AddThalosServiceRuntime();
+
var app = builder.Build();
+app.MapGrpcService();
+app.MapHealthChecks("/healthz");
+
app.Run();
diff --git a/src/Thalos.Service.Grpc/Protos/identity_runtime.proto b/src/Thalos.Service.Grpc/Protos/identity_runtime.proto
new file mode 100644
index 0000000..80fcbce
--- /dev/null
+++ b/src/Thalos.Service.Grpc/Protos/identity_runtime.proto
@@ -0,0 +1,32 @@
+syntax = "proto3";
+
+option csharp_namespace = "Thalos.Service.Grpc";
+
+package thalos.service.grpc;
+
+service IdentityRuntime {
+ rpc IssueIdentityToken (IssueIdentityTokenGrpcRequest) returns (IssueIdentityTokenGrpcResponse);
+ rpc EvaluateIdentityPolicy (EvaluateIdentityPolicyGrpcRequest) returns (EvaluateIdentityPolicyGrpcResponse);
+}
+
+message IssueIdentityTokenGrpcRequest {
+ string subject_id = 1;
+ string tenant_id = 2;
+}
+
+message IssueIdentityTokenGrpcResponse {
+ string token = 1;
+ int32 expires_in_seconds = 2;
+}
+
+message EvaluateIdentityPolicyGrpcRequest {
+ string subject_id = 1;
+ string tenant_id = 2;
+ string permission_code = 3;
+}
+
+message EvaluateIdentityPolicyGrpcResponse {
+ string subject_id = 1;
+ string permission_code = 2;
+ bool is_allowed = 3;
+}
diff --git a/src/Thalos.Service.Grpc/Services/IdentityRuntimeGrpcService.cs b/src/Thalos.Service.Grpc/Services/IdentityRuntimeGrpcService.cs
new file mode 100644
index 0000000..948cfb6
--- /dev/null
+++ b/src/Thalos.Service.Grpc/Services/IdentityRuntimeGrpcService.cs
@@ -0,0 +1,62 @@
+using Grpc.Core;
+using Thalos.Service.Application.Adapters;
+using Thalos.Service.Application.Grpc;
+using Thalos.Service.Application.UseCases;
+using Thalos.Service.Identity.Abstractions.Contracts;
+
+namespace Thalos.Service.Grpc.Services;
+
+///
+/// Internal gRPC endpoint implementation for identity runtime operations.
+///
+public sealed class IdentityRuntimeGrpcService(
+ IIssueIdentityTokenUseCase issueIdentityTokenUseCase,
+ IEvaluateIdentityPolicyUseCase evaluateIdentityPolicyUseCase,
+ IIdentityPolicyGrpcContractAdapter grpcContractAdapter) : IdentityRuntime.IdentityRuntimeBase
+{
+ ///
+ /// Issues identity token through service use-case orchestration.
+ ///
+ /// gRPC token issuance request.
+ /// gRPC server call context.
+ /// gRPC token issuance response.
+ public override async Task IssueIdentityToken(
+ IssueIdentityTokenGrpcRequest request,
+ ServerCallContext context)
+ {
+ var useCaseRequest = new IssueIdentityTokenRequest(request.SubjectId, request.TenantId);
+ var useCaseResponse = await issueIdentityTokenUseCase.HandleAsync(useCaseRequest);
+
+ return new IssueIdentityTokenGrpcResponse
+ {
+ Token = useCaseResponse.Token,
+ ExpiresInSeconds = useCaseResponse.ExpiresInSeconds
+ };
+ }
+
+ ///
+ /// Evaluates identity policy through service use-case orchestration.
+ ///
+ /// gRPC policy evaluation request.
+ /// gRPC server call context.
+ /// gRPC policy evaluation response.
+ public override async Task EvaluateIdentityPolicy(
+ EvaluateIdentityPolicyGrpcRequest request,
+ ServerCallContext context)
+ {
+ var grpcContract = new EvaluateIdentityPolicyGrpcContract(
+ request.SubjectId,
+ request.TenantId,
+ request.PermissionCode);
+
+ var useCaseRequest = grpcContractAdapter.FromGrpc(grpcContract);
+ var useCaseResponse = await evaluateIdentityPolicyUseCase.HandleAsync(useCaseRequest);
+
+ return new EvaluateIdentityPolicyGrpcResponse
+ {
+ SubjectId = useCaseResponse.SubjectId,
+ PermissionCode = useCaseResponse.PermissionCode,
+ IsAllowed = useCaseResponse.IsAllowed
+ };
+ }
+}
diff --git a/src/Thalos.Service.Grpc/Thalos.Service.Grpc.csproj b/src/Thalos.Service.Grpc/Thalos.Service.Grpc.csproj
index b7f7de2..4db7838 100644
--- a/src/Thalos.Service.Grpc/Thalos.Service.Grpc.csproj
+++ b/src/Thalos.Service.Grpc/Thalos.Service.Grpc.csproj
@@ -5,6 +5,14 @@
enable
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
diff --git a/tests/Thalos.Service.Application.UnitTests/RuntimeWiringTests.cs b/tests/Thalos.Service.Application.UnitTests/RuntimeWiringTests.cs
new file mode 100644
index 0000000..fee3f25
--- /dev/null
+++ b/tests/Thalos.Service.Application.UnitTests/RuntimeWiringTests.cs
@@ -0,0 +1,73 @@
+using Microsoft.Extensions.DependencyInjection;
+using Thalos.Service.Application.Adapters;
+using Thalos.Service.Application.DependencyInjection;
+using Thalos.Service.Application.Grpc;
+using Thalos.Service.Application.UseCases;
+using Thalos.Service.Identity.Abstractions.Contracts;
+
+namespace Thalos.Service.Application.UnitTests;
+
+public class RuntimeWiringTests
+{
+ [Fact]
+ public async Task AddThalosServiceRuntime_WhenInvoked_ResolvesUseCases()
+ {
+ var services = new ServiceCollection();
+ services.AddThalosServiceRuntime();
+
+ using var provider = services.BuildServiceProvider();
+ var issueTokenUseCase = provider.GetRequiredService();
+ var evaluatePolicyUseCase = provider.GetRequiredService();
+
+ var tokenResponse = await issueTokenUseCase.HandleAsync(new IssueIdentityTokenRequest("user-1", "tenant-1"));
+ var policyResponse = await evaluatePolicyUseCase.HandleAsync(
+ new EvaluateIdentityPolicyRequest("user-1", "tenant-1", "identity.token.issue"));
+
+ Assert.Equal("user-1:tenant-1:token", tokenResponse.Token);
+ Assert.Equal(1800, tokenResponse.ExpiresInSeconds);
+ Assert.True(policyResponse.IsAllowed);
+ }
+
+ [Fact]
+ public async Task AddThalosServiceRuntime_WhenSubjectMissing_ReturnsEmptyToken()
+ {
+ var services = new ServiceCollection();
+ services.AddThalosServiceRuntime();
+
+ using var provider = services.BuildServiceProvider();
+ var issueTokenUseCase = provider.GetRequiredService();
+
+ var tokenResponse = await issueTokenUseCase.HandleAsync(
+ new IssueIdentityTokenRequest("missing-user", "tenant-1"));
+
+ Assert.Equal(string.Empty, tokenResponse.Token);
+ Assert.Equal(0, tokenResponse.ExpiresInSeconds);
+ }
+
+ [Fact]
+ public void IdentityPolicyGrpcContractAdapter_WhenMapped_PreservesValues()
+ {
+ var adapter = new IdentityPolicyGrpcContractAdapter();
+ var useCaseRequest = new EvaluateIdentityPolicyRequest("user-2", "tenant-2", "identity.policy.evaluate");
+
+ var grpcContract = adapter.ToGrpc(useCaseRequest);
+ var roundtrip = adapter.FromGrpc(grpcContract);
+
+ Assert.Equal("user-2", roundtrip.SubjectId);
+ Assert.Equal("tenant-2", roundtrip.TenantId);
+ Assert.Equal("identity.policy.evaluate", roundtrip.PermissionCode);
+ }
+
+ [Fact]
+ public void IdentityPolicyGrpcContractAdapter_WhenFromGrpc_UsesExpectedContractShape()
+ {
+ var adapter = new IdentityPolicyGrpcContractAdapter();
+ var contract = new EvaluateIdentityPolicyGrpcContract("subject-9", "tenant-9", "identity.token.issue");
+
+ var request = adapter.FromGrpc(contract);
+
+ Assert.Equal("subject-9", request.SubjectId);
+ Assert.Equal("tenant-9", request.TenantId);
+ Assert.Equal("identity.token.issue", request.PermissionCode);
+ }
+}
diff --git a/tests/Thalos.Service.Application.UnitTests/Thalos.Service.Application.UnitTests.csproj b/tests/Thalos.Service.Application.UnitTests/Thalos.Service.Application.UnitTests.csproj
index a887a0c..a08db62 100644
--- a/tests/Thalos.Service.Application.UnitTests/Thalos.Service.Application.UnitTests.csproj
+++ b/tests/Thalos.Service.Application.UnitTests/Thalos.Service.Application.UnitTests.csproj
@@ -7,6 +7,7 @@
+