From 42500ea45cc3c2257ec95df580229a158e32af27 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 13 Jun 2026 15:59:26 +0100 Subject: [PATCH] Advertise Aetheria Eve provider surfaces --- .voidbot/state/aetheria.cc | Bin 36913 -> 37229 bytes Aetheria.State.Smoke/Program.cs | 10 ++ Aetheria.State/AetheriaDocumentRegistry.cs | 6 +- .../AetheriaProviderAdvertisementProjector.cs | 81 ++++++++++++ Aetheria.State/AetheriaStateNode.cs | 12 ++ .../EveProviderAdvertisementState.cs | 118 ++++++++++++++++++ Aetheria.State/README.md | 4 +- Economy.Server/Program.cs | 2 + docs/aetheria-perfect-machine-map.md | 7 ++ 9 files changed, 237 insertions(+), 3 deletions(-) create mode 100644 Aetheria.State/AetheriaProviderAdvertisementProjector.cs create mode 100644 Aetheria.State/Documents/EveProviderAdvertisementState.cs diff --git a/.voidbot/state/aetheria.cc b/.voidbot/state/aetheria.cc index d81b74d05a2e20831c55c08b16ae4000fea4d6e9..d48e63357492729044ad49fc0c28b07cac79dbd6 100644 GIT binary patch delta 1241 zcmah}&1(}u6t{`m_Mlj#6${08&?>gBP14d-Pd$kAqQyjzQq;-L+hpwS&bl+3vKM@E;KLA1J=vO=85aQ`na`zxRG0GaL6aUms-FpJe)r z$ENaDex|r^r)SW-$=pf{g^8)8AhTC`la9iK`JSE3T+25PZmsRRxIBxxhM{2K%LW8d z#TBZC3K-~>06CnfBLtzW3yvW$UdK=ig&z7E92vrCF8rf$4;RXo^zRBt2ZUB&GtFFC zHM!EBRb@T4T_(JbJv*p(z6CeijaJf3BM2{W6L1Ap=Aj+A+QK?o?F}ycvVm34B})(i zU~L-hRtw5;Ii4}mS=lF01U`x-7e=9QbISU0U z%Y4~Cyec#>N0TT#WWonU`$r0*fD;d8J<;eZA^i`xe_`1r6D|uyGSSgg=okRUp%Q60 z>kGX?h{AQjW%d%e;U`9%orAKJ?h?&5BcuK2b&Y8V(I;7<`2;Lr(A zXPOgAubUAOR2mtNW5GD3r3w{Pfm}j}PSRM;RIMUe$d&bs`F2f)XV6ifx13vh7^3sC z*%-YyK0RH+kbLZpXLojDM9L?~0KyJqcu=gJdCPqWSc4aoa%pU`)>}JkqR6lFyr~ zYeUVx#%6Z(ReQ-J9h`W{7m^G-(z)u*?N@g8_qAcnyX*YQ2F}nRv|yxdW5!{ d;*B6ZQpgw0`i;A-k3z0FfAf8RtG_U{@B_zu%0mDE delta 958 zcmZvb&ubGw6vs{4Y6~g|ErL*ewDPex8DimoN4?Q{AeaQ~powv-)CJhP! z(FQyz`wvJ_3!aqy0~D`byegi(iS**xx5=(n>0#O7J2P*-@AKaKv0HriqWE^NICkaw zLZ!S|ncI9`n24V}IU0W|?&RWfU!EDV>`IX}f8GJ3AxQRA?T%BA8jSgwoEmCkjy6 zLAd|fM0H;p4-M4GdLSB>fuV3b7|;^dVi4N$o<(J3m(-Wn8{T1TQkbEpmUL0iP+M`1 z2f5ZG+lSXd3qG|`^F9}&rnnBXwN=<4^0#E>QKqKZQc63$fZHJ%WM-FxAVnfoi=2LG zppG3%OUAq#jLd`7uFsW>3s1+VduzCDp|(0pp@BB(EifKMK1T>b(iTWV{Qdl;J84kO zUBbB^##Q*FnuarDplm}}7MeNf@*ANXkMue=%(o-=lHP^g(%IX2>;1tyz&R*!{B!#B zM8^6b%qF>C9VsNDR-Bw#xhsr8_Xe1Nw$kLel}%QazhRDZgJ~lTm&d{6y{?R$8jQs9 z!sbgWnrvw;8QmTqPHsIsIX0T_#h-`HWW#jrYBpgX2Ex+ZLN;ATW4jaP;!_nV;YM4FQi surface.SurfaceId == AetheriaCatalogSurfaceProjector.SurfaceId) || + !advertisement.Surfaces.Any(surface => surface.SurfaceId == AetheriaOperationsSurfaceProjector.SurfaceId)) + { + throw new InvalidOperationException("Aetheria Eve provider advertisement did not survive flush/reopen."); + } + if (playerSettings?.ActiveRunKey != runKey.ToString() || playerSettings.PlayerName != "Smoke Pilot" || !playerSettings.TutorialPassed || diff --git a/Aetheria.State/AetheriaDocumentRegistry.cs b/Aetheria.State/AetheriaDocumentRegistry.cs index c4fda9c2f..cb4095e23 100644 --- a/Aetheria.State/AetheriaDocumentRegistry.cs +++ b/Aetheria.State/AetheriaDocumentRegistry.cs @@ -38,7 +38,8 @@ public static class AetheriaDocumentRegistry CultNetDocumentBinding.ForDocument(registry), CultNetDocumentBinding.ForDocument(registry), CultNetDocumentBinding.ForDocument(registry), - CultNetDocumentBinding.ForDocument(registry) + CultNetDocumentBinding.ForDocument(registry), + CultNetDocumentBinding.ForDocument(registry) }); } @@ -58,6 +59,7 @@ public static class AetheriaDocumentRegistry typeof(AetheriaZoneState), typeof(AetheriaEntitySnapshot), typeof(AetheriaRuntimeCommitDrainStatus), - typeof(EveSurfaceState) + typeof(EveSurfaceState), + typeof(EveProviderAdvertisementState) ]; } diff --git a/Aetheria.State/AetheriaProviderAdvertisementProjector.cs b/Aetheria.State/AetheriaProviderAdvertisementProjector.cs new file mode 100644 index 000000000..66f60ce10 --- /dev/null +++ b/Aetheria.State/AetheriaProviderAdvertisementProjector.cs @@ -0,0 +1,81 @@ +using Aetheria.State.Documents; + +namespace Aetheria.State; + +public static class AetheriaProviderAdvertisementProjector +{ + public const string AdvertisementKey = "eve:provider:aetheria"; + public const string ProviderId = "aetheria"; + + public static EveProviderAdvertisementState Build(string statePath, string updatedAtUtc) + { + return new EveProviderAdvertisementState + { + ProviderId = ProviderId, + ServiceId = "aetheria.runtime", + VerseId = "aetheria.local", + RootVerse = "asgard", + CanonicalService = "asgard.aetheria", + LocatedService = "asgard.local.aetheria", + CultMeshAddress = "asgard.local.aetheria/eve", + Title = "Aetheria", + Kind = "game.runtime", + UpdatedAtUtc = updatedAtUtc, + Freshness = new EveProviderFreshness + { + State = "fresh", + LastSeenAtUtc = updatedAtUtc, + MaxAgeMs = 15000 + }, + Schemas = + [ + "aetheria.world_state.v1", + "aetheria.item_definition.v1", + "aetheria.corporation.v2", + "aetheria.name_file.v2", + "aetheria.player_settings.v1", + "aetheria.loadout_template.v1", + "aetheria.run_state.v1", + "aetheria.zone_state.v1", + "aetheria.entity_snapshot.v1", + "aetheria.runtime_commit_drain_status.v1", + "gamecult.eve.surface.v1" + ], + Witnesses = + [ + new EveProviderWitness + { + Kind = "cultcache", + Ref = statePath, + Summary = "Aetheria typed CultCache state file" + } + ], + Surfaces = + [ + new EveProviderSurfaceRef + { + SurfaceId = AetheriaCatalogSurfaceProjector.SurfaceId, + Key = AetheriaCatalogSurfaceProjector.SurfaceKey + }, + new EveProviderSurfaceRef + { + SurfaceId = AetheriaOperationsSurfaceProjector.SurfaceId, + Key = AetheriaOperationsSurfaceProjector.SurfaceKey + } + ], + Commands = + [ + new EveProviderCommandRef + { + Command = "aetheria.catalog.refresh", + Summary = "Refresh catalog projection" + }, + new EveProviderCommandRef + { + Command = "aetheria.operations.refresh", + Summary = "Refresh operations projection" + } + ] + }; + } +} diff --git a/Aetheria.State/AetheriaStateNode.cs b/Aetheria.State/AetheriaStateNode.cs index 1944b168b..a1e3f07a8 100644 --- a/Aetheria.State/AetheriaStateNode.cs +++ b/Aetheria.State/AetheriaStateNode.cs @@ -183,6 +183,18 @@ public sealed class AetheriaStateNode : IAsyncDisposable, IDisposable return Database.GetAsync(new CultRecordKey(AetheriaOperationsSurfaceProjector.SurfaceKey)); } + public Task> PutProviderAdvertisementAsync( + EveProviderAdvertisementState advertisement) + { + return Database.PutAsync(new CultRecordKey(AetheriaProviderAdvertisementProjector.AdvertisementKey), advertisement); + } + + public Task GetProviderAdvertisementAsync() + { + return Database.GetAsync( + new CultRecordKey(AetheriaProviderAdvertisementProjector.AdvertisementKey)); + } + public Task> PutPlayerSettingsAsync(AetheriaPlayerSettings settings) { return Database.PutAsync(new CultRecordKey("global:aetheria.player_settings.v1"), settings); diff --git a/Aetheria.State/Documents/EveProviderAdvertisementState.cs b/Aetheria.State/Documents/EveProviderAdvertisementState.cs new file mode 100644 index 000000000..f42cd1d1f --- /dev/null +++ b/Aetheria.State/Documents/EveProviderAdvertisementState.cs @@ -0,0 +1,118 @@ +using GameCult.Caching; +using MessagePack; + +namespace Aetheria.State.Documents; + +[CultDocument("gamecult.eve.provider_advertisement", "gamecult.eve.provider_advertisement.v1")] +[CultGlobal] +[MessagePackObject] +public sealed class EveProviderAdvertisementState +{ + [Key(0)] + [CultIndex("schema")] + public string Schema { get; set; } = "gamecult.eve.provider_advertisement.v1"; + + [Key(1)] + [CultName] + public string ProviderId { get; set; } = ""; + + [Key(2)] + public string ServiceId { get; set; } = ""; + + [Key(3)] + public string VerseId { get; set; } = ""; + + [Key(4)] + public string RootVerse { get; set; } = "asgard"; + + [Key(5)] + public string CanonicalService { get; set; } = ""; + + [Key(6)] + public string LocatedService { get; set; } = ""; + + [Key(7)] + public string CultMeshAddress { get; set; } = ""; + + [Key(8)] + public string Title { get; set; } = ""; + + [Key(9)] + public string Kind { get; set; } = "game.runtime"; + + [Key(10)] + public string UpdatedAtUtc { get; set; } = ""; + + [Key(11)] + public EveProviderFreshness Freshness { get; set; } = new(); + + [Key(12)] + public string[] Schemas { get; set; } = []; + + [Key(13)] + public EveProviderWitness[] Witnesses { get; set; } = []; + + [Key(14)] + public EveProviderSurfaceRef[] Surfaces { get; set; } = []; + + [Key(15)] + public EveProviderCommandRef[] Commands { get; set; } = []; +} + +[MessagePackObject] +public sealed class EveProviderFreshness +{ + [Key(0)] + public string State { get; set; } = "fresh"; + + [Key(1)] + public string LastSeenAtUtc { get; set; } = ""; + + [Key(2)] + public int MaxAgeMs { get; set; } = 15000; +} + +[MessagePackObject] +public sealed class EveProviderWitness +{ + [Key(0)] + public string Kind { get; set; } = ""; + + [Key(1)] + public string Ref { get; set; } = ""; + + [Key(2)] + public string Summary { get; set; } = ""; +} + +[MessagePackObject] +public sealed class EveProviderSurfaceRef +{ + [Key(0)] + public string Schema { get; set; } = "gamecult.eve.surface.v1"; + + [Key(1)] + public string SurfaceId { get; set; } = ""; + + [Key(2)] + public string Key { get; set; } = ""; + + [Key(3)] + public string Transport { get; set; } = "cultmesh"; + + [Key(4)] + public string Status { get; set; } = "available"; +} + +[MessagePackObject] +public sealed class EveProviderCommandRef +{ + [Key(0)] + public string Command { get; set; } = ""; + + [Key(1)] + public string Transport { get; set; } = "cultmesh"; + + [Key(2)] + public string Summary { get; set; } = ""; +} diff --git a/Aetheria.State/README.md b/Aetheria.State/README.md index 250fc2b33..75c542128 100644 --- a/Aetheria.State/README.md +++ b/Aetheria.State/README.md @@ -140,7 +140,9 @@ dotnet run --project .\Economy.Server\Economy.Server.csproj -- --apply-pending-o The server publishes `AetheriaRuntimeCommitDrainStatus` and the `aetheria.operations` Eve surface after each drain attempt, so pending depth, applied counts, failures, and timestamps are typed state rather than console-only -status. +status. It also publishes `gamecult.eve.provider_advertisement.v1` for the +`aetheria` provider, advertising the catalog and operations surfaces plus the +typed schemas witnessed by the local `.cc` state file. `Aetheria.State.ApplyPending` is the smaller local operator applicator for queued Unity runtime commits when the server host is not being used: diff --git a/Economy.Server/Program.cs b/Economy.Server/Program.cs index 3f9a51bcd..5802e05f7 100644 --- a/Economy.Server/Program.cs +++ b/Economy.Server/Program.cs @@ -130,6 +130,8 @@ internal static class Program { await node.PutRuntimeCommitDrainStatusAsync(status).ConfigureAwait(false); await node.PutOperationsSurfaceAsync(AetheriaOperationsSurfaceProjector.Build(status)).ConfigureAwait(false); + await node.PutProviderAdvertisementAsync( + AetheriaProviderAdvertisementProjector.Build(node.StatePath, status.LastPollAtUtc)).ConfigureAwait(false); await node.FlushAsync().ConfigureAwait(false); } diff --git a/docs/aetheria-perfect-machine-map.md b/docs/aetheria-perfect-machine-map.md index 7544db418..518497ba1 100644 --- a/docs/aetheria-perfect-machine-map.md +++ b/docs/aetheria-perfect-machine-map.md @@ -178,6 +178,11 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted. - `AetheriaRuntimeCommitDrainStatus` and the `aetheria.operations` Eve surface publish pending-drain health, pending depth, applied counts, failures, and timestamps as typed state. Console logs are notification-only. +- `AetheriaProviderAdvertisementProjector` publishes + `gamecult.eve.provider_advertisement.v1` for the `aetheria` provider, + advertising the catalog and operations surfaces, command boundaries, schemas, + and `.cc` witness path. This is the discovery map for Odin/Eve, not a health + page. - `AetheriaCatalogSurfaceProjector` now emits the first provider-owned Eve surface from typed catalog state. The importer materializes a `gamecult.eve.surface.v1` catalog operator document at @@ -542,6 +547,8 @@ First Aetheria surfaces to publish: and daemon polling, with `--apply-pending-once` for bounded operation. - Done: publish pending-drain health as typed CultCache state and an Eve operations surface. + - Done: publish an Eve provider advertisement so Odin/Eve can discover + Aetheria surfaces and command boundaries through typed state. - Done: stop legacy catalog pull/read paths from writing entries back to their source backing store. - Done: delete legacy catalog backing-store write/realtime APIs.