Use generic node documents for service state

This commit is contained in:
Meta Vens 2026-06-29 01:19:11 +01:00
commit 7eda899f74
5 changed files with 124 additions and 132 deletions

View file

@ -50,7 +50,6 @@ if (options.Once)
await rtsPump.ConfigureAwait(false);
return;
}
var stopped = new TaskCompletionSource<object?>();
Console.CancelKeyPress += (_, eventArgs) =>
{
@ -83,7 +82,7 @@ while (!stopped.Task.IsCompleted)
if (buildPublications)
{
nextApiPublicationUtc = DateTimeOffset.UtcNow.Add(options.ApiPublicationInterval);
discoveryHost.Update(await node.VerseHostSettings().ReadAsync().ConfigureAwait(false));
discoveryHost.Update(await node.MutableDocument<AetheriaVerseHostSettings>(AetheriaStateNode.VerseHostSettingsKey).ReadAsync().ConfigureAwait(false));
await PublishRuntimeSessionAsync(node, options, startedAtUtc, "running").ConfigureAwait(false);
}
if (tick.Frame.FrameId % 120 == 0)
@ -798,12 +797,12 @@ static async Task AcceptEveCommandsAsync(AetheriaStateNode node, AetheriaDaemonH
var now = DateTimeOffset.UtcNow.ToString("O");
try
{
var existingStatus = await node.EveCommandAcceptanceStatus().ReadAsync().ConfigureAwait(false);
var existingStatus = await node.MutableDocument<AetheriaEveCommandAcceptanceStatus>(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReadAsync().ConfigureAwait(false);
var report = await AetheriaEveCommandBridge.AcceptObservedAsync(
node,
existingStatus?.AccountedCommandIds)
.ConfigureAwait(false);
await node.EveCommandAcceptanceStatus()
await node.MutableDocument<AetheriaEveCommandAcceptanceStatus>(AetheriaStateNode.EveCommandAcceptanceStatusKey)
.ReplaceAsync(new AetheriaEveCommandAcceptanceStatus
{
RuntimeId = options.DaemonId,
@ -829,8 +828,8 @@ static async Task AcceptEveCommandsAsync(AetheriaStateNode node, AetheriaDaemonH
}
catch (Exception ex)
{
var existing = await node.EveCommandAcceptanceStatus().ReadAsync().ConfigureAwait(false);
await node.EveCommandAcceptanceStatus()
var existing = await node.MutableDocument<AetheriaEveCommandAcceptanceStatus>(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReadAsync().ConfigureAwait(false);
await node.MutableDocument<AetheriaEveCommandAcceptanceStatus>(AetheriaStateNode.EveCommandAcceptanceStatusKey)
.ReplaceAsync(new AetheriaEveCommandAcceptanceStatus
{
RuntimeId = options.DaemonId,
@ -854,20 +853,20 @@ static async Task PublishStateSurfacesAsync(
string updatedAtUtc)
{
var verseHost = await EnsureVerseHostSettingsAsync(node, options, updatedAtUtc).ConfigureAwait(false);
var eveStatus = await node.EveCommandAcceptanceStatus().ReadAsync().ConfigureAwait(false);
var runtimeSession = await node.RuntimeSession(options.DaemonId).ReadAsync().ConfigureAwait(false);
var playerSettings = await node.PlayerSettings().ReadAsync().ConfigureAwait(false) ?? new AetheriaPlayerSettings();
var eveStatus = await node.MutableDocument<AetheriaEveCommandAcceptanceStatus>(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReadAsync().ConfigureAwait(false);
var runtimeSession = await node.MutableDocument<AetheriaRuntimeSession>(AetheriaStateNode.RuntimeSessionKey(options.DaemonId)).ReadAsync().ConfigureAwait(false);
var playerSettings = await node.MutableDocument<AetheriaPlayerSettings>(AetheriaStateNode.PlayerSettingsKey).ReadAsync().ConfigureAwait(false) ?? new AetheriaPlayerSettings();
var playerSettingsUpdatedAt = string.IsNullOrWhiteSpace(playerSettings.LastUpdatedAtUtc)
? updatedAtUtc
: playerSettings.LastUpdatedAtUtc;
await node.OperationsSurface()
await node.MutableDocument<EveSurfaceState>(AetheriaStateNode.OperationsSurfaceKey)
.ReplaceAsync(AetheriaOperationsSurfaceProjector.Build(eveStatus, verseHost, runtimeSession))
.ConfigureAwait(false);
await node.PlayerSettingsSurface()
await node.MutableDocument<EveSurfaceState>(AetheriaStateNode.PlayerSettingsSurfaceKey)
.ReplaceAsync(AetheriaPlayerSettingsSurfaceProjector.Build(playerSettings, playerSettingsUpdatedAt))
.ConfigureAwait(false);
await node.ProviderAdvertisementSurface()
await node.MutableDocument<EveProviderAdvertisementState>(AetheriaStateNode.ProviderAdvertisementSurfaceKey)
.ReplaceAsync(AetheriaProviderAdvertisementProjector.Build(verseHost, node.StatePath, updatedAtUtc))
.ConfigureAwait(false);
await node.FlushAsync().ConfigureAwait(false);
@ -880,7 +879,7 @@ static async Task PublishRuntimeSessionAsync(
string status)
{
var now = DateTimeOffset.UtcNow.ToString("O");
await node.RuntimeSession(options.DaemonId)
await node.MutableDocument<AetheriaRuntimeSession>(AetheriaStateNode.RuntimeSessionKey(options.DaemonId))
.ReplaceAsync(new AetheriaRuntimeSession
{
RuntimeId = options.DaemonId,
@ -917,7 +916,7 @@ static async Task EnsureWorldDocumentAsync(AetheriaStateNode node)
static async Task EnsureTradeValuePolicyAsync(AetheriaStateNode node, string now)
{
var tradeValuePolicy = node.TradeValuePolicy();
var tradeValuePolicy = node.MutableDocument<AetheriaTradeValuePolicy>(AetheriaStateNode.TradeValuePolicyKey);
var existing = await tradeValuePolicy.ReadAsync().ConfigureAwait(false);
if (existing != null)
return;
@ -930,7 +929,7 @@ static async Task EnsureTradeValuePolicyAsync(AetheriaStateNode node, string now
static async Task EnsurePlayableRunDocumentsAsync(AetheriaStateNode node, string now)
{
var settings = await node.PlayerSettings().ReadAsync().ConfigureAwait(false);
var settings = await node.MutableDocument<AetheriaPlayerSettings>(AetheriaStateNode.PlayerSettingsKey).ReadAsync().ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(settings?.ActiveRunKey))
{
var existingRun = await ReadRuntimeRunCheckpointAsync(node).ConfigureAwait(false);
@ -950,7 +949,7 @@ static async Task EnsurePlayableRunDocumentsAsync(AetheriaStateNode node, string
settings.PlayerName = string.IsNullOrWhiteSpace(settings.PlayerName) ? "Codex RTS" : settings.PlayerName;
settings.TutorialPassed = true;
settings.LastUpdatedAtUtc = now;
await node.PlayerSettings()
await node.MutableDocument<AetheriaPlayerSettings>(AetheriaStateNode.PlayerSettingsKey)
.ReplaceAsync(settings)
.ConfigureAwait(false);
@ -1322,7 +1321,7 @@ static async Task EnsureVerseAuthorityPolicyAsync(
static async Task<AetheriaRuntimeRunCheckpointCommit?> ReadRuntimeRunCheckpointAsync(AetheriaStateNode node)
{
var settings = await node.PlayerSettings().ReadAsync().ConfigureAwait(false);
var settings = await node.MutableDocument<AetheriaPlayerSettings>(AetheriaStateNode.PlayerSettingsKey).ReadAsync().ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(settings?.ActiveRunKey))
return null;
@ -1749,7 +1748,7 @@ static async Task<AetheriaVerseHostSettings> EnsureVerseHostSettingsAsync(
AetheriaDaemonHostOptions options,
string now)
{
var existing = await node.VerseHostSettings().ReadAsync().ConfigureAwait(false);
var existing = await node.MutableDocument<AetheriaVerseHostSettings>(AetheriaStateNode.VerseHostSettingsKey).ReadAsync().ConfigureAwait(false);
var normalized = AetheriaVerseHostSettingsNormalizer.Normalize(existing);
normalized.ServiceId = options.DaemonId;
normalized.VerseId = options.VerseId;
@ -1760,7 +1759,7 @@ static async Task<AetheriaVerseHostSettings> EnsureVerseHostSettingsAsync(
!AetheriaVerseHostSettingsNormalizer.Equivalent(existing, normalized))
{
normalized.LastUpdatedAtUtc = now;
await node.VerseHostSettings()
await node.MutableDocument<AetheriaVerseHostSettings>(AetheriaStateNode.VerseHostSettingsKey)
.ReplaceAsync(normalized)
.ConfigureAwait(false);
}

View file

@ -94,17 +94,17 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s
await node.FlushAsync();
var runtimeCatalog = await node.RuntimeCatalog().LatestAsync().ConfigureAwait(false);
await node.CatalogSurface()
await node.MutableDocument<EveSurfaceState>(AetheriaStateNode.CatalogSurfaceKey)
.ReplaceAsync(AetheriaCatalogSurfaceProjector.Build(runtimeCatalog, now));
var verseHostSettings = AetheriaVerseHostSettingsNormalizer.Normalize(new AetheriaVerseHostSettings
{
LastUpdatedAtUtc = now
});
await node.VerseHostSettings().ReplaceAsync(verseHostSettings);
await node.OperationsSurface()
await node.MutableDocument<AetheriaVerseHostSettings>(AetheriaStateNode.VerseHostSettingsKey).ReplaceAsync(verseHostSettings);
await node.MutableDocument<EveSurfaceState>(AetheriaStateNode.OperationsSurfaceKey)
.ReplaceAsync(AetheriaOperationsSurfaceProjector.Build(verseHostSettings: verseHostSettings));
await node.ProviderAdvertisementSurface()
await node.MutableDocument<EveProviderAdvertisementState>(AetheriaStateNode.ProviderAdvertisementSurfaceKey)
.ReplaceAsync(AetheriaProviderAdvertisementProjector.Build(verseHostSettings, statePath, now));
var daemonProvider = AetheriaRuntimeDaemonProviderAdvertisementDocument.Create(
statePath,
@ -171,7 +171,7 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s
}
await node.MutableDocument<EveSurfaceState>(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface)
.ReplaceAsync(AetheriaRuntimeEveSurfaceStateProjector.ToState(daemonGameSurface));
await node.RuntimeSession("smoke-runtime").ReplaceAsync(new AetheriaRuntimeSession
await node.MutableDocument<AetheriaRuntimeSession>(AetheriaStateNode.RuntimeSessionKey("smoke-runtime")).ReplaceAsync(new AetheriaRuntimeSession
{
RuntimeId = "smoke-runtime",
Role = "state-smoke",
@ -179,9 +179,9 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s
LastSeenAtUtc = now,
Status = "running"
});
await node.OperationsSurface().ReplaceAsync(AetheriaOperationsSurfaceProjector.Build(
await node.MutableDocument<EveSurfaceState>(AetheriaStateNode.OperationsSurfaceKey).ReplaceAsync(AetheriaOperationsSurfaceProjector.Build(
verseHostSettings: verseHostSettings,
runtimeSession: await node.RuntimeSession("smoke-runtime").ReadAsync()));
runtimeSession: await node.MutableDocument<AetheriaRuntimeSession>(AetheriaStateNode.RuntimeSessionKey("smoke-runtime")).ReadAsync()));
await node.LoadoutTemplate(loadoutKey).ReplaceAsync(new AetheriaLoadoutTemplate
{
@ -493,7 +493,7 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s
UpdatedAtUtc = now
});
await node.PlayerSettings().ReplaceAsync(new AetheriaPlayerSettings
await node.MutableDocument<AetheriaPlayerSettings>(AetheriaStateNode.PlayerSettingsKey).ReplaceAsync(new AetheriaPlayerSettings
{
ActiveRunKey = runKey.ToString(),
LastUpdatedAtUtc = now,
@ -531,9 +531,9 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s
ActionBarInputs = ["<Keyboard>/1", "<Mouse>/leftButton"]
}
});
await node.PlayerSettingsSurface()
await node.MutableDocument<EveSurfaceState>(AetheriaStateNode.PlayerSettingsSurfaceKey)
.ReplaceAsync(AetheriaPlayerSettingsSurfaceProjector.Build(
await node.PlayerSettings().ReadAsync(),
await node.MutableDocument<AetheriaPlayerSettings>(AetheriaStateNode.PlayerSettingsKey).ReadAsync(),
now));
using var eveCommandClient = await AetheriaClient.OpenAsync(
@ -608,15 +608,14 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s
LastRejectedReason = eveCommandReport.LastRejectedReason,
Status = eveCommandReport.RejectedCommands > 0 ? "rejected" : "ok"
};
await node.EveCommandAcceptanceStatus().ReplaceAsync(eveCommandStatus);
await node.OperationsSurface().ReplaceAsync(AetheriaOperationsSurfaceProjector.Build(
await node.MutableDocument<AetheriaEveCommandAcceptanceStatus>(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReplaceAsync(eveCommandStatus);
await node.MutableDocument<EveSurfaceState>(AetheriaStateNode.OperationsSurfaceKey).ReplaceAsync(AetheriaOperationsSurfaceProjector.Build(
eveCommandStatus,
verseHostSettings: await node.VerseHostSettings().ReadAsync(),
runtimeSession: await node.RuntimeSession("smoke-runtime").ReadAsync()));
verseHostSettings: await node.MutableDocument<AetheriaVerseHostSettings>(AetheriaStateNode.VerseHostSettingsKey).ReadAsync(),
runtimeSession: await node.MutableDocument<AetheriaRuntimeSession>(AetheriaStateNode.RuntimeSessionKey("smoke-runtime")).ReadAsync()));
await node.FlushAsync();
}
await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aetheria-state-smoke-reopen"))
{
var world = await reopened.World().ReadAsync();
@ -624,11 +623,11 @@ await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aether
var faction = await reopened.CorporationByLegacyId(factionLegacyId).ReadAsync();
var nameFile = await reopened.NameFileByLegacyId(nameFileLegacyId).ReadAsync();
var quarantine = await reopened.LegacyCatalogQuarantine().ReadAsync();
var catalogSurface = await reopened.CatalogSurface().ReadAsync();
var eveCommandStatus = await reopened.EveCommandAcceptanceStatus().ReadAsync();
var operationsSurface = await reopened.OperationsSurface().ReadAsync();
var playerSettingsSurface = await reopened.PlayerSettingsSurface().ReadAsync();
var advertisement = await reopened.ProviderAdvertisementSurface().ReadAsync();
var catalogSurface = await reopened.MutableDocument<EveSurfaceState>(AetheriaStateNode.CatalogSurfaceKey).ReadAsync();
var eveCommandStatus = await reopened.MutableDocument<AetheriaEveCommandAcceptanceStatus>(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReadAsync();
var operationsSurface = await reopened.MutableDocument<EveSurfaceState>(AetheriaStateNode.OperationsSurfaceKey).ReadAsync();
var playerSettingsSurface = await reopened.MutableDocument<EveSurfaceState>(AetheriaStateNode.PlayerSettingsSurfaceKey).ReadAsync();
var advertisement = await reopened.MutableDocument<EveProviderAdvertisementState>(AetheriaStateNode.ProviderAdvertisementSurfaceKey).ReadAsync();
var daemonProvider = await reopened.MutableDocument<AetheriaRuntimeDaemonProviderAdvertisementDocument>(AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement)
.ReadAsync();
var daemonHealth = await reopened.MutableDocument<AetheriaRuntimeDaemonHealthDocument>(AetheriaRuntimeVerseRecordKeys.DaemonHealth)
@ -639,8 +638,8 @@ await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aether
.ReadAsync();
var daemonGameSurface = await reopened.MutableDocument<EveSurfaceState>(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface)
.ReadAsync();
var runtimeSession = await reopened.RuntimeSession("smoke-runtime").ReadAsync();
var playerSettings = await reopened.PlayerSettings().ReadAsync();
var runtimeSession = await reopened.MutableDocument<AetheriaRuntimeSession>(AetheriaStateNode.RuntimeSessionKey("smoke-runtime")).ReadAsync();
var playerSettings = await reopened.MutableDocument<AetheriaPlayerSettings>(AetheriaStateNode.PlayerSettingsKey).ReadAsync();
var loadout = await reopened.LoadoutTemplate(loadoutKey).ReadAsync();
var runState = await reopened.RunState(runKey).ReadAsync();
var zoneState = await reopened.ZoneState(zoneKey).ReadAsync();

View file

@ -110,7 +110,7 @@ var ledger = await node.MigrationLedger().ReadAsync()
?? throw new InvalidOperationException("Missing typed migration ledger.");
var quarantine = await node.LegacyCatalogQuarantine().ReadAsync()
?? throw new InvalidOperationException("Missing legacy catalog quarantine document.");
var publishedSurface = await node.CatalogSurface().ReadAsync()
var publishedSurface = await node.MutableDocument<EveSurfaceState>(AetheriaStateNode.CatalogSurfaceKey).ReadAsync()
?? throw new InvalidOperationException("Missing Aetheria catalog Eve surface document.");
var items = node.Cache.GetAll<AetheriaItemDefinition>().ToArray();
@ -118,7 +118,7 @@ var corporations = node.Cache.GetAll<AetheriaCorporation>().ToArray();
var nameFiles = node.Cache.GetAll<AetheriaNameFile>().ToArray();
var catalog = await node.RuntimeCatalog().LatestAsync().ConfigureAwait(false);
var surface = AetheriaCatalogSurfaceProjector.Build(catalog, DateTimeOffset.UtcNow.ToString("O"));
var tradeValuePolicy = await node.TradeValuePolicy().ReadAsync()
var tradeValuePolicy = await node.MutableDocument<AetheriaTradeValuePolicy>(AetheriaStateNode.TradeValuePolicyKey).ReadAsync()
?? throw new InvalidOperationException("Missing authored trade value policy document.");
await RequireTradeValuePolicyEveCommandPersistsAsync();
var runtimeCatalog = catalog;
@ -146,7 +146,6 @@ if (nameFiles.Any(nameFile => nameFile.Names.Length == 0 || nameFile.Names.Lengt
{
throw new InvalidOperationException("Typed name files did not import their full name arrays.");
}
if (tradeValuePolicy.QualityPriceModifier == null ||
tradeValuePolicy.QualityPriceModifier.Exponent <= 0 ||
tradeValuePolicy.QualityPriceModifier.Maximum <= tradeValuePolicy.QualityPriceModifier.Minimum ||
@ -6809,7 +6808,7 @@ static void RequirePlayerSettingsEveSurface(string root)
if (!bridge.Contains("AcceptedPlayerSettingsCommands", StringComparison.Ordinal) ||
!bridge.Contains("ExecutePlayerSettingsCommandAsync", StringComparison.Ordinal) ||
!bridge.Contains("PlayerSettingsSurface()", StringComparison.Ordinal) ||
!bridge.Contains("MutableDocument<EveSurfaceState>(AetheriaStateNode.PlayerSettingsSurfaceKey)", StringComparison.Ordinal) ||
!bridge.Contains(".ReplaceAsync(AetheriaPlayerSettingsSurfaceProjector.Build", StringComparison.Ordinal) ||
!bridge.Contains("SetPlayerName", StringComparison.Ordinal) ||
!bridge.Contains("command.PlayerSettings.PlayerName", StringComparison.Ordinal))
@ -6825,7 +6824,7 @@ static void RequirePlayerSettingsEveSurface(string root)
"Provider advertisement no longer publishes the player-settings Eve surface and commands.");
}
if (!daemonHost.Contains("PlayerSettingsSurface()", StringComparison.Ordinal) ||
if (!daemonHost.Contains("MutableDocument<EveSurfaceState>(AetheriaStateNode.PlayerSettingsSurfaceKey)", StringComparison.Ordinal) ||
!daemonHost.Contains(".ReplaceAsync(AetheriaPlayerSettingsSurfaceProjector.Build", StringComparison.Ordinal) ||
!daemonHost.Contains("AetheriaPlayerSettingsSurfaceProjector.Build", StringComparison.Ordinal))
{
@ -6833,6 +6832,13 @@ static void RequirePlayerSettingsEveSurface(string root)
"Aetheria.State.Daemon no longer republishes the provider-owned player-settings Eve surface.");
}
if (bridge.Contains("PlayerSettingsSurface()", StringComparison.Ordinal) ||
daemonHost.Contains("PlayerSettingsSurface()", StringComparison.Ordinal))
{
throw new InvalidOperationException(
"Player-settings Eve publication still uses named AetheriaStateNode surface helpers instead of generic mutable typed documents.");
}
if (!sharedCommands.Contains("SurfaceId = \"aetheria.player_settings\"", StringComparison.Ordinal) ||
!sharedCommands.Contains("public static bool IsKnown", StringComparison.Ordinal) ||
!sharedCommands.Contains("SetPlayerName", StringComparison.Ordinal))
@ -8626,9 +8632,10 @@ static void RequireVerseHostSettingsAuthority(string root)
"Aetheria document registry does not register typed verse-host settings.");
}
if (!node.Contains("public CultMeshMutableStatePointer<AetheriaVerseHostSettings> VerseHostSettings()", StringComparison.Ordinal) ||
!node.Contains("public CultMeshMutableStatePointer<AetheriaPlayerSettings> PlayerSettings()", StringComparison.Ordinal) ||
!node.Contains("public CultMeshMutableStatePointer<AetheriaEveCommandAcceptanceStatus> EveCommandAcceptanceStatus()", StringComparison.Ordinal) ||
if (!node.Contains("public CultMeshMutableStatePointer<TDocument> MutableDocument<TDocument>(CultRecordKey key)", StringComparison.Ordinal) ||
!node.Contains("public static CultRecordKey VerseHostSettingsKey", StringComparison.Ordinal) ||
!node.Contains("public static CultRecordKey PlayerSettingsKey", StringComparison.Ordinal) ||
!node.Contains("public static CultRecordKey EveCommandAcceptanceStatusKey", StringComparison.Ordinal) ||
!node.Contains("public CultMeshNode MeshNode => _node;", StringComparison.Ordinal) ||
!node.Contains("global:aetheria.verse_host_settings.v1", StringComparison.Ordinal))
{
@ -8636,6 +8643,20 @@ static void RequireVerseHostSettingsAuthority(string root)
"Aetheria state node does not expose typed verse-host/settings managed state pointers.");
}
if (node.Contains("public CultMeshMutableStatePointer<AetheriaVerseHostSettings> VerseHostSettings()", StringComparison.Ordinal) ||
node.Contains("public CultMeshMutableStatePointer<AetheriaPlayerSettings> PlayerSettings()", StringComparison.Ordinal) ||
node.Contains("public CultMeshMutableStatePointer<AetheriaEveCommandAcceptanceStatus> EveCommandAcceptanceStatus()", StringComparison.Ordinal) ||
node.Contains("public CultMeshMutableStatePointer<EveSurfaceState> CatalogSurface()", StringComparison.Ordinal) ||
node.Contains("public CultMeshMutableStatePointer<EveSurfaceState> OperationsSurface()", StringComparison.Ordinal) ||
node.Contains("public CultMeshMutableStatePointer<EveSurfaceState> PlayerSettingsSurface()", StringComparison.Ordinal) ||
node.Contains("public CultMeshMutableStatePointer<EveProviderAdvertisementState> ProviderAdvertisementSurface()", StringComparison.Ordinal) ||
node.Contains("public CultMeshMutableStatePointer<AetheriaRuntimeSession> RuntimeSession(", StringComparison.Ordinal) ||
node.Contains("public CultMeshMutableStatePointer<AetheriaTradeValuePolicy> TradeValuePolicy()", StringComparison.Ordinal))
{
throw new InvalidOperationException(
"Aetheria state node still exposes named document helpers instead of generic mutable typed document access.");
}
var forbiddenHardcodedProviderSymbols = new[]
{
"VerseId = \"aetheria.local\"",
@ -8677,8 +8698,8 @@ static void RequireVerseHostSettingsAuthority(string root)
"discoveryHost.Update",
"ReadOption(args, \"--verse-id\")",
"ReadOption(args, \"--cultmesh-address\")",
"node.VerseHostSettings().ReadAsync()",
"node.VerseHostSettings()",
"node.MutableDocument<AetheriaVerseHostSettings>(AetheriaStateNode.VerseHostSettingsKey).ReadAsync()",
"node.MutableDocument<AetheriaVerseHostSettings>(AetheriaStateNode.VerseHostSettingsKey)",
".ReplaceAsync(normalized)",
"AetheriaProviderAdvertisementProjector.Build(verseHost, node.StatePath, updatedAtUtc)"
};
@ -9675,9 +9696,9 @@ static void RequireVerseSettingsShellAndBridge(string root)
"AetheriaRuntimeEveCommandKind.VerseHostRefresh",
"AetheriaRuntimeEveCommandKind.CycleVerseHostVisibility",
"ExecuteVerseHostCommandAsync",
"node.VerseHostSettings()",
"node.MutableDocument<AetheriaVerseHostSettings>(AetheriaStateNode.VerseHostSettingsKey)",
".ReplaceAsync(normalized)",
"node.ProviderAdvertisementSurface()",
"node.MutableDocument<EveProviderAdvertisementState>(AetheriaStateNode.ProviderAdvertisementSurfaceKey)",
"AetheriaOperationsSurfaceProjector.Build(",
"switch (command.Kind)"
};
@ -10944,13 +10965,13 @@ static void RequireDaemonVersePublication(string root)
"ReadObservedDaemonCommands()",
"currentFrame?.AccountedCommandIds ?? Array.Empty<string>()",
"AetheriaRuntimeDaemonTickRunner.Tick(",
"node.EveCommandAcceptanceStatus().ReadAsync()",
"node.EveCommandAcceptanceStatus()",
"node.RuntimeSession(options.DaemonId)",
"node.PlayerSettings().ReadAsync()",
"node.OperationsSurface()",
"node.PlayerSettingsSurface()",
"node.ProviderAdvertisementSurface()",
"node.MutableDocument<AetheriaEveCommandAcceptanceStatus>(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReadAsync()",
"node.MutableDocument<AetheriaEveCommandAcceptanceStatus>(AetheriaStateNode.EveCommandAcceptanceStatusKey)",
"node.MutableDocument<AetheriaRuntimeSession>(AetheriaStateNode.RuntimeSessionKey(options.DaemonId))",
"node.MutableDocument<AetheriaPlayerSettings>(AetheriaStateNode.PlayerSettingsKey).ReadAsync()",
"node.MutableDocument<EveSurfaceState>(AetheriaStateNode.OperationsSurfaceKey)",
"node.MutableDocument<EveSurfaceState>(AetheriaStateNode.PlayerSettingsSurfaceKey)",
"node.MutableDocument<EveProviderAdvertisementState>(AetheriaStateNode.ProviderAdvertisementSurfaceKey)",
"AetheriaProviderAdvertisementProjector.Build(verseHost, node.StatePath, updatedAtUtc)",
"AetheriaOperationsSurfaceProjector.Build(eveStatus, verseHost, runtimeSession)",
"AetheriaPlayerSettingsSurfaceProjector.Build(playerSettings, playerSettingsUpdatedAt)",
@ -12276,7 +12297,7 @@ static void RequireTypedEveCommandBodies(string root)
"command.TradeValuePolicy.Value",
"command.TradeValuePolicy.TierIndex",
"ExecuteTradeValuePolicyCommandAsync(",
"TradeValuePolicy()",
"MutableDocument<AetheriaTradeValuePolicy>(AetheriaStateNode.TradeValuePolicyKey)",
"SubmitInputSettingsCommandAsync(",
"SubmitLoadoutTemplateCommandAsync(",
"SubmitKnownSurfaceCommandAsync(",
@ -12329,6 +12350,12 @@ static void RequireTypedEveCommandBodies(string root)
"Eve command acceptance report still exposes path vocabulary instead of command-id receipts.");
}
if (typedCommandSources.Contains("TradeValuePolicy()", StringComparison.Ordinal))
{
throw new InvalidOperationException(
"Eve command handling still uses the named trade-value policy helper instead of generic mutable typed document access.");
}
var missingTypedCommandSymbols = requiredTypedCommandSymbols
.Where(symbol => !typedCommandSources.Contains(symbol, StringComparison.Ordinal))
.ToArray();
@ -15232,7 +15259,7 @@ static async Task RequireTradeValuePolicyEveCommandPersistsAsync()
var policy = AetheriaRuntimeStateMapper.ToTradeValuePolicy(
AetheriaRuntimeTradeValueSettings.Default,
DateTimeOffset.UtcNow.ToString("O"));
await commandNode.TradeValuePolicy()
await commandNode.MutableDocument<AetheriaTradeValuePolicy>(AetheriaStateNode.TradeValuePolicyKey)
.ReplaceAsync(policy)
.ConfigureAwait(false);
var originalMinimum = policy.QualityPriceModifier?.Minimum ?? 0;
@ -15255,7 +15282,7 @@ static async Task RequireTradeValuePolicyEveCommandPersistsAsync()
"Trade value policy Eve command was not accepted by the typed command bridge.");
}
var editedPolicy = await commandNode.TradeValuePolicy().ReadAsync().ConfigureAwait(false)
var editedPolicy = await commandNode.MutableDocument<AetheriaTradeValuePolicy>(AetheriaStateNode.TradeValuePolicyKey).ReadAsync().ConfigureAwait(false)
?? throw new InvalidOperationException("Trade value policy disappeared after typed Eve command.");
var actualMinimum = editedPolicy.QualityPriceModifier?.Minimum ?? 0;
if (Math.Abs(actualMinimum - editedMinimum) > 0.000001)

View file

@ -19,7 +19,6 @@ public sealed class AetheriaEveCommandAcceptanceReport
public string LastRejectedCommand { get; set; } = "";
public string LastRejectedReason { get; set; } = "";
}
public static class AetheriaEveCommandBridge
{
public const string CommandSchema = "gamecult.eve.command.v1";
@ -49,17 +48,17 @@ public static class AetheriaEveCommandBridge
{
case AetheriaRuntimeEveCommandKind.CatalogRefresh:
var catalog = await node.RuntimeCatalog().LatestAsync().ConfigureAwait(false);
await node.CatalogSurface()
await node.MutableDocument<EveSurfaceState>(AetheriaStateNode.CatalogSurfaceKey)
.ReplaceAsync(AetheriaCatalogSurfaceProjector.Build(catalog, command.IssuedAtUtc))
.ConfigureAwait(false);
report.AcceptedCatalogRefreshes++;
break;
case AetheriaRuntimeEveCommandKind.OperationsRefresh:
var eveStatus = await node.EveCommandAcceptanceStatus().ReadAsync().ConfigureAwait(false) ??
var eveStatus = await node.MutableDocument<AetheriaEveCommandAcceptanceStatus>(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReadAsync().ConfigureAwait(false) ??
EmptyEveCommandAcceptanceStatus(node.StatePath, command.IssuedAtUtc);
var verseHostSettings = await node.VerseHostSettings().ReadAsync().ConfigureAwait(false);
var runtimeSession = await node.RuntimeSession(eveStatus.RuntimeId).ReadAsync().ConfigureAwait(false);
await node.OperationsSurface()
var verseHostSettings = await node.MutableDocument<AetheriaVerseHostSettings>(AetheriaStateNode.VerseHostSettingsKey).ReadAsync().ConfigureAwait(false);
var runtimeSession = await node.MutableDocument<AetheriaRuntimeSession>(AetheriaStateNode.RuntimeSessionKey(eveStatus.RuntimeId)).ReadAsync().ConfigureAwait(false);
await node.MutableDocument<EveSurfaceState>(AetheriaStateNode.OperationsSurfaceKey)
.ReplaceAsync(AetheriaOperationsSurfaceProjector.Build(
eveStatus,
verseHostSettings,
@ -135,7 +134,7 @@ public static class AetheriaEveCommandBridge
AetheriaStateNode node,
AetheriaRuntimeEveCommandDocument command)
{
var settings = await node.PlayerSettings().ReadAsync().ConfigureAwait(false) ?? new AetheriaPlayerSettings();
var settings = await node.MutableDocument<AetheriaPlayerSettings>(AetheriaStateNode.PlayerSettingsKey).ReadAsync().ConfigureAwait(false) ?? new AetheriaPlayerSettings();
settings.Gameplay ??= new AetheriaPlayerGameplaySettings();
settings.Graphics ??= new AetheriaPlayerGraphicsSettings();
var persistSettings = false;
@ -181,12 +180,12 @@ public static class AetheriaEveCommandBridge
if (persistSettings)
{
settings.LastUpdatedAtUtc = command.IssuedAtUtc;
await node.PlayerSettings()
await node.MutableDocument<AetheriaPlayerSettings>(AetheriaStateNode.PlayerSettingsKey)
.ReplaceAsync(settings)
.ConfigureAwait(false);
}
await node.PlayerSettingsSurface()
await node.MutableDocument<EveSurfaceState>(AetheriaStateNode.PlayerSettingsSurfaceKey)
.ReplaceAsync(AetheriaPlayerSettingsSurfaceProjector.Build(settings, command.IssuedAtUtc))
.ConfigureAwait(false);
}
@ -195,7 +194,7 @@ public static class AetheriaEveCommandBridge
AetheriaStateNode node,
AetheriaRuntimeEveCommandDocument command)
{
var settings = await node.PlayerSettings().ReadAsync().ConfigureAwait(false) ?? new AetheriaPlayerSettings();
var settings = await node.MutableDocument<AetheriaPlayerSettings>(AetheriaStateNode.PlayerSettingsKey).ReadAsync().ConfigureAwait(false) ?? new AetheriaPlayerSettings();
settings.Input ??= new AetheriaPlayerInputSettings();
var persistSettings = false;
@ -246,7 +245,7 @@ public static class AetheriaEveCommandBridge
if (persistSettings)
{
settings.LastUpdatedAtUtc = command.IssuedAtUtc;
await node.PlayerSettings()
await node.MutableDocument<AetheriaPlayerSettings>(AetheriaStateNode.PlayerSettingsKey)
.ReplaceAsync(settings)
.ConfigureAwait(false);
}
@ -256,7 +255,7 @@ public static class AetheriaEveCommandBridge
AetheriaStateNode node,
AetheriaRuntimeEveCommandDocument command)
{
var settings = await node.VerseHostSettings().ReadAsync().ConfigureAwait(false) ?? new AetheriaVerseHostSettings();
var settings = await node.MutableDocument<AetheriaVerseHostSettings>(AetheriaStateNode.VerseHostSettingsKey).ReadAsync().ConfigureAwait(false) ?? new AetheriaVerseHostSettings();
var normalized = AetheriaVerseHostSettingsNormalizer.Normalize(settings);
var persistSettings = false;
@ -271,22 +270,22 @@ public static class AetheriaEveCommandBridge
if (persistSettings)
{
normalized.LastUpdatedAtUtc = command.IssuedAtUtc;
await node.VerseHostSettings()
await node.MutableDocument<AetheriaVerseHostSettings>(AetheriaStateNode.VerseHostSettingsKey)
.ReplaceAsync(normalized)
.ConfigureAwait(false);
}
var eveStatus = await node.EveCommandAcceptanceStatus().ReadAsync().ConfigureAwait(false) ??
var eveStatus = await node.MutableDocument<AetheriaEveCommandAcceptanceStatus>(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReadAsync().ConfigureAwait(false) ??
EmptyEveCommandAcceptanceStatus(node.StatePath, command.IssuedAtUtc);
var runtimeSession = await node.RuntimeSession(eveStatus.RuntimeId).ReadAsync().ConfigureAwait(false);
var runtimeSession = await node.MutableDocument<AetheriaRuntimeSession>(AetheriaStateNode.RuntimeSessionKey(eveStatus.RuntimeId)).ReadAsync().ConfigureAwait(false);
await node.OperationsSurface()
await node.MutableDocument<EveSurfaceState>(AetheriaStateNode.OperationsSurfaceKey)
.ReplaceAsync(AetheriaOperationsSurfaceProjector.Build(
eveStatus,
normalized,
runtimeSession))
.ConfigureAwait(false);
await node.ProviderAdvertisementSurface()
await node.MutableDocument<EveProviderAdvertisementState>(AetheriaStateNode.ProviderAdvertisementSurfaceKey)
.ReplaceAsync(AetheriaProviderAdvertisementProjector.Build(normalized, node.StatePath, command.IssuedAtUtc))
.ConfigureAwait(false);
}
@ -307,7 +306,7 @@ public static class AetheriaEveCommandBridge
AetheriaStateNode node,
AetheriaRuntimeEveCommandDocument command)
{
var tradeValuePolicy = node.TradeValuePolicy();
var tradeValuePolicy = node.MutableDocument<AetheriaTradeValuePolicy>(AetheriaStateNode.TradeValuePolicyKey);
var policy = await tradeValuePolicy.ReadAsync().ConfigureAwait(false) ??
AetheriaRuntimeStateMapper.ToTradeValuePolicy(
AetheriaRuntimeTradeValueSettings.Default,

View file

@ -116,47 +116,6 @@ public sealed class AetheriaStateNode : IAsyncDisposable, IDisposable
routeHint: new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "Aetheria typed catalog state"));
}
public CultMeshMutableStatePointer<EveSurfaceState> CatalogSurface()
{
return MutableDocumentPointer<EveSurfaceState>(new CultRecordKey(AetheriaCatalogSurfaceProjector.SurfaceKey));
}
public CultMeshMutableStatePointer<EveSurfaceState> OperationsSurface()
{
return MutableDocumentPointer<EveSurfaceState>(new CultRecordKey(AetheriaOperationsSurfaceProjector.SurfaceKey));
}
public CultMeshMutableStatePointer<EveSurfaceState> PlayerSettingsSurface()
{
return MutableDocumentPointer<EveSurfaceState>(new CultRecordKey(AetheriaPlayerSettingsSurfaceProjector.SurfaceKey));
}
public CultMeshMutableStatePointer<EveProviderAdvertisementState> ProviderAdvertisementSurface()
{
return MutableDocumentPointer<EveProviderAdvertisementState>(
new CultRecordKey(AetheriaProviderAdvertisementProjector.AdvertisementKey));
}
public CultMeshMutableStatePointer<AetheriaRuntimeSession> RuntimeSession(string runtimeId)
{
return MutableDocumentPointer<AetheriaRuntimeSession>(RuntimeSessionKey(runtimeId));
}
public CultMeshMutableStatePointer<AetheriaPlayerSettings> PlayerSettings()
{
return MutableDocumentPointer<AetheriaPlayerSettings>(PlayerSettingsKey);
}
public CultMeshMutableStatePointer<AetheriaVerseHostSettings> VerseHostSettings()
{
return MutableDocumentPointer<AetheriaVerseHostSettings>(VerseHostSettingsKey);
}
public CultMeshMutableStatePointer<AetheriaEveCommandAcceptanceStatus> EveCommandAcceptanceStatus()
{
return MutableDocumentPointer<AetheriaEveCommandAcceptanceStatus>(EveCommandAcceptanceStatusKey);
}
public CultMeshMutableStatePointer<AetheriaWorldState> World()
{
return MutableDocumentPointer<AetheriaWorldState>(new CultRecordKey("global:aetheria.world_state.v1"));
@ -204,12 +163,6 @@ public sealed class AetheriaStateNode : IAsyncDisposable, IDisposable
return NameFile(AetheriaCatalogKeys.NameFileFromLegacyId(legacyId));
}
public CultMeshMutableStatePointer<AetheriaTradeValuePolicy> TradeValuePolicy()
{
return MutableDocumentPointer<AetheriaTradeValuePolicy>(
new CultRecordKey(AetheriaTradeValuePolicy.RecordKey));
}
public CultMeshMutableStatePointer<AetheriaLoadoutTemplate> LoadoutTemplate(CultRecordKey key)
{
return MutableDocumentPointer<AetheriaLoadoutTemplate>(key);
@ -334,20 +287,35 @@ public sealed class AetheriaStateNode : IAsyncDisposable, IDisposable
return string.IsNullOrWhiteSpace(token) ? "empty" : token;
}
private static CultRecordKey RuntimeSessionKey(string runtimeId)
public static CultRecordKey RuntimeSessionKey(string runtimeId)
{
return new CultRecordKey($"runtime:{runtimeId}:aetheria.runtime_session.v1");
}
private static CultRecordKey PlayerSettingsKey { get; } =
public static CultRecordKey CatalogSurfaceKey { get; } =
new(AetheriaCatalogSurfaceProjector.SurfaceKey);
public static CultRecordKey OperationsSurfaceKey { get; } =
new(AetheriaOperationsSurfaceProjector.SurfaceKey);
public static CultRecordKey PlayerSettingsSurfaceKey { get; } =
new(AetheriaPlayerSettingsSurfaceProjector.SurfaceKey);
public static CultRecordKey ProviderAdvertisementSurfaceKey { get; } =
new(AetheriaProviderAdvertisementProjector.AdvertisementKey);
public static CultRecordKey PlayerSettingsKey { get; } =
new("global:aetheria.player_settings.v1");
private static CultRecordKey VerseHostSettingsKey { get; } =
public static CultRecordKey VerseHostSettingsKey { get; } =
new("global:aetheria.verse_host_settings.v1");
private static CultRecordKey EveCommandAcceptanceStatusKey { get; } =
public static CultRecordKey EveCommandAcceptanceStatusKey { get; } =
new("global:aetheria.eve_command_acceptance_status.v1");
public static CultRecordKey TradeValuePolicyKey { get; } =
new(AetheriaTradeValuePolicy.RecordKey);
private CultMeshMutableStatePointer<T> MutableDocumentPointer<T>(CultRecordKey key) where T : class
{
return CultMesh.MutableStatePointer(