Advertise Aetheria Eve provider surfaces
This commit is contained in:
parent
1f88375a7e
commit
42500ea45c
9 changed files with 237 additions and 3 deletions
Binary file not shown.
|
|
@ -103,6 +103,7 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s
|
|||
};
|
||||
await node.PutRuntimeCommitDrainStatusAsync(drainStatus);
|
||||
await node.PutOperationsSurfaceAsync(AetheriaOperationsSurfaceProjector.Build(drainStatus));
|
||||
await node.PutProviderAdvertisementAsync(AetheriaProviderAdvertisementProjector.Build(statePath, now));
|
||||
|
||||
await node.PutLoadoutTemplateAsync(loadoutKey, new AetheriaLoadoutTemplate
|
||||
{
|
||||
|
|
@ -256,6 +257,7 @@ await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aether
|
|||
var catalogSurface = await reopened.GetCatalogSurfaceAsync();
|
||||
var drainStatus = await reopened.GetRuntimeCommitDrainStatusAsync();
|
||||
var operationsSurface = await reopened.GetOperationsSurfaceAsync();
|
||||
var advertisement = await reopened.GetProviderAdvertisementAsync();
|
||||
var playerSettings = await reopened.GetPlayerSettingsAsync();
|
||||
var loadout = await reopened.GetLoadoutTemplateAsync(loadoutKey);
|
||||
var runState = await reopened.GetRunStateAsync(runKey);
|
||||
|
|
@ -300,6 +302,14 @@ await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aether
|
|||
throw new InvalidOperationException("Runtime commit drain status or operations surface did not survive flush/reopen.");
|
||||
}
|
||||
|
||||
if (advertisement?.ProviderId != AetheriaProviderAdvertisementProjector.ProviderId ||
|
||||
advertisement.Surfaces.Length < 2 ||
|
||||
!advertisement.Surfaces.Any(surface => 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 ||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,8 @@ public static class AetheriaDocumentRegistry
|
|||
CultNetDocumentBinding.ForDocument<AetheriaZoneState>(registry),
|
||||
CultNetDocumentBinding.ForDocument<AetheriaEntitySnapshot>(registry),
|
||||
CultNetDocumentBinding.ForDocument<AetheriaRuntimeCommitDrainStatus>(registry),
|
||||
CultNetDocumentBinding.ForDocument<EveSurfaceState>(registry)
|
||||
CultNetDocumentBinding.ForDocument<EveSurfaceState>(registry),
|
||||
CultNetDocumentBinding.ForDocument<EveProviderAdvertisementState>(registry)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -58,6 +59,7 @@ public static class AetheriaDocumentRegistry
|
|||
typeof(AetheriaZoneState),
|
||||
typeof(AetheriaEntitySnapshot),
|
||||
typeof(AetheriaRuntimeCommitDrainStatus),
|
||||
typeof(EveSurfaceState)
|
||||
typeof(EveSurfaceState),
|
||||
typeof(EveProviderAdvertisementState)
|
||||
];
|
||||
}
|
||||
|
|
|
|||
81
Aetheria.State/AetheriaProviderAdvertisementProjector.cs
Normal file
81
Aetheria.State/AetheriaProviderAdvertisementProjector.cs
Normal file
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -183,6 +183,18 @@ public sealed class AetheriaStateNode : IAsyncDisposable, IDisposable
|
|||
return Database.GetAsync<EveSurfaceState>(new CultRecordKey(AetheriaOperationsSurfaceProjector.SurfaceKey));
|
||||
}
|
||||
|
||||
public Task<CultRecordHandle<EveProviderAdvertisementState>> PutProviderAdvertisementAsync(
|
||||
EveProviderAdvertisementState advertisement)
|
||||
{
|
||||
return Database.PutAsync(new CultRecordKey(AetheriaProviderAdvertisementProjector.AdvertisementKey), advertisement);
|
||||
}
|
||||
|
||||
public Task<EveProviderAdvertisementState?> GetProviderAdvertisementAsync()
|
||||
{
|
||||
return Database.GetAsync<EveProviderAdvertisementState>(
|
||||
new CultRecordKey(AetheriaProviderAdvertisementProjector.AdvertisementKey));
|
||||
}
|
||||
|
||||
public Task<CultRecordHandle<AetheriaPlayerSettings>> PutPlayerSettingsAsync(AetheriaPlayerSettings settings)
|
||||
{
|
||||
return Database.PutAsync(new CultRecordKey("global:aetheria.player_settings.v1"), settings);
|
||||
|
|
|
|||
118
Aetheria.State/Documents/EveProviderAdvertisementState.cs
Normal file
118
Aetheria.State/Documents/EveProviderAdvertisementState.cs
Normal file
|
|
@ -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; } = "";
|
||||
}
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue