feat(release): add reproducible signed artifacts (#19)
quality-gate / quality (push) Failing after 1m50s
quality-gate / container (push) Has been skipped

This commit is contained in:
KyuubiYoru
2026-07-16 17:48:21 +02:00
parent 07004cd75f
commit cc5793f935
52 changed files with 2568 additions and 73 deletions
@@ -7,10 +7,12 @@
<PackageId>FinalFactory.Rendezvous.Client</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Description>Godot-independent client SDK for Final Factory Rendezvous.</Description>
<PackageTags>final-factory;multiplayer;nat;godot;litenetlib</PackageTags>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../FinalFactory.Rendezvous.Contracts/FinalFactory.Rendezvous.Contracts.csproj" />
<PackageReference Include="LiteNetLib" />
<None Update="README.md" Pack="true" PackagePath="\" />
<None Include="../../CHANGELOG.md" Pack="true" PackagePath="\" Link="CHANGELOG.md" />
</ItemGroup>
</Project>
@@ -4,7 +4,7 @@
".NETStandard,Version=v2.1": {
"LiteNetLib": {
"type": "Direct",
"requested": "[2.1.4, )",
"requested": "[2.1.4, 2.1.4]",
"resolved": "2.1.4",
"contentHash": "KWlxvMw3Urpqj9joD96LRiK+LC62pQNs/zkXRJc+rHnxgkGp+vV703xzDrxRmv+V1YhCFfIGzs5nrVWtREIlyA=="
},
@@ -5,9 +5,13 @@
<RootNamespace>FinalFactory.Rendezvous.Contracts</RootNamespace>
<IsPackable>true</IsPackable>
<PackageId>FinalFactory.Rendezvous.Contracts</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Description>Versioned transport-neutral contracts for Final Factory Rendezvous.</Description>
<PackageTags>final-factory;multiplayer;contracts;godot</PackageTags>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.Json" />
<None Update="README.md" Pack="true" PackagePath="\" />
<None Include="../../CHANGELOG.md" Pack="true" PackagePath="\" Link="CHANGELOG.md" />
</ItemGroup>
</Project>
@@ -0,0 +1,8 @@
# FinalFactory.Rendezvous.Contracts
Transport-neutral v1 HTTP/UDP contract types for Final Factory Rendezvous.
The package targets `netstandard2.1`, contains no Godot or LiteNetLib dependency,
and is versioned with the Client package and server release.
Compatibility and migration policy is maintained in the repository's
`docs/releases/README.md` document.
@@ -4,7 +4,7 @@ using Microsoft.Extensions.Options;
namespace FinalFactory.Rendezvous.Server.Deployment;
internal sealed partial class GracefulDrainService : IHostedService, IDisposable
internal sealed class GracefulDrainService : IHostedService, IDisposable
{
private static readonly TimeSpan PollInterval = TimeSpan.FromMilliseconds(50);
private readonly InMemoryEphemeralRendezvousStore _store;
@@ -84,15 +84,19 @@ internal sealed partial class GracefulDrainService : IHostedService, IDisposable
}
}
[LoggerMessage(
EventId = 1,
Level = LogLevel.Information,
Message = "Graceful drain started with a {DrainDeadlineSeconds}-second deadline")]
private static partial void LogDrainStarted(ILogger logger, int drainDeadlineSeconds);
private static readonly Action<ILogger, int, Exception?> DrainStarted = LoggerMessage.Define<int>(
LogLevel.Information,
new EventId(1, nameof(LogDrainStarted)),
"Graceful drain started with a {DrainDeadlineSeconds}-second deadline");
[LoggerMessage(
EventId = 2,
Level = LogLevel.Information,
Message = "Graceful drain finished after {ElapsedMilliseconds:F0} ms; ephemeral state was cleared")]
private static partial void LogDrainFinished(ILogger logger, double elapsedMilliseconds);
private static readonly Action<ILogger, double, Exception?> DrainFinished = LoggerMessage.Define<double>(
LogLevel.Information,
new EventId(2, nameof(LogDrainFinished)),
"Graceful drain finished after {ElapsedMilliseconds:F0} ms; ephemeral state was cleared");
private static void LogDrainStarted(ILogger logger, int drainDeadlineSeconds) =>
DrainStarted(logger, drainDeadlineSeconds, null);
private static void LogDrainFinished(ILogger logger, double elapsedMilliseconds) =>
DrainFinished(logger, elapsedMilliseconds, null);
}
@@ -3,6 +3,7 @@
<TargetFramework>net10.0</TargetFramework>
<AssemblyName>FinalFactory.Rendezvous.Server</AssemblyName>
<RootNamespace>FinalFactory.Rendezvous.Server</RootNamespace>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<IsPackable>false</IsPackable>
<OpenApiGenerateDocuments>true</OpenApiGenerateDocuments>
<OpenApiDocumentsDirectory>$(MSBuildProjectDirectory)/../../docs/api</OpenApiDocumentsDirectory>
@@ -14,4 +15,74 @@
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
<PackageReference Include="Microsoft.Extensions.ApiDescription.Server" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>RendezvousMinimumClientVersion</_Parameter1>
<_Parameter2>$(MinimumClientVersion)</_Parameter2>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>RendezvousMaximumClientMajorVersion</_Parameter1>
<_Parameter2>$(MaximumClientMajorVersion)</_Parameter2>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>RendezvousUdpContractVersion</_Parameter1>
<_Parameter2>$(UdpContractVersion)</_Parameter2>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>RendezvousConnectionTicketFormatVersion</_Parameter1>
<_Parameter2>$(ConnectionTicketFormatVersion)</_Parameter2>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>RendezvousLiteNetLibMajorVersion</_Parameter1>
<_Parameter2>$(LiteNetLibMajorVersion)</_Parameter2>
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>
<!-- Roslyn and source generators consume syntax trees in item order. Keep
this ordinal manifest explicit so clean builds are byte reproducible. -->
<Compile Include="Abuse/AbuseProtectionOptions.cs" />
<Compile Include="Abuse/AbuseProtectionService.cs" />
<Compile Include="Abuse/HttpAbuseProtectionMiddleware.cs" />
<Compile Include="Abuse/TrustedProxyForwarding.cs" />
<Compile Include="Browser/EphemeralCursorProtector.cs" />
<Compile Include="Browser/SessionBrowserCursorCodec.cs" />
<Compile Include="Browser/SessionBrowserService.cs" />
<Compile Include="ConnectionOutcomes/ConnectionOutcomeService.cs" />
<Compile Include="Deployment/DeploymentOptions.cs" />
<Compile Include="Deployment/GracefulDrainService.cs" />
<Compile Include="Http/ContractEndpoints.cs" />
<Compile Include="Http/RendezvousExceptionHandler.cs" />
<Compile Include="JoinAttempts/JoinAttemptCursorCodec.cs" />
<Compile Include="JoinAttempts/JoinAttemptService.cs" />
<Compile Include="Observability/AuditOptions.cs" />
<Compile Include="Observability/AuditTrail.cs" />
<Compile Include="Observability/HealthEndpoints.cs" />
<Compile Include="Observability/RendezvousReadiness.cs" />
<Compile Include="Observability/RendezvousTelemetry.cs" />
<Compile Include="Observability/TelemetryMiddleware.cs" />
<Compile Include="Operations/OperatorEndpoints.cs" />
<Compile Include="Operations/OperatorModels.cs" />
<Compile Include="Operations/OperatorService.cs" />
<Compile Include="Operations/ReleaseCompatibility.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties/AssemblyInfo.cs" />
<Compile Include="Provisioning/GamePolicy.cs" />
<Compile Include="Provisioning/GamePolicyRegistry.cs" />
<Compile Include="Provisioning/PrincipalCredentialService.cs" />
<Compile Include="Provisioning/Principals.cs" />
<Compile Include="Provisioning/ProvisioningOptions.cs" />
<Compile Include="Provisioning/ProvisioningRuntime.cs" />
<Compile Include="Provisioning/PublisherAuthorizationService.cs" />
<Compile Include="Provisioning/SecretProviders.cs" />
<Compile Include="Provisioning/SigningKeyRing.cs" />
<Compile Include="Sessions/EphemeralCapabilityIssuer.cs" />
<Compile Include="Sessions/SessionLeaseService.cs" />
<Compile Include="State/EphemeralStateContracts.cs" />
<Compile Include="State/InMemoryEphemeralRendezvousStore.cs" />
<Compile Include="State/StoreResultMapping.cs" />
<Compile Include="Transport/LiteNetNatRequestCodec.cs" />
<Compile Include="Transport/NatMediationProcessor.cs" />
<Compile Include="Transport/UdpMediatorOptions.cs" />
<Compile Include="Transport/UdpMediatorService.cs" />
</ItemGroup>
</Project>
@@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Diagnostics;
namespace FinalFactory.Rendezvous.Server.Http;
internal sealed partial class RendezvousExceptionHandler(
internal sealed class RendezvousExceptionHandler(
ILogger<RendezvousExceptionHandler> logger) : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(
@@ -51,13 +51,15 @@ internal sealed partial class RendezvousExceptionHandler(
return true;
}
[LoggerMessage(
EventId = 200,
Level = LogLevel.Warning,
Message = "Request failed with {FailureKind} and HTTP status {StatusCode}; correlation {CorrelationId}")]
private static partial void LogRequestFailure(
private static readonly Action<ILogger, string, int, string, Exception?> RequestFailure =
LoggerMessage.Define<string, int, string>(
LogLevel.Warning,
new EventId(200, nameof(LogRequestFailure)),
"Request failed with {FailureKind} and HTTP status {StatusCode}; correlation {CorrelationId}");
private static void LogRequestFailure(
ILogger logger,
string failureKind,
int statusCode,
string correlationId);
string correlationId) => RequestFailure(logger, failureKind, statusCode, correlationId, null);
}
@@ -4,7 +4,7 @@ using Microsoft.Extensions.Options;
namespace FinalFactory.Rendezvous.Server.Observability;
internal sealed partial class AuditTrail
internal sealed class AuditTrail
{
private readonly object _gate = new();
private readonly LinkedList<AuditEntry> _entries = [];
@@ -57,7 +57,6 @@ internal sealed partial class AuditTrail
_telemetry.RecordAudit(action, result);
LogOperatorAction(
_logger,
entry.Timestamp,
entry.ActorFingerprint,
action,
result,
@@ -105,19 +104,28 @@ internal sealed partial class AuditTrail
return Convert.ToHexString(digest.AsSpan(0, 12));
}
[LoggerMessage(
EventId = 100,
Level = LogLevel.Information,
Message = "Operator audit at {Timestamp}: actor {ActorFingerprint} action {Action} completed with {Result} for {TargetKind} target {TargetFingerprint}; correlation {CorrelationId}")]
private static partial void LogOperatorAction(
private static readonly Action<ILogger, string, string, string, string, string, string, Exception?>
OperatorAction = LoggerMessage.Define<string, string, string, string, string, string>(
LogLevel.Information,
new EventId(100, nameof(LogOperatorAction)),
"Operator audit: actor {ActorFingerprint} action {Action} completed with {Result} for {TargetKind} target {TargetFingerprint}; correlation {CorrelationId}");
private static void LogOperatorAction(
ILogger logger,
DateTimeOffset timestamp,
string actorFingerprint,
string action,
string result,
string targetKind,
string targetFingerprint,
string correlationId);
string correlationId) => OperatorAction(
logger,
actorFingerprint,
action,
result,
targetKind,
targetFingerprint,
correlationId,
null);
}
internal sealed record AuditEntry(
@@ -3,6 +3,7 @@ namespace FinalFactory.Rendezvous.Server.Operations;
internal sealed record OperatorStatusResponse
{
public required string Status { get; init; }
public required OperatorCompatibilityResponse Compatibility { get; init; }
public required OperatorReadinessResponse Readiness { get; init; }
public required OperatorStoreResponse Store { get; init; }
public required IReadOnlyList<OperatorTenantResponse> Tenants { get; init; }
@@ -10,6 +11,18 @@ internal sealed record OperatorStatusResponse
public required IReadOnlyDictionary<string, long> AuditCounts { get; init; }
}
internal sealed record OperatorCompatibilityResponse
{
public required string ServerVersion { get; init; }
public required string MinimumClientVersion { get; init; }
public required int MaximumClientMajorVersion { get; init; }
public required IReadOnlyList<int> HttpContractVersions { get; init; }
public required IReadOnlyList<int> UdpContractVersions { get; init; }
public required IReadOnlyList<int> ConnectionTicketFormatVersions { get; init; }
public required int LiteNetLibMajorVersion { get; init; }
public required string GameplayProtocolCompatibility { get; init; }
}
internal sealed record OperatorReadinessResponse
{
public required bool HttpListener { get; init; }
@@ -19,6 +19,7 @@ internal sealed class OperatorService(
return new OperatorStatusResponse
{
Status = readinessSnapshot.IsReady ? "ready" : "not-ready",
Compatibility = ReleaseCompatibility.CreateResponse(),
Readiness = new OperatorReadinessResponse
{
HttpListener = readinessSnapshot.HttpListenerReady,
@@ -0,0 +1,41 @@
using System.Reflection;
using FinalFactory.Rendezvous.Contracts;
namespace FinalFactory.Rendezvous.Server.Operations;
internal static class ReleaseCompatibility
{
private static readonly Assembly ServerAssembly = typeof(ReleaseCompatibility).Assembly;
internal static string MinimumClientVersion => Metadata("RendezvousMinimumClientVersion");
internal static int MaximumClientMajorVersion => MetadataInteger("RendezvousMaximumClientMajorVersion");
internal static int UdpContractVersion => MetadataInteger("RendezvousUdpContractVersion");
internal static int ConnectionTicketFormatVersion => MetadataInteger("RendezvousConnectionTicketFormatVersion");
internal static int LiteNetLibMajorVersion => MetadataInteger("RendezvousLiteNetLibMajorVersion");
internal static OperatorCompatibilityResponse CreateResponse() => new()
{
ServerVersion = ServerAssembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?
.InformationalVersion.Split('+', 2)[0]
?? ServerAssembly.GetName().Version?.ToString(3)
?? "unknown",
MinimumClientVersion = MinimumClientVersion,
MaximumClientMajorVersion = MaximumClientMajorVersion,
HttpContractVersions = [ContractLimits.ContractVersion],
UdpContractVersions = [UdpContractVersion],
ConnectionTicketFormatVersions = [ConnectionTicketFormatVersion],
LiteNetLibMajorVersion = LiteNetLibMajorVersion,
GameplayProtocolCompatibility = "exact-per-tenant",
};
private static string Metadata(string key) => ServerAssembly
.GetCustomAttributes<AssemblyMetadataAttribute>()
.Single(attribute => string.Equals(attribute.Key, key, StringComparison.Ordinal))
.Value
?? throw new InvalidOperationException($"Assembly metadata {key} has no value.");
private static int MetadataInteger(string key) => int.Parse(
Metadata(key),
System.Globalization.CultureInfo.InvariantCulture);
}
@@ -8,7 +8,7 @@ using Microsoft.Extensions.Options;
namespace FinalFactory.Rendezvous.Server.Transport;
internal sealed partial class UdpMediatorService : BackgroundService
internal sealed class UdpMediatorService : BackgroundService
{
private readonly ILogger<UdpMediatorService> _logger;
private readonly UdpMediatorOptions _options;
@@ -133,20 +133,23 @@ internal sealed partial class UdpMediatorService : BackgroundService
manager?.Stop();
}
[LoggerMessage(
EventId = 1,
Level = LogLevel.Information,
Message = "UDP mediator listening on {ListenAddress}:{ListenPort}")]
private static partial void LogMediatorListening(
private static readonly Action<ILogger, IPAddress, int, Exception?> MediatorListening =
LoggerMessage.Define<IPAddress, int>(
LogLevel.Information,
new EventId(1, nameof(LogMediatorListening)),
"UDP mediator listening on {ListenAddress}:{ListenPort}");
private static readonly Action<ILogger, Exception?> MediatorStopped = LoggerMessage.Define(
LogLevel.Information,
new EventId(2, nameof(LogMediatorStopped)),
"UDP mediator stopped");
private static void LogMediatorListening(
ILogger logger,
IPAddress listenAddress,
int listenPort);
int listenPort) => MediatorListening(logger, listenAddress, listenPort, null);
[LoggerMessage(
EventId = 2,
Level = LogLevel.Information,
Message = "UDP mediator stopped")]
private static partial void LogMediatorStopped(ILogger logger);
private static void LogMediatorStopped(ILogger logger) => MediatorStopped(logger, null);
private sealed class LiteNetIntroductionSink(NatPunchModule module) : INatIntroductionSink
{
@@ -4,7 +4,7 @@
"net10.0": {
"LiteNetLib": {
"type": "Direct",
"requested": "[2.1.4, )",
"requested": "[2.1.4, 2.1.4]",
"resolved": "2.1.4",
"contentHash": "KWlxvMw3Urpqj9joD96LRiK+LC62pQNs/zkXRJc+rHnxgkGp+vV703xzDrxRmv+V1YhCFfIGzs5nrVWtREIlyA=="
},
@@ -4,7 +4,7 @@
"net8.0": {
"LiteNetLib": {
"type": "Direct",
"requested": "[2.1.4, )",
"requested": "[2.1.4, 2.1.4]",
"resolved": "2.1.4",
"contentHash": "KWlxvMw3Urpqj9joD96LRiK+LC62pQNs/zkXRJc+rHnxgkGp+vV703xzDrxRmv+V1YhCFfIGzs5nrVWtREIlyA=="
},
@@ -22,7 +22,7 @@
"type": "Project",
"dependencies": {
"FinalFactory.Rendezvous.Contracts": "[1.0.0, )",
"LiteNetLib": "[2.1.4, )"
"LiteNetLib": "[2.1.4, 2.1.4]"
}
},
"finalfactory.rendezvous.contracts": {