Publish VoidBot daemon-owned provider witness

This commit is contained in:
Meta Vens 2026-06-16 20:50:49 +01:00
commit 8b4e043164
3 changed files with 194 additions and 45 deletions

View file

@ -21,6 +21,12 @@ const eveBindingDocumentType = "gamecult.eve.interface_binding";
const eveBindingSchemaId = "gamecult.eve.interface_binding.v1";
const surfaceDocumentType = "gamecult.eve.surface_state";
const surfaceSchemaId = "gamecult.eve.surface_state.v1";
const providerCatalogDocumentType = "voidbot.provider_advertisement_catalog";
const providerCatalogSchemaId = "voidbot.provider_advertisement_catalog.v0";
const transportProfileDocumentType = "idunn.daemon_transport_profile";
const transportProfileSchemaId = "idunn.daemon_transport_profile.v1";
const commandBoundaryDocumentType = "idunn.command_boundary";
const commandBoundarySchemaId = "idunn.command_boundary.v1";
const args = parseArgs(process.argv.slice(2));
@ -716,7 +722,7 @@ async function writeCultMeshPublication(snapshot, eveState, storePath, allowStor
schemaId: providerAdvertisementSchemaId,
schemaVersion: providerAdvertisementSchemaId,
contentHash: providerAdvertisementSchemaId,
global: true,
global: false,
name: "providerId",
schema: { parse: parseObjectDocument("Eve provider advertisement") },
});
@ -736,24 +742,79 @@ async function writeCultMeshPublication(snapshot, eveState, storePath, allowStor
schemaId: eveBindingSchemaId,
schemaVersion: eveBindingSchemaId,
contentHash: eveBindingSchemaId,
global: true,
global: false,
name: "bindingId",
schema: { parse: parseObjectDocument("Eve interface binding") },
});
const node = await CultMesh.createNode(storePath, {
documents: [snapshotDefinition, providerDefinition, surfaceDefinition, eveBindingDefinition],
const providerCatalogDefinition = defineDocumentType({
type: providerCatalogDocumentType,
schemaName: providerCatalogDocumentType,
schemaId: providerCatalogSchemaId,
schemaVersion: providerCatalogSchemaId,
contentHash: providerCatalogSchemaId,
global: true,
schema: { parse: parseObjectDocument("VoidBot provider advertisement catalog") },
});
const providerAdvertisement = buildProviderAdvertisement(snapshot);
const transportProfileDefinition = defineDocumentType({
type: transportProfileDocumentType,
schemaName: transportProfileDocumentType,
schemaId: transportProfileSchemaId,
schemaVersion: transportProfileSchemaId,
contentHash: transportProfileSchemaId,
global: false,
name: (value) => value?.profile_id || value?.daemon_id || "voidbot",
schema: { parse: parseObjectDocument("Idunn daemon transport profile") },
});
const commandBoundaryDefinition = defineDocumentType({
type: commandBoundaryDocumentType,
schemaName: commandBoundaryDocumentType,
schemaId: commandBoundarySchemaId,
schemaVersion: commandBoundarySchemaId,
contentHash: commandBoundarySchemaId,
global: false,
name: (value) => value?.boundary_id || value?.daemon_id || "voidbot",
schema: { parse: parseObjectDocument("Idunn command boundary") },
});
const node = await CultMesh.createNode(storePath, {
documents: [
snapshotDefinition,
providerDefinition,
surfaceDefinition,
eveBindingDefinition,
providerCatalogDefinition,
transportProfileDefinition,
commandBoundaryDefinition,
],
});
const providerCatalog = loadProviderAdvertisementCatalog(snapshot);
const providerAdvertisements = buildLiveProviderAdvertisements(providerCatalog, snapshot);
const providerAdvertisement = providerAdvertisements.find((item) => item.providerId === providerId)
?? buildProviderAdvertisement(snapshot);
const eveBinding = buildEveInterfaceBinding(snapshot, eveState);
const commandBoundary = buildCommandBoundary(snapshot);
const transportProfile = buildTransportProfile(snapshot);
await node.put(snapshotDefinition, "voidbot-swarm", snapshot);
await node.put(providerDefinition, providerId, providerAdvertisement);
await node.put(providerCatalogDefinition, "voidbot.providers", providerCatalog);
for (const advertisement of providerAdvertisements) {
await node.put(providerDefinition, advertisement.providerId, advertisement);
}
await node.put(surfaceDefinition, providerId, eveState);
await node.put(eveBindingDefinition, providerId, eveBinding);
await node.put(commandBoundaryDefinition, commandBoundary.boundary_id, commandBoundary);
await node.put(transportProfileDefinition, transportProfile.profile_id, transportProfile);
await node.flush?.(true);
return {
writeStatus: "ok",
storePath,
documents: [snapshotDocumentType, providerAdvertisementDocumentType, surfaceDocumentType, eveBindingDocumentType],
documents: [
snapshotDocumentType,
providerCatalogDocumentType,
providerAdvertisementDocumentType,
surfaceDocumentType,
eveBindingDocumentType,
commandBoundaryDocumentType,
transportProfileDocumentType,
],
providerId,
verseId,
eveBinding: {
@ -845,6 +906,39 @@ function parseObjectDocument(label) {
};
}
function loadProviderAdvertisementCatalog(snapshot) {
const fixturePath = resolve(repoRoot, "docs", "architecture", "voidbot-provider-advertisements.fixture.json");
const fixture = JSON.parse(readFileSync(fixturePath, "utf8"));
fixture.fixtureKind = "daemon-live-publication";
fixture.generatedAt = snapshot.generatedAt;
fixture.cultMeshKeys.liveSwarmStore = cultMeshStorePath;
return fixture;
}
function buildLiveProviderAdvertisements(catalog, snapshot) {
const providers = Array.isArray(catalog.providers) ? catalog.providers : [];
return providers.map((provider) => {
if (provider.providerId === providerId) {
return {
...provider,
status: snapshot.summary?.state ?? provider.status ?? "unknown",
updatedAt: snapshot.generatedAt,
witnesses: Array.isArray(provider.witnesses)
? provider.witnesses.map((witness) =>
witness.id === "voidbot.swarm_state_snapshot"
? { ...witness, path: cultMeshStorePath }
: witness)
: provider.witnesses,
};
}
return {
...provider,
updatedAt: snapshot.generatedAt,
};
});
}
function buildProviderAdvertisement(snapshot) {
const endpoints = [
primaryCultMeshEndpoint(),
@ -884,6 +978,39 @@ function buildProviderAdvertisement(snapshot) {
};
}
function buildCommandBoundary(snapshot) {
return {
boundary_id: "command-boundary:voidbot",
daemon_id: "voidbot",
owner: "VoidBot local stack",
restart_authority: "E:\\Projects\\Odin\\scripts\\restart-voidbot.cmd",
deploy_authority: "none",
health_authority: "voidbot.cultnet-rudp-stack-health",
alarm_authority: "bifrost.operator-notification",
compatibility_commands: [
"E:\\Projects\\Odin\\scripts\\health-voidbot.cmd",
"E:\\Projects\\Odin\\scripts\\restart-voidbot.cmd",
],
forbidden_authority: "Health commands, operations watchdog probes, static HTML lowerings, and Odin renderers do not own VoidBot provider state or restart truth.",
observed_at: snapshot.generatedAt,
};
}
function buildTransportProfile(snapshot) {
return {
profile_id: "transport:voidbot",
daemon_id: "voidbot",
target_transport: "cultnet.transport.rudp.v0",
current_transport: "daemon-published-rudp-health + daemon-owned-cultmesh-provider-store + compatibility.local-command fallback",
state: "partial-rudp-health-and-provider-store-live",
health_contract: "voidbot.cultnet-rudp-stack-health",
publication_schema: "gamecult.eve.provider_advertisement.v1 + idunn.command_boundary.v1 + idunn.daemon_transport_profile.v1",
compatibility_mechanism: "E:\\Projects\\Odin\\scripts\\health-voidbot.cmd + operations-health.json watchdog probe",
cut_line: `VoidBot now publishes provider advertisements, command boundary, and transport profile from ${cultMeshStorePath}; the remaining compatibility path is the local operations probe, which becomes fallback evidence only.`,
observed_at: snapshot.generatedAt,
};
}
function buildEveInterfaceBinding(snapshot, eveState) {
return {
schemaVersion: eveBindingSchemaId,

View file

@ -134,6 +134,7 @@ function Publish-IdunnRudpHealth {
Add-Content -LiteralPath (Join-Path $logDir "idunn-rudp-health.log") -Encoding UTF8 -Value $message
}
}
function Get-OrganState {
param($State, [string] $Id)
$property = $State.organs.PSObject.Properties[$Id]
@ -148,6 +149,19 @@ function Set-OrganState {
$State.organs | Add-Member -NotePropertyName $Id -NotePropertyValue $Value -Force
}
function Set-SkippedOrganState {
param($State, $Organ, [datetime] $Now, [string] $Status, $LogPath = $null)
Set-OrganState -State $State -Id $Organ.Id -Value ([pscustomobject]@{
label = $Organ.Label
intervalMinutes = $Organ.IntervalMinutes
lastStartedAt = $Now.ToString("o")
lastFinishedAt = $Now.ToString("o")
lastExitCode = 0
lastStatus = $Status
lastLogPath = $LogPath
})
}
function Test-Due {
param($State, [string] $Id, [int] $IntervalMinutes, [datetime] $Now)
if ($Force) {
@ -251,6 +265,25 @@ function Test-OrchestratorLock {
return $false
}
$startedAt = [datetime]::Parse($lock.startedAt).ToUniversalTime()
$lockPid = if ($lock.PSObject.Properties["pid"]) { [int]$lock.pid } else { 0 }
$lockAgeMinutes = (([DateTime]::UtcNow) - $startedAt).TotalMinutes
if ($lockPid -gt 0) {
$lockProcess = Get-CimInstance Win32_Process -Filter "ProcessId = $lockPid" -ErrorAction SilentlyContinue
if ($null -eq $lockProcess) {
Remove-Item -LiteralPath $lockPath -Force -ErrorAction SilentlyContinue
return $false
}
if ($lockAgeMinutes -gt 2) {
$children = @(Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object {
[int]$_.ParentProcessId -eq $lockPid -and $_.Name -ne "conhost.exe"
})
if ($children.Count -eq 0) {
Stop-Process -Id $lockPid -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath $lockPath -Force -ErrorAction SilentlyContinue
return $false
}
}
}
if ((([DateTime]::UtcNow) - $startedAt).TotalMinutes -gt 45) {
Remove-Item -LiteralPath $lockPath -Force -ErrorAction SilentlyContinue
return $false
@ -302,6 +335,7 @@ try {
"bifrost-dispatch" = $true
"repo-face-heartbeats" = $true
"void-mood-drift" = $true
"void-moderation-heartbeat" = $true
"void-moderation-rumination" = $true
}
$onlySet = @{}
@ -323,6 +357,15 @@ try {
Executable = $node
Arguments = @((Join-Path $bifrostRoot "tools\dispatch-agent-requests.mjs"), "dispatch", "--repo", "*", "--max", "1")
},
[pscustomobject]@{
Id = "voidbot-swarm-surface"
Label = "VoidBot swarm surface"
IntervalMinutes = 1
TimeoutMinutes = 5
Cwd = $repoRoot
Executable = $node
Arguments = @((Join-Path $PSScriptRoot "render-voidbot-swarm-dashboard.mjs"))
},
[pscustomobject]@{
Id = "repo-face-heartbeats"
Label = "Repo Face CTB turns"
@ -345,7 +388,7 @@ try {
Id = "void-moderation-heartbeat"
Label = "Void rules moderation heartbeat"
IntervalMinutes = Get-ConfigInt -Config $config -Name "VOIDBOT_MODERATION_HEARTBEAT_INTERVAL_MINUTES" -Default 1 -Minimum 1
TimeoutMinutes = 10
TimeoutMinutes = Get-ConfigInt -Config $config -Name "VOIDBOT_MODERATION_HEARTBEAT_TIMEOUT_MINUTES" -Default 3 -Minimum 1
Cwd = $repoRoot
Executable = $powershell
Arguments = @("-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", (Join-Path $PSScriptRoot "run-void-moderator-rumination.ps1"), "-ModerationHeartbeatOnly")
@ -376,15 +419,22 @@ try {
continue
}
if ($organ.Id -eq "bifrost-dispatch" -and -not (Get-ConfigBool -Config $config -Name "BIFROST_DISPATCH_ENABLED" -Default $false)) {
Set-OrganState -State $state -Id $organ.Id -Value ([pscustomobject]@{
label = $organ.Label
intervalMinutes = $organ.IntervalMinutes
lastStartedAt = $now.ToString("o")
lastFinishedAt = $now.ToString("o")
lastExitCode = 0
lastStatus = "skipped_disabled"
lastLogPath = $null
})
Set-SkippedOrganState -State $state -Organ $organ -Now $now -Status "skipped_disabled"
Write-JsonFile -Path $statePath -Data $state
continue
}
if ($organ.Id -eq "void-moderation-heartbeat" -and -not (Get-ConfigBool -Config $config -Name "VOIDBOT_MODERATION_HEARTBEAT_ENABLED" -Default $true)) {
Set-SkippedOrganState -State $state -Organ $organ -Now $now -Status "skipped_disabled"
Write-JsonFile -Path $statePath -Data $state
continue
}
if ($organ.Id -eq "void-moderation-rumination" -and -not (Get-ConfigBool -Config $config -Name "VOIDBOT_MODERATION_RUMINATION_ENABLED" -Default $true)) {
Set-SkippedOrganState -State $state -Organ $organ -Now $now -Status "skipped_disabled"
Write-JsonFile -Path $statePath -Data $state
continue
}
if ($organ.Id -eq "voidbot-operations-watchdog" -and -not (Get-ConfigBool -Config $config -Name "VOIDBOT_OPERATIONS_WATCHDOG_ENABLED" -Default $true)) {
Set-SkippedOrganState -State $state -Organ $organ -Now $now -Status "skipped_disabled"
Write-JsonFile -Path $statePath -Data $state
continue
}
@ -393,15 +443,7 @@ try {
}
if ($agentSwarmPaused -and $agentSwarmOrganIds.ContainsKey($organ.Id)) {
Set-OrganState -State $state -Id $organ.Id -Value ([pscustomobject]@{
label = $organ.Label
intervalMinutes = $organ.IntervalMinutes
lastStartedAt = $now.ToString("o")
lastFinishedAt = $now.ToString("o")
lastExitCode = 0
lastStatus = "skipped_agent_swarm_paused"
lastLogPath = $agentSwarmPausePath
})
Set-SkippedOrganState -State $state -Organ $organ -Now $now -Status "skipped_agent_swarm_paused" -LogPath $agentSwarmPausePath
Write-JsonFile -Path $statePath -Data $state
continue
}

View file

@ -375,7 +375,6 @@ async function main(): Promise<void> {
state.lastTickAt = now.toISOString();
if (!dryRun) {
await writeHeartbeatState(config.repoFaceHeartbeats.statePath, state);
publishSwarmDashboardSurface();
}
process.stdout.write(
`${JSON.stringify({
@ -5535,25 +5534,6 @@ async function writeHeartbeatState(path: string, state: FaceHeartbeatState): Pro
await writeFile(path, `${JSON.stringify(state, null, 2)}\n`, "utf8");
}
function publishSwarmDashboardSurface(): void {
const scriptPath = resolve(process.cwd(), "scripts", "render-voidbot-swarm-dashboard.mjs");
const result = spawnSync(process.execPath, [scriptPath], {
cwd: process.cwd(),
encoding: "utf8",
windowsHide: true,
});
if (result.status === 0) {
return;
}
const stderr = result.stderr?.trim();
const stdout = result.stdout?.trim();
const detail = [stderr, stdout].filter(Boolean).join("\n");
console.error(
`VoidBot swarm CultMesh surface publish failed with exit code ${result.status ?? "unknown"}${detail ? `:\n${detail}` : "."}`,
);
}
function migrateLegacyHeartbeatState(
parsed: Partial<FaceHeartbeatState> & {
baseIntervalMinutes?: number;