Add Idunn keepalive organ
This commit is contained in:
parent
1a4b67c7a8
commit
27ec51ec9a
6 changed files with 246 additions and 0 deletions
16
README.md
16
README.md
|
|
@ -35,6 +35,22 @@ Local package surfaces:
|
|||
- Avatar asset: `assets/personas/gjallar-avatar.png`
|
||||
- Pixel avatar: `assets/personas/gjallar-avatar-pixel-256.png`
|
||||
|
||||
## Idunn
|
||||
|
||||
Idunn is Odin's keepalive organ: the daemon package that should keep the known
|
||||
swarm alive after Odin has accepted where each daemon lives and what authority
|
||||
path may touch it. Individual daemons publish health and command boundaries;
|
||||
Idunn brings them up after reboots or crashes, watches health, and escalates
|
||||
operator-needed failures through VoidBot's CultMesh owner-DM command. Odin sees
|
||||
the daemons; Idunn keeps the apples from rotting.
|
||||
|
||||
Local package surfaces:
|
||||
|
||||
- Organ contract: `docs/idunn.md`
|
||||
- C# CultMesh organ: `src/Idunn/Idunn.csproj`
|
||||
- Runtime store: `scratch/idunn/idunn.keepalive.cc`
|
||||
- Operator escalation: `voidbot.operator-dm` command `owner.dm.send`
|
||||
|
||||
## Authority Map
|
||||
|
||||
- Owner: Odin owns the network-wide Verse registry, schema catalog index, translation map, and the accepted `odin.allseer` Eve surface.
|
||||
|
|
|
|||
|
|
@ -78,6 +78,18 @@ Odin's coordinator. Gjallar may read Odin-owned state and emit affordance
|
|||
packets; it must not own the underlying registry, probe, provider, layout, or
|
||||
translation decisions.
|
||||
|
||||
Idunn is the named keepalive organ for daemon continuity. Its current package
|
||||
lives in `docs/idunn.md` and `src/Idunn/Idunn.csproj`. Idunn may read
|
||||
Odin-owned service records and provider advertisements, then bring daemons up
|
||||
after reboots or crashes, watch health, emit keepalive observations, restart
|
||||
requests, denied-action records, and operator alarms. When human action is
|
||||
needed, Idunn should use CultMesh to request VoidBot's provider-owned
|
||||
`voidbot.operator-dm` command `owner.dm.send` with payload schema
|
||||
`gamecult.operator_dm_request.v1`. It must not own Verse discovery, schema
|
||||
truth, provider dashboards, identity grants, Discord delivery, or renderer
|
||||
layout. Keepalive loops should move into Idunn instead of accumulating inside
|
||||
Odin's coordinator or individual daemons.
|
||||
|
||||
## Target Mechanism
|
||||
|
||||
```text
|
||||
|
|
|
|||
102
docs/idunn.md
Normal file
102
docs/idunn.md
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
# Idunn
|
||||
|
||||
Idunn is Odin's keepalive organ.
|
||||
|
||||
Odin is the all-seer: it accepts Verse discovery, schema catalogs, translation
|
||||
routes, provider surfaces, and observation projections. Idunn keeps the daemon
|
||||
swarm alive from that accepted map. It is not a second Odin, not a dashboard,
|
||||
and not a heroic supervisor with secret service truth in its pockets.
|
||||
|
||||
Idunn keeps the apples: desired daemon presence, boot recovery, crash recovery,
|
||||
health freshness, restart intent, operator escalation, and continuity witness
|
||||
state.
|
||||
|
||||
## Authority Map
|
||||
|
||||
- Owner: Idunn owns daemon lifecycle management after Odin has accepted where a
|
||||
daemon lives: bring-up after host reboot, crash recovery, health watching,
|
||||
restart policy, and operator escalation.
|
||||
- Inputs: Odin's accepted service records, provider advertisements, `.cc`
|
||||
witnesses, advertised command boundaries, freshness windows, operator policy,
|
||||
local service manager state, and direct local service probes only when no
|
||||
provider advertisement exists.
|
||||
- Outputs: typed keepalive observations, restart requests, denied-action
|
||||
records, operator alarms, owner-DM escalation requests through VoidBot, and an
|
||||
Eve/CultUI keepalive surface.
|
||||
- Derived state: dashboard cells, Discord speech, agent summaries, and Odin
|
||||
service projections are notification-only views of Idunn-owned keepalive
|
||||
records.
|
||||
- Forbidden writers: Idunn does not decide which Verses exist, invent provider
|
||||
schemas, mutate provider dashboards, own identity/session grants, or hide
|
||||
restarts behind Odin refresh logic. Individual daemons should not carry
|
||||
independent crash-recovery loops once Idunn owns their lifecycle path; they
|
||||
publish health, surfaces, state witnesses, and command boundaries instead.
|
||||
- Shared paths: manual operator restart, scheduled restart, degraded-health
|
||||
repair, boot rehydration, and future remote worker recovery must pass through
|
||||
the same keepalive command primitive.
|
||||
- Deletion line: any keepalive loop inside Odin, Gjallar, Eve lowerers, or
|
||||
renderer code should be cut or demoted to a probe that names Idunn as the
|
||||
restart owner.
|
||||
|
||||
## Body
|
||||
|
||||
The initial local package is deliberately small:
|
||||
|
||||
- `src/Idunn/Idunn.csproj` is the first C# CultMesh organ.
|
||||
- `src/Idunn/Program.cs` opens or creates Idunn's local CultCache/CultMesh
|
||||
store at `scratch/idunn/idunn.keepalive.cc`.
|
||||
- `npm run idunn:build` builds the organ beside `gjallar:build`.
|
||||
- `npm run idunn:start -- --serve` starts the resident keepalive process.
|
||||
|
||||
This is the organ contract and runtime foothold. It does not yet restart
|
||||
anything. The first real keepalive cut should add typed records before adding
|
||||
actuators:
|
||||
|
||||
```text
|
||||
idunn.desired_daemon.v1
|
||||
idunn.daemon_health.v1
|
||||
idunn.keepalive_decision.v1
|
||||
idunn.restart_request.v1
|
||||
idunn.restart_result.v1
|
||||
idunn.operator_alarm.v1
|
||||
idunn.owner_dm_escalation.v1
|
||||
```
|
||||
|
||||
## Invariants
|
||||
|
||||
- Odin remains the accepted owner of Verse and service discovery.
|
||||
- Idunn owns continuity decisions after a daemon is known.
|
||||
- Individual daemons own their work and their health publication, not their
|
||||
surrounding lifecycle. They should be simple to kill and simple for Idunn to
|
||||
bring back.
|
||||
- Providers own their own command boundaries. Idunn requests restart through
|
||||
advertised authority or a named local service manager adapter.
|
||||
- Host reboot recovery, crash recovery, stale-health recovery, and manual
|
||||
operator restart must share the same Idunn command primitive.
|
||||
- A repair loop is not an owner. If a daemon becomes healthy only after a later
|
||||
Odin refresh or manual click, Idunn's ownership path is still incomplete.
|
||||
- Restart attempts must be witnessed: requested by whom, against which service,
|
||||
through which command boundary, with what result and timestamp.
|
||||
- Idunn should fail closed when authority is unclear. Unknown ownership,
|
||||
repeated restart failure, missing command authority, or degraded health that
|
||||
needs a human becomes an operator alarm.
|
||||
- Operator alarms use CultMesh to request VoidBot owner notification through
|
||||
the provider-owned `voidbot.operator-dm` command `owner.dm.send` with payload
|
||||
schema `gamecult.operator_dm_request.v1`. Idunn must not learn Discord token
|
||||
handling or DM delivery internals.
|
||||
|
||||
## First Runtime Direction
|
||||
|
||||
Idunn's C# runtime should grow in this order:
|
||||
|
||||
1. Read Odin-owned service and provider-advertisement records through CultMesh.
|
||||
2. Normalize them into `idunn.desired_daemon.v1` records.
|
||||
3. Observe freshness and process state through narrow probe ports.
|
||||
4. Emit `idunn.keepalive_decision.v1` records without acting.
|
||||
5. Add restart adapters only for named authority boundaries: local systemd,
|
||||
Windows service control, Docker, or provider-advertised commands.
|
||||
6. Add `voidbot.operator-dm` escalation for cases that need operator action.
|
||||
7. Publish an Eve/CultUI keepalive surface from Idunn-owned records.
|
||||
|
||||
No ad hoc JSON manifest should become the live state owner. Debug projections
|
||||
are fine when they name the `.cc` record or CultMesh document behind them.
|
||||
|
|
@ -8,6 +8,8 @@
|
|||
"gjallar:persona": "node scripts/write-gjallar-persona-cc.mjs inspect",
|
||||
"gjallar:persona:write": "node scripts/write-gjallar-persona-cc.mjs write",
|
||||
"gjallar:build": "dotnet build src/Gjallar/Gjallar.csproj",
|
||||
"idunn:build": "dotnet build src/Idunn/Idunn.csproj",
|
||||
"idunn:start": "dotnet run --project src/Idunn/Idunn.csproj --",
|
||||
"rust:test": "cargo test --workspace",
|
||||
"check": "node scripts/check-syntax.cjs",
|
||||
"start": "node src/odin-coordinator.cjs"
|
||||
|
|
|
|||
12
src/Idunn/Idunn.csproj
Normal file
12
src/Idunn/Idunn.csproj
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\CultLib\src\GameCult.Mesh\GameCult.Mesh.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
102
src/Idunn/Program.cs
Normal file
102
src/Idunn/Program.cs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
using GameCult.Caching.MessagePack;
|
||||
using GameCult.Mesh;
|
||||
|
||||
var options = IdunnOptions.Parse(args);
|
||||
var cachePath = Path.GetFullPath(options.CachePath);
|
||||
var cacheDirectory = Path.GetDirectoryName(cachePath);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(cacheDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(cacheDirectory);
|
||||
}
|
||||
|
||||
using var node = await CultMesh.CreateNodeAsync(
|
||||
cachePath,
|
||||
new CultMeshNodeOptions
|
||||
{
|
||||
StartServer = options.Serve,
|
||||
CacheOptions = new CultCacheOpenOptions
|
||||
{
|
||||
PullOnOpen = false
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
Console.WriteLine("Idunn CultMesh organ initialized.");
|
||||
Console.WriteLine($"Keepalive store: {cachePath}");
|
||||
Console.WriteLine("Provider id: idunn.keepalive");
|
||||
Console.WriteLine("Authority: daemon lifecycle, health watching, restart intent, and operator escalation for Odin-known daemons");
|
||||
Console.WriteLine("Owner escalation: CultMesh command voidbot.operator-dm owner.dm.send");
|
||||
Console.WriteLine($"CultMesh server: {(options.Serve ? "started" : "disabled")}");
|
||||
Console.WriteLine("Typed keepalive records and restart adapters are the next cut.");
|
||||
|
||||
if (!options.Serve)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var stopping = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
Console.CancelKeyPress += (_, eventArgs) =>
|
||||
{
|
||||
eventArgs.Cancel = true;
|
||||
stopping.TrySetResult();
|
||||
};
|
||||
|
||||
Console.WriteLine("Press Ctrl+C to stop Idunn.");
|
||||
await stopping.Task.ConfigureAwait(false);
|
||||
return 0;
|
||||
|
||||
internal sealed record IdunnOptions(string CachePath, bool Serve)
|
||||
{
|
||||
public static IdunnOptions Parse(string[] args)
|
||||
{
|
||||
var repoRoot = FindRepoRoot(Directory.GetCurrentDirectory());
|
||||
var cachePath = Path.Combine(repoRoot, "scratch", "idunn", "idunn.keepalive.cc");
|
||||
var serve = false;
|
||||
|
||||
for (var index = 0; index < args.Length; index++)
|
||||
{
|
||||
switch (args[index])
|
||||
{
|
||||
case "--serve":
|
||||
serve = true;
|
||||
break;
|
||||
case "--cache" when index + 1 < args.Length:
|
||||
cachePath = args[++index];
|
||||
break;
|
||||
case "--help":
|
||||
case "-h":
|
||||
PrintUsage();
|
||||
Environment.Exit(0);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Unknown Idunn argument: {args[index]}");
|
||||
}
|
||||
}
|
||||
|
||||
return new IdunnOptions(cachePath, serve);
|
||||
}
|
||||
|
||||
private static string FindRepoRoot(string start)
|
||||
{
|
||||
var current = new DirectoryInfo(start);
|
||||
while (current != null)
|
||||
{
|
||||
if (File.Exists(Path.Combine(current.FullName, "package.json")) &&
|
||||
Directory.Exists(Path.Combine(current.FullName, "src", "Gjallar")))
|
||||
{
|
||||
return current.FullName;
|
||||
}
|
||||
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
private static void PrintUsage()
|
||||
{
|
||||
Console.WriteLine("Idunn");
|
||||
Console.WriteLine(" --cache <path> Keepalive CultCache store path.");
|
||||
Console.WriteLine(" --serve Start the CultMesh server instead of only opening the node.");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue