feat(authority): truthful dpop runtime extensions

Sprint SPRINT_20260416_012_Authority_truthful_dpop_runtime.

AuthorityDpopRuntimeExtensions wiring, standard plugin bootstrapper +
options tests, DPoP runtime security tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-04-19 14:40:16 +03:00
parent 45ebcb88b9
commit fe3eacbba4
11 changed files with 458 additions and 40 deletions

View File

@@ -16,4 +16,4 @@ PostgreSQL database `stellaops_authority` (dedicated DB); Valkey for session/cac
## Background Workers
- `AuthoritySecretHasherInitializer` — crypto secret initialization on startup
- Plugin hosting via `IPluginHost` (standard identity plugin with bootstrap user/client seeding)
- Plugin hosting via `IPluginHost` (standard identity plugin with first-party client seeding and setup-driven human admin creation)

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -32,7 +33,7 @@ public class StandardPluginBootstrapperTests
services.AddOptions<StandardPluginOptions>("standard")
.Configure(options =>
{
options.TenantId = "demo-prod";
options.TenantId = "default";
options.BootstrapClients = new[]
{
new BootstrapClientOptions
@@ -100,7 +101,7 @@ public class StandardPluginBootstrapperTests
Assert.Contains(StellaOpsScopes.ReleasePublish, client.AllowedScopes);
Assert.Contains("authorization_code", client.AllowedGrantTypes);
Assert.True(client.RequirePkce);
Assert.Equal("demo-prod", client.Properties[AuthorityClientMetadataKeys.Tenant]);
Assert.Equal("default", client.Properties[AuthorityClientMetadataKeys.Tenant]);
var humanCliClient = await clientStore.FindByClientIdAsync("stellaops-cli", TestContext.Current.CancellationToken);
Assert.NotNull(humanCliClient);
@@ -119,6 +120,60 @@ public class StandardPluginBootstrapperTests
Assert.Contains(StellaOpsScopes.OpsHealth, automationCliClient.AllowedScopes);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StartAsync_RetriesBootstrapClientsUntilClientStoreRecovers()
{
var services = new ServiceCollection();
services.AddOptions<StandardPluginOptions>("standard")
.Configure(options =>
{
options.TenantId = "default";
options.BootstrapClients = new[]
{
new BootstrapClientOptions
{
ClientId = "stella-ops-ui",
DisplayName = "Stella Ops Console",
AllowedGrantTypes = "authorization_code refresh_token",
AllowedScopes = $"openid profile {StellaOpsScopes.UiRead}",
RedirectUris = "https://stella-ops.local/auth/callback",
PostLogoutRedirectUris = "https://stella-ops.local/",
RequirePkce = true
}
};
});
var clientStore = new TransientClientStore(new InMemoryClientStore(), failureCount: 2);
services.AddSingleton<IAuthorityClientStore>(clientStore);
services.AddSingleton<IAuthorityRevocationStore>(new StubRevocationStore());
services.AddSingleton<TimeProvider>(new FakeTimeProvider(DateTimeOffset.Parse("2025-12-29T13:00:00Z")));
services.AddSingleton(sp =>
new StandardClientProvisioningStore(
"standard",
sp.GetRequiredService<IAuthorityClientStore>(),
sp.GetRequiredService<IAuthorityRevocationStore>(),
sp.GetRequiredService<TimeProvider>()));
services.AddSingleton(sp =>
new StandardPluginBootstrapper(
"standard",
sp.GetRequiredService<IServiceScopeFactory>(),
NullLogger<StandardPluginBootstrapper>.Instance,
retryDelay: TimeSpan.Zero));
using var provider = services.BuildServiceProvider();
var bootstrapper = provider.GetRequiredService<StandardPluginBootstrapper>();
await bootstrapper.StartAsync(TestContext.Current.CancellationToken);
Assert.True(clientStore.FailureObserved);
var client = await clientStore.FindByClientIdAsync("stella-ops-ui", TestContext.Current.CancellationToken);
Assert.NotNull(client);
Assert.Contains("authorization_code", client!.AllowedGrantTypes);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task StartAsync_DoesNotThrow_WhenBootstrapFails()
@@ -432,6 +487,41 @@ public class StandardPluginBootstrapperTests
=> ValueTask.CompletedTask;
}
private sealed class TransientClientStore : IAuthorityClientStore
{
private readonly IAuthorityClientStore inner;
private int remainingFailures;
public TransientClientStore(IAuthorityClientStore inner, int failureCount)
{
this.inner = inner;
remainingFailures = failureCount;
}
public bool FailureObserved { get; private set; }
public ValueTask<AuthorityClientDocument?> FindByClientIdAsync(string clientId, CancellationToken cancellationToken, MongoDB.Driver.IClientSessionHandle? session = null)
{
if (remainingFailures > 0)
{
remainingFailures--;
FailureObserved = true;
throw new IOException("Transient bootstrap storage failure.");
}
return inner.FindByClientIdAsync(clientId, cancellationToken, session);
}
public ValueTask<IReadOnlyList<AuthorityClientDocument>> ListAsync(int limit = 500, int offset = 0, CancellationToken cancellationToken = default, MongoDB.Driver.IClientSessionHandle? session = null)
=> inner.ListAsync(limit, offset, cancellationToken, session);
public ValueTask UpsertAsync(AuthorityClientDocument document, CancellationToken cancellationToken, MongoDB.Driver.IClientSessionHandle? session = null)
=> inner.UpsertAsync(document, cancellationToken, session);
public ValueTask<bool> DeleteByClientIdAsync(string clientId, CancellationToken cancellationToken, MongoDB.Driver.IClientSessionHandle? session = null)
=> inner.DeleteByClientIdAsync(clientId, cancellationToken, session);
}
private sealed class FakeTimeProvider : TimeProvider
{
private readonly DateTimeOffset fixedNow;

View File

@@ -74,6 +74,9 @@ public class StandardPluginOptionsTests
options.Normalize(configPath);
options.Validate("standard");
Assert.Equal("default", options.TenantId);
Assert.Null(options.BootstrapUser);
var clients = options.BootstrapClients.ToDictionary(client => client.ClientId!, StringComparer.Ordinal);
Assert.Contains("stella-ops-ui", clients.Keys);

View File

@@ -7,8 +7,12 @@ using StellaOps.Auth.Abstractions;
using StellaOps.Authority.Persistence.Postgres.Models;
using StellaOps.Authority.Persistence.Postgres.Repositories;
using StellaOps.Authority.Plugin.Standard.Storage;
using Npgsql;
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
@@ -22,28 +26,30 @@ internal sealed class StandardPluginBootstrapper : IHostedService
private readonly string pluginName;
private readonly IServiceScopeFactory scopeFactory;
private readonly ILogger<StandardPluginBootstrapper> logger;
private readonly int maxAttempts;
private readonly int? maxAttempts;
private readonly TimeSpan retryDelay;
public StandardPluginBootstrapper(
string pluginName,
IServiceScopeFactory scopeFactory,
ILogger<StandardPluginBootstrapper> logger,
int maxAttempts = 15,
int maxAttempts = 0,
TimeSpan? retryDelay = null)
{
this.pluginName = pluginName;
this.scopeFactory = scopeFactory;
this.logger = logger;
this.maxAttempts = Math.Max(1, maxAttempts);
this.maxAttempts = maxAttempts > 0 ? maxAttempts : null;
this.retryDelay = retryDelay ?? DefaultRetryDelay;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
for (var attempt = 1; attempt <= maxAttempts; attempt++)
var attempt = 0;
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
attempt++;
try
{
@@ -55,16 +61,27 @@ internal sealed class StandardPluginBootstrapper : IHostedService
"Standard Authority plugin '{PluginName}' bootstrap completed on retry attempt {Attempt}/{MaxAttempts}.",
pluginName,
attempt,
maxAttempts);
maxAttempts?.ToString() ?? "unbounded");
}
return;
}
catch (Exception ex)
{
var finalAttempt = attempt == maxAttempts;
var finalAttempt = maxAttempts.HasValue && attempt >= maxAttempts.Value;
var retryable = IsRetryable(ex);
var level = finalAttempt ? LogLevel.Error : LogLevel.Warning;
if (!retryable && !finalAttempt)
{
logger.LogError(
ex,
"Standard Authority plugin '{PluginName}' bootstrap failed on attempt {Attempt} with a non-retryable error. Automatic bootstrap retries are stopping.",
pluginName,
attempt);
return;
}
logger.Log(
level,
ex,
@@ -73,7 +90,7 @@ internal sealed class StandardPluginBootstrapper : IHostedService
: "Standard Authority plugin '{PluginName}' bootstrap attempt {Attempt}/{MaxAttempts} failed. Retrying in {RetryDelay}.",
pluginName,
attempt,
maxAttempts,
maxAttempts?.ToString() ?? "unbounded",
retryDelay);
if (finalAttempt)
@@ -86,6 +103,25 @@ internal sealed class StandardPluginBootstrapper : IHostedService
}
}
private static bool IsRetryable(Exception exception)
{
if (exception is OperationCanceledException)
{
return false;
}
return exception switch
{
IOException => true,
HttpRequestException => true,
NpgsqlException => true,
SocketException => true,
TimeoutException => true,
_ when exception.InnerException is not null => IsRetryable(exception.InnerException),
_ => false,
};
}
private async Task RunBootstrapPassAsync(CancellationToken cancellationToken)
{
using var scope = scopeFactory.CreateScope();

View File

@@ -0,0 +1,194 @@
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Containers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using StellaOps.Authority.Security;
using StellaOps.Auth.Security.Dpop;
using StellaOps.Configuration;
using Xunit;
namespace StellaOps.Authority.Tests.Security;
[Collection(nameof(AuthorityValkeyFixtureCollection))]
public sealed class AuthorityDpopRuntimeTests
{
private readonly AuthorityValkeyFixture _valkey;
public AuthorityDpopRuntimeTests(AuthorityValkeyFixture valkey)
{
_valkey = valkey;
}
[Fact]
public void TestingRuntime_UsesInMemoryDpopStores()
{
using var provider = CreateServiceProvider(
"Testing",
_ => { });
Assert.IsType<InMemoryDpopReplayCache>(provider.GetRequiredService<IDpopReplayCache>());
Assert.IsType<InMemoryDpopNonceStore>(provider.GetRequiredService<IDpopNonceStore>());
}
[Fact]
public async Task ProductionRuntime_DpopDisabled_StartsWithoutDurableStore()
{
using var provider = CreateServiceProvider(
"Production",
options => options.Dpop.Enabled = false);
var replayCache = provider.GetRequiredService<IDpopReplayCache>();
var nonceStore = provider.GetRequiredService<IDpopNonceStore>();
Assert.True(await replayCache.TryStoreAsync(Guid.NewGuid().ToString("N"), DateTimeOffset.UtcNow.AddMinutes(5)));
var issuance = await nonceStore.IssueAsync(
"signer",
"client",
"thumb",
TimeSpan.FromMinutes(5),
maxIssuancePerMinute: 5);
Assert.Equal(DpopNonceIssueStatus.Failure, issuance.Status);
Assert.Null(issuance.Nonce);
}
[Fact]
public void ProductionRuntime_DpopEnabled_WithMemoryNonceStore_FailsFast()
{
var exception = Assert.Throws<InvalidOperationException>(() => CreateServiceProvider(
"Production",
options =>
{
options.Dpop.Enabled = true;
options.Dpop.Nonce.Store = "memory";
}));
Assert.Contains("Nonce:Store=redis", exception.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task ProductionRuntime_UsesDurableReplayAndNonceStoresAcrossRestart()
{
var jwtId = Guid.NewGuid().ToString("N");
var expiresAt = DateTimeOffset.UtcNow.AddMinutes(5);
string issuedNonce;
await using (var firstProvider = CreateServiceProvider(
"Production",
options =>
{
options.Dpop.Enabled = true;
options.Dpop.Nonce.Enabled = true;
options.Dpop.Nonce.Store = "redis";
options.Dpop.Nonce.RedisConnectionString = _valkey.ConnectionString;
}))
{
var replayCache = firstProvider.GetRequiredService<IDpopReplayCache>();
var nonceStore = firstProvider.GetRequiredService<IDpopNonceStore>();
Assert.IsType<MessagingDpopReplayCache>(replayCache);
Assert.IsType<MessagingDpopNonceStore>(nonceStore);
Assert.True(await replayCache.TryStoreAsync(jwtId, expiresAt));
var issuance = await nonceStore.IssueAsync(
"signer",
"authority-client",
"thumbprint",
TimeSpan.FromMinutes(5),
maxIssuancePerMinute: 5);
Assert.Equal(DpopNonceIssueStatus.Success, issuance.Status);
issuedNonce = issuance.Nonce!;
}
await using (var restartedProvider = CreateServiceProvider(
"Production",
options =>
{
options.Dpop.Enabled = true;
options.Dpop.Nonce.Enabled = true;
options.Dpop.Nonce.Store = "redis";
options.Dpop.Nonce.RedisConnectionString = _valkey.ConnectionString;
}))
{
var replayCache = restartedProvider.GetRequiredService<IDpopReplayCache>();
var nonceStore = restartedProvider.GetRequiredService<IDpopNonceStore>();
Assert.IsType<MessagingDpopReplayCache>(replayCache);
Assert.IsType<MessagingDpopNonceStore>(nonceStore);
Assert.False(await replayCache.TryStoreAsync(jwtId, expiresAt));
var consume = await nonceStore.TryConsumeAsync(
issuedNonce,
"signer",
"authority-client",
"thumbprint");
Assert.Equal(DpopNonceConsumeStatus.Success, consume.Status);
}
}
private static ServiceProvider CreateServiceProvider(
string environmentName,
Action<AuthoritySenderConstraintOptions> configure)
{
var services = new ServiceCollection();
services.AddLogging();
services.AddSingleton<TimeProvider>(TimeProvider.System);
var senderConstraints = new AuthoritySenderConstraintOptions();
configure(senderConstraints);
services.AddAuthorityDpopRuntime(new TestHostEnvironment(environmentName), senderConstraints);
return services.BuildServiceProvider(validateScopes: true);
}
private sealed class TestHostEnvironment : IHostEnvironment
{
public TestHostEnvironment(string environmentName)
{
EnvironmentName = environmentName;
}
public string EnvironmentName { get; set; }
public string ApplicationName { get; set; } = "AuthorityDpopRuntimeTests";
public string ContentRootPath { get; set; } = AppContext.BaseDirectory;
public IFileProvider ContentRootFileProvider { get; set; } = new NullFileProvider();
}
}
public sealed class AuthorityValkeyFixture : IAsyncLifetime
{
private readonly IContainer _container;
public AuthorityValkeyFixture()
{
_container = new ContainerBuilder()
.WithImage("valkey/valkey:8-alpine")
.WithPortBinding(6379, true)
.WithWaitStrategy(Wait.ForUnixContainer().UntilCommandIsCompleted("valkey-cli", "ping"))
.Build();
}
public string ConnectionString { get; private set; } = null!;
public async ValueTask InitializeAsync()
{
await _container.StartAsync();
ConnectionString = $"{_container.Hostname}:{_container.GetMappedPublicPort(6379)}";
}
public async ValueTask DisposeAsync()
{
await _container.StopAsync();
await _container.DisposeAsync();
}
}
[CollectionDefinition(nameof(AuthorityValkeyFixtureCollection))]
public sealed class AuthorityValkeyFixtureCollection : ICollectionFixture<AuthorityValkeyFixture>
{
}

View File

@@ -18,6 +18,7 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" />
<PackageReference Include="Moq" />
<PackageReference Include="Testcontainers" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Authority\StellaOps.Authority.csproj" />
@@ -31,4 +32,4 @@
<Compile Include="../../../__Tests/shared/OpenSslLegacyShim.cs" Link="Infrastructure/OpenSslLegacyShim.cs" />
<None Include="../../../__Tests/native/openssl-1.1/linux-x64/*" Link="native/linux-x64/%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>
</Project>

View File

@@ -8,3 +8,4 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
| AUDIT-0100-M | DONE | Revalidated 2026-01-06. |
| AUDIT-0100-T | DONE | Revalidated 2026-01-06. |
| AUDIT-0100-A | DONE | Waived (test project; revalidated 2026-01-06). |
| AUTH-DPOP-REAL-003 | DONE | 2026-04-16: focused Authority DPoP runtime wiring and restart-survival proof passed (`4/4`). |

View File

@@ -68,7 +68,6 @@ using StellaOps.Authority.Vulnerability.Attachments;
using StellaOps.Audit.Emission;
#if STELLAOPS_AUTH_SECURITY
using StellaOps.Auth.Security.Dpop;
using StackExchange.Redis;
#endif
var builder = WebApplication.CreateBuilder(args);
@@ -172,34 +171,8 @@ builder.Services.AddOptions<DpopValidationOptions>()
})
.PostConfigure(static options => options.Validate());
builder.Services.TryAddSingleton<IDpopReplayCache>(provider => new InMemoryDpopReplayCache(provider.GetService<TimeProvider>()));
builder.Services.AddAuthorityDpopRuntime(builder.Environment, senderConstraints);
builder.Services.TryAddSingleton<IDpopProofValidator, DpopProofValidator>();
if (string.Equals(senderConstraints.Dpop.Nonce.Store, "redis", StringComparison.OrdinalIgnoreCase))
{
builder.Services.TryAddSingleton<IConnectionMultiplexer>(_ =>
{
var redisOptions = ConfigurationOptions.Parse(senderConstraints.Dpop.Nonce.RedisConnectionString!);
redisOptions.ClientName ??= "stellaops-authority-dpop-nonce";
return ConnectionMultiplexer.Connect(redisOptions);
});
builder.Services.TryAddSingleton<IDpopNonceStore>(provider =>
{
var multiplexer = provider.GetRequiredService<IConnectionMultiplexer>();
var timeProvider = provider.GetService<TimeProvider>();
return new RedisDpopNonceStore(multiplexer, timeProvider);
});
}
else
{
builder.Services.TryAddSingleton<IDpopNonceStore>(provider =>
{
var timeProvider = provider.GetService<TimeProvider>();
var nonceLogger = provider.GetService<ILogger<InMemoryDpopNonceStore>>();
return new InMemoryDpopNonceStore(timeProvider, nonceLogger);
});
}
builder.Services.AddScoped<ValidateDpopProofHandler>();
#endif
@@ -3418,4 +3391,3 @@ sealed record RouterClaimRequirementEntry
public string Type { get; init; } = string.Empty;
public string? Value { get; init; }
}

View File

@@ -0,0 +1,117 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using StellaOps.Auth.Security.Dpop;
using StellaOps.Configuration;
using StellaOps.Messaging.Abstractions;
using StellaOps.Messaging.Transport.Valkey;
namespace StellaOps.Authority.Security;
public static class AuthorityDpopRuntimeExtensions
{
public static IServiceCollection AddAuthorityDpopRuntime(
this IServiceCollection services,
IHostEnvironment environment,
AuthoritySenderConstraintOptions senderConstraints)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(environment);
ArgumentNullException.ThrowIfNull(senderConstraints);
if (environment.IsEnvironment("Testing"))
{
services.TryAddSingleton<IDpopReplayCache>(sp => new InMemoryDpopReplayCache(sp.GetService<TimeProvider>()));
services.TryAddSingleton<IDpopNonceStore>(sp =>
{
var timeProvider = sp.GetService<TimeProvider>();
var logger = sp.GetService<ILogger<InMemoryDpopNonceStore>>();
return new InMemoryDpopNonceStore(timeProvider, logger);
});
return services;
}
if (!senderConstraints.Dpop.Enabled)
{
services.TryAddSingleton<IDpopReplayCache, DisabledDpopReplayCache>();
services.TryAddSingleton<IDpopNonceStore, DisabledDpopNonceStore>();
return services;
}
if (!string.Equals(senderConstraints.Dpop.Nonce.Store, "redis", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(
"Authority requires Authority:Security:SenderConstraints:Dpop:Nonce:Store=redis outside Testing when DPoP is enabled.");
}
if (string.IsNullOrWhiteSpace(senderConstraints.Dpop.Nonce.RedisConnectionString))
{
throw new InvalidOperationException(
"Authority requires Authority:Security:SenderConstraints:Dpop:Nonce:RedisConnectionString outside Testing when DPoP is enabled.");
}
services.AddOptions<ValkeyTransportOptions>()
.Configure(options =>
{
options.ConnectionString = senderConstraints.Dpop.Nonce.RedisConnectionString!;
options.QueueWaitTimeoutSeconds = 0;
});
services.TryAddSingleton<ValkeyConnectionFactory>();
services.TryAddSingleton<IRateLimiterFactory, ValkeyRateLimiterFactory>();
services.TryAddSingleton<IAtomicTokenStoreFactory, ValkeyAtomicTokenStoreFactory>();
services.TryAddSingleton<IIdempotencyStoreFactory, ValkeyIdempotencyStoreFactory>();
services.TryAddSingleton<IDpopReplayCache>(sp =>
new MessagingDpopReplayCache(
sp.GetRequiredService<IIdempotencyStoreFactory>(),
sp.GetRequiredService<TimeProvider>()));
services.TryAddSingleton<IDpopNonceStore>(sp =>
new MessagingDpopNonceStore(
sp.GetRequiredService<IRateLimiterFactory>().Create("authority:dpop:nonce"),
sp.GetRequiredService<IAtomicTokenStoreFactory>().Create<DpopNonceMetadata>("authority:dpop:nonce"),
sp.GetRequiredService<TimeProvider>(),
sp.GetService<ILogger<MessagingDpopNonceStore>>()));
return services;
}
private sealed class DisabledDpopReplayCache : IDpopReplayCache
{
public ValueTask<bool> TryStoreAsync(
string jwtId,
DateTimeOffset expiresAt,
CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(jwtId);
return ValueTask.FromResult(true);
}
}
private sealed class DisabledDpopNonceStore : IDpopNonceStore
{
public ValueTask<DpopNonceIssueResult> IssueAsync(
string audience,
string clientId,
string keyThumbprint,
TimeSpan ttl,
int maxIssuancePerMinute,
CancellationToken cancellationToken = default)
{
return ValueTask.FromResult(DpopNonceIssueResult.Failure("dpop_disabled"));
}
public ValueTask<DpopNonceConsumeResult> TryConsumeAsync(
string nonce,
string audience,
string clientId,
string keyThumbprint,
CancellationToken cancellationToken = default)
{
return ValueTask.FromResult(DpopNonceConsumeResult.NotFound());
}
}
}

View File

@@ -35,6 +35,7 @@
<ProjectReference Include="../../../__Libraries/StellaOps.Configuration.AuthorityPlugin/StellaOps.Configuration.AuthorityPlugin.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.DependencyInjection/StellaOps.DependencyInjection.csproj" />
<ProjectReference Include="../../../Attestor/StellaOps.Attestation/StellaOps.Attestation.csproj" />
<ProjectReference Include="../../../Router/__Libraries/StellaOps.Messaging.Transport.Valkey/StellaOps.Messaging.Transport.Valkey.csproj" />
<ProjectReference Include="../../../Router/__Libraries/StellaOps.Router.AspNet/StellaOps.Router.AspNet.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Audit.Emission/StellaOps.Audit.Emission.csproj" />
</ItemGroup>

View File

@@ -10,3 +10,6 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
| AUDIT-0085-T | DONE | Revalidated 2026-01-06 (coverage reviewed). |
| AUDIT-0085-A | TODO | Reopened 2026-01-06: remove Guid.NewGuid/DateTimeOffset.UtcNow, fix branding error messages, and modularize Program.cs. |
| TASK-033-008 | DONE | Added BCrypt.Net-Next and updated dependency notices (SPRINT_20260120_033). |
| AUTH-DPOP-REAL-001 | DONE | 2026-04-16: non-testing Authority now resolves DPoP replay state through durable Valkey-backed messaging primitives; `Testing` remains explicitly in-memory. |
| AUTH-DPOP-REAL-002 | DONE | 2026-04-16: removed non-testing in-memory DPoP nonce fallback; misconfigured live DPoP now fails fast. |
| AUTH-DPOP-REAL-003 | DONE | 2026-04-16: added restart-survival proof in `AuthorityDpopRuntimeTests` and synced Authority docs/task boards. |