diff --git a/Aetheria.State.Daemon/Program.cs b/Aetheria.State.Daemon/Program.cs index dd3607fb2..b54be1e4a 100644 --- a/Aetheria.State.Daemon/Program.cs +++ b/Aetheria.State.Daemon/Program.cs @@ -50,7 +50,6 @@ if (options.Once) await rtsPump.ConfigureAwait(false); return; } - var stopped = new TaskCompletionSource(); 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(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(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReadAsync().ConfigureAwait(false); var report = await AetheriaEveCommandBridge.AcceptObservedAsync( node, existingStatus?.AccountedCommandIds) .ConfigureAwait(false); - await node.EveCommandAcceptanceStatus() + await node.MutableDocument(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(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReadAsync().ConfigureAwait(false); + await node.MutableDocument(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(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReadAsync().ConfigureAwait(false); + var runtimeSession = await node.MutableDocument(AetheriaStateNode.RuntimeSessionKey(options.DaemonId)).ReadAsync().ConfigureAwait(false); + var playerSettings = await node.MutableDocument(AetheriaStateNode.PlayerSettingsKey).ReadAsync().ConfigureAwait(false) ?? new AetheriaPlayerSettings(); var playerSettingsUpdatedAt = string.IsNullOrWhiteSpace(playerSettings.LastUpdatedAtUtc) ? updatedAtUtc : playerSettings.LastUpdatedAtUtc; - await node.OperationsSurface() + await node.MutableDocument(AetheriaStateNode.OperationsSurfaceKey) .ReplaceAsync(AetheriaOperationsSurfaceProjector.Build(eveStatus, verseHost, runtimeSession)) .ConfigureAwait(false); - await node.PlayerSettingsSurface() + await node.MutableDocument(AetheriaStateNode.PlayerSettingsSurfaceKey) .ReplaceAsync(AetheriaPlayerSettingsSurfaceProjector.Build(playerSettings, playerSettingsUpdatedAt)) .ConfigureAwait(false); - await node.ProviderAdvertisementSurface() + await node.MutableDocument(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(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(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(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(AetheriaStateNode.PlayerSettingsKey) .ReplaceAsync(settings) .ConfigureAwait(false); @@ -1322,7 +1321,7 @@ static async Task EnsureVerseAuthorityPolicyAsync( static async Task ReadRuntimeRunCheckpointAsync(AetheriaStateNode node) { - var settings = await node.PlayerSettings().ReadAsync().ConfigureAwait(false); + var settings = await node.MutableDocument(AetheriaStateNode.PlayerSettingsKey).ReadAsync().ConfigureAwait(false); if (string.IsNullOrWhiteSpace(settings?.ActiveRunKey)) return null; @@ -1749,7 +1748,7 @@ static async Task EnsureVerseHostSettingsAsync( AetheriaDaemonHostOptions options, string now) { - var existing = await node.VerseHostSettings().ReadAsync().ConfigureAwait(false); + var existing = await node.MutableDocument(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 EnsureVerseHostSettingsAsync( !AetheriaVerseHostSettingsNormalizer.Equivalent(existing, normalized)) { normalized.LastUpdatedAtUtc = now; - await node.VerseHostSettings() + await node.MutableDocument(AetheriaStateNode.VerseHostSettingsKey) .ReplaceAsync(normalized) .ConfigureAwait(false); } diff --git a/Aetheria.State.Smoke/Program.cs b/Aetheria.State.Smoke/Program.cs index f0eb808ab..cd6b4cee9 100644 --- a/Aetheria.State.Smoke/Program.cs +++ b/Aetheria.State.Smoke/Program.cs @@ -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(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(AetheriaStateNode.VerseHostSettingsKey).ReplaceAsync(verseHostSettings); + await node.MutableDocument(AetheriaStateNode.OperationsSurfaceKey) .ReplaceAsync(AetheriaOperationsSurfaceProjector.Build(verseHostSettings: verseHostSettings)); - await node.ProviderAdvertisementSurface() + await node.MutableDocument(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(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface) .ReplaceAsync(AetheriaRuntimeEveSurfaceStateProjector.ToState(daemonGameSurface)); - await node.RuntimeSession("smoke-runtime").ReplaceAsync(new AetheriaRuntimeSession + await node.MutableDocument(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(AetheriaStateNode.OperationsSurfaceKey).ReplaceAsync(AetheriaOperationsSurfaceProjector.Build( verseHostSettings: verseHostSettings, - runtimeSession: await node.RuntimeSession("smoke-runtime").ReadAsync())); + runtimeSession: await node.MutableDocument(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(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 = ["/1", "/leftButton"] } }); - await node.PlayerSettingsSurface() + await node.MutableDocument(AetheriaStateNode.PlayerSettingsSurfaceKey) .ReplaceAsync(AetheriaPlayerSettingsSurfaceProjector.Build( - await node.PlayerSettings().ReadAsync(), + await node.MutableDocument(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(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReplaceAsync(eveCommandStatus); + await node.MutableDocument(AetheriaStateNode.OperationsSurfaceKey).ReplaceAsync(AetheriaOperationsSurfaceProjector.Build( eveCommandStatus, - verseHostSettings: await node.VerseHostSettings().ReadAsync(), - runtimeSession: await node.RuntimeSession("smoke-runtime").ReadAsync())); + verseHostSettings: await node.MutableDocument(AetheriaStateNode.VerseHostSettingsKey).ReadAsync(), + runtimeSession: await node.MutableDocument(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(AetheriaStateNode.CatalogSurfaceKey).ReadAsync(); + var eveCommandStatus = await reopened.MutableDocument(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReadAsync(); + var operationsSurface = await reopened.MutableDocument(AetheriaStateNode.OperationsSurfaceKey).ReadAsync(); + var playerSettingsSurface = await reopened.MutableDocument(AetheriaStateNode.PlayerSettingsSurfaceKey).ReadAsync(); + var advertisement = await reopened.MutableDocument(AetheriaStateNode.ProviderAdvertisementSurfaceKey).ReadAsync(); var daemonProvider = await reopened.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonProviderAdvertisement) .ReadAsync(); var daemonHealth = await reopened.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonHealth) @@ -639,8 +638,8 @@ await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aether .ReadAsync(); var daemonGameSurface = await reopened.MutableDocument(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface) .ReadAsync(); - var runtimeSession = await reopened.RuntimeSession("smoke-runtime").ReadAsync(); - var playerSettings = await reopened.PlayerSettings().ReadAsync(); + var runtimeSession = await reopened.MutableDocument(AetheriaStateNode.RuntimeSessionKey("smoke-runtime")).ReadAsync(); + var playerSettings = await reopened.MutableDocument(AetheriaStateNode.PlayerSettingsKey).ReadAsync(); var loadout = await reopened.LoadoutTemplate(loadoutKey).ReadAsync(); var runState = await reopened.RunState(runKey).ReadAsync(); var zoneState = await reopened.ZoneState(zoneKey).ReadAsync(); diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index c302934f5..9c979d5bb 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -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(AetheriaStateNode.CatalogSurfaceKey).ReadAsync() ?? throw new InvalidOperationException("Missing Aetheria catalog Eve surface document."); var items = node.Cache.GetAll().ToArray(); @@ -118,7 +118,7 @@ var corporations = node.Cache.GetAll().ToArray(); var nameFiles = node.Cache.GetAll().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(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(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(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 VerseHostSettings()", StringComparison.Ordinal) || - !node.Contains("public CultMeshMutableStatePointer PlayerSettings()", StringComparison.Ordinal) || - !node.Contains("public CultMeshMutableStatePointer EveCommandAcceptanceStatus()", StringComparison.Ordinal) || + if (!node.Contains("public CultMeshMutableStatePointer MutableDocument(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 VerseHostSettings()", StringComparison.Ordinal) || + node.Contains("public CultMeshMutableStatePointer PlayerSettings()", StringComparison.Ordinal) || + node.Contains("public CultMeshMutableStatePointer EveCommandAcceptanceStatus()", StringComparison.Ordinal) || + node.Contains("public CultMeshMutableStatePointer CatalogSurface()", StringComparison.Ordinal) || + node.Contains("public CultMeshMutableStatePointer OperationsSurface()", StringComparison.Ordinal) || + node.Contains("public CultMeshMutableStatePointer PlayerSettingsSurface()", StringComparison.Ordinal) || + node.Contains("public CultMeshMutableStatePointer ProviderAdvertisementSurface()", StringComparison.Ordinal) || + node.Contains("public CultMeshMutableStatePointer RuntimeSession(", StringComparison.Ordinal) || + node.Contains("public CultMeshMutableStatePointer 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(AetheriaStateNode.VerseHostSettingsKey).ReadAsync()", + "node.MutableDocument(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(AetheriaStateNode.VerseHostSettingsKey)", ".ReplaceAsync(normalized)", - "node.ProviderAdvertisementSurface()", + "node.MutableDocument(AetheriaStateNode.ProviderAdvertisementSurfaceKey)", "AetheriaOperationsSurfaceProjector.Build(", "switch (command.Kind)" }; @@ -10944,13 +10965,13 @@ static void RequireDaemonVersePublication(string root) "ReadObservedDaemonCommands()", "currentFrame?.AccountedCommandIds ?? Array.Empty()", "AetheriaRuntimeDaemonTickRunner.Tick(", - "node.EveCommandAcceptanceStatus().ReadAsync()", - "node.EveCommandAcceptanceStatus()", - "node.RuntimeSession(options.DaemonId)", - "node.PlayerSettings().ReadAsync()", - "node.OperationsSurface()", - "node.PlayerSettingsSurface()", - "node.ProviderAdvertisementSurface()", + "node.MutableDocument(AetheriaStateNode.EveCommandAcceptanceStatusKey).ReadAsync()", + "node.MutableDocument(AetheriaStateNode.EveCommandAcceptanceStatusKey)", + "node.MutableDocument(AetheriaStateNode.RuntimeSessionKey(options.DaemonId))", + "node.MutableDocument(AetheriaStateNode.PlayerSettingsKey).ReadAsync()", + "node.MutableDocument(AetheriaStateNode.OperationsSurfaceKey)", + "node.MutableDocument(AetheriaStateNode.PlayerSettingsSurfaceKey)", + "node.MutableDocument(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(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(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(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) diff --git a/Aetheria.State/AetheriaEveCommandBridge.cs b/Aetheria.State/AetheriaEveCommandBridge.cs index 9c4dadd62..12121da5e 100644 --- a/Aetheria.State/AetheriaEveCommandBridge.cs +++ b/Aetheria.State/AetheriaEveCommandBridge.cs @@ -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(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(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(AetheriaStateNode.VerseHostSettingsKey).ReadAsync().ConfigureAwait(false); + var runtimeSession = await node.MutableDocument(AetheriaStateNode.RuntimeSessionKey(eveStatus.RuntimeId)).ReadAsync().ConfigureAwait(false); + await node.MutableDocument(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(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(AetheriaStateNode.PlayerSettingsKey) .ReplaceAsync(settings) .ConfigureAwait(false); } - await node.PlayerSettingsSurface() + await node.MutableDocument(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(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(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(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(AetheriaStateNode.VerseHostSettingsKey) .ReplaceAsync(normalized) .ConfigureAwait(false); } - var eveStatus = await node.EveCommandAcceptanceStatus().ReadAsync().ConfigureAwait(false) ?? + var eveStatus = await node.MutableDocument(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(AetheriaStateNode.RuntimeSessionKey(eveStatus.RuntimeId)).ReadAsync().ConfigureAwait(false); - await node.OperationsSurface() + await node.MutableDocument(AetheriaStateNode.OperationsSurfaceKey) .ReplaceAsync(AetheriaOperationsSurfaceProjector.Build( eveStatus, normalized, runtimeSession)) .ConfigureAwait(false); - await node.ProviderAdvertisementSurface() + await node.MutableDocument(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(AetheriaStateNode.TradeValuePolicyKey); var policy = await tradeValuePolicy.ReadAsync().ConfigureAwait(false) ?? AetheriaRuntimeStateMapper.ToTradeValuePolicy( AetheriaRuntimeTradeValueSettings.Default, diff --git a/Aetheria.State/AetheriaStateNode.cs b/Aetheria.State/AetheriaStateNode.cs index ac03ee4af..8344077ad 100644 --- a/Aetheria.State/AetheriaStateNode.cs +++ b/Aetheria.State/AetheriaStateNode.cs @@ -116,47 +116,6 @@ public sealed class AetheriaStateNode : IAsyncDisposable, IDisposable routeHint: new CultMeshRouteHint(CultMeshLocalityKind.SharedMemory, "Aetheria typed catalog state")); } - public CultMeshMutableStatePointer CatalogSurface() - { - return MutableDocumentPointer(new CultRecordKey(AetheriaCatalogSurfaceProjector.SurfaceKey)); - } - - public CultMeshMutableStatePointer OperationsSurface() - { - return MutableDocumentPointer(new CultRecordKey(AetheriaOperationsSurfaceProjector.SurfaceKey)); - } - - public CultMeshMutableStatePointer PlayerSettingsSurface() - { - return MutableDocumentPointer(new CultRecordKey(AetheriaPlayerSettingsSurfaceProjector.SurfaceKey)); - } - - public CultMeshMutableStatePointer ProviderAdvertisementSurface() - { - return MutableDocumentPointer( - new CultRecordKey(AetheriaProviderAdvertisementProjector.AdvertisementKey)); - } - - public CultMeshMutableStatePointer RuntimeSession(string runtimeId) - { - return MutableDocumentPointer(RuntimeSessionKey(runtimeId)); - } - - public CultMeshMutableStatePointer PlayerSettings() - { - return MutableDocumentPointer(PlayerSettingsKey); - } - - public CultMeshMutableStatePointer VerseHostSettings() - { - return MutableDocumentPointer(VerseHostSettingsKey); - } - - public CultMeshMutableStatePointer EveCommandAcceptanceStatus() - { - return MutableDocumentPointer(EveCommandAcceptanceStatusKey); - } - public CultMeshMutableStatePointer World() { return MutableDocumentPointer(new CultRecordKey("global:aetheria.world_state.v1")); @@ -204,12 +163,6 @@ public sealed class AetheriaStateNode : IAsyncDisposable, IDisposable return NameFile(AetheriaCatalogKeys.NameFileFromLegacyId(legacyId)); } - public CultMeshMutableStatePointer TradeValuePolicy() - { - return MutableDocumentPointer( - new CultRecordKey(AetheriaTradeValuePolicy.RecordKey)); - } - public CultMeshMutableStatePointer LoadoutTemplate(CultRecordKey key) { return MutableDocumentPointer(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 MutableDocumentPointer(CultRecordKey key) where T : class { return CultMesh.MutableStatePointer(