VoidBot/scripts/export-voidbot-provider-advertisements.mjs
2026-06-05 19:06:08 +01:00

358 lines
17 KiB
JavaScript

#!/usr/bin/env node
import { readFile, writeFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const defaultOutPath = resolve(repoRoot, "docs", "architecture", "voidbot-provider-advertisements.fixture.json");
const providerAdvertisementDocumentType = "gamecult.eve.provider_advertisement";
const providerAdvertisementSchemaId = "gamecult.eve.provider_advertisement.v1";
const interfaceBindingDocumentType = "gamecult.eve.interface_binding";
const interfaceBindingSchemaId = "gamecult.eve.interface_binding.v1";
const surpersonaStateDocumentType = "gamecult.eve.surface_state";
const surpersonaStateSchemaId = "gamecult.eve.surface_state.v1";
const fixtureDocumentType = "voidbot.provider_advertisement_catalog";
const fixtureSchemaId = "voidbot.provider_advertisement_catalog.v0";
const verseId = "voidbot.local";
const rootVerse = "asgard";
const currentMachine = "starfire";
const canonicalService = `${rootVerse}.voidbot`;
const locatedService = `${rootVerse}.${currentMachine}.voidbot`;
const args = parseArgs(process.argv.slice(2));
const outPath = resolve(repoRoot, args.out ?? defaultOutPath);
const catalog = buildCatalog();
validateCatalog(catalog);
const serialized = `${JSON.stringify(catalog, null, 2)}\n`;
if (args.check) {
const existing = await readFile(outPath, "utf8");
if (existing !== serialized) {
throw new Error(`Provider advertisement fixture is stale: ${outPath}`);
}
console.log(`Provider advertisement fixture is current: ${outPath}`);
} else {
await writeFile(outPath, serialized, "utf8");
console.log(`Provider advertisement fixture written: ${outPath}`);
}
function buildCatalog() {
const providers = [
provider({
id: "voidbot.discord",
title: "VoidBot Discord",
description: "Discord ingress, room obligations, direct mentions, speech receipts, moderation pressure, and delivery targets.",
status: "planned-witness",
surfaces: [
surface("voidbot.discord.room_obligations", "Room obligations", "Open direct asks, moderation cases, recent speech receipts, and channel delivery targets."),
surface("voidbot.discord.mention_queue", "Mention queue", "Role/name/bot mentions that become CTB obligations instead of direct provider jobs."),
],
witnesses: [
witness("voidbot.private_self_state", ".voidbot/private/void-self-state.cc", "VoidBot typed operations", ["void.moderation_cursor", "void.speech_receipts", "void.candidate_interventions"]),
witness("voidbot.discord_archive", ".voidbot/rag/messages.json", "VoidBot raw archive", ["archived Discord messages", "forensic bot-directed prompts"]),
witness("voidbot.moderation_status", ".voidbot/status/moderation-rumination.json", "VoidBot rumination runner", ["debug status only"]),
witness("voidbot.pending_mentions", ".voidbot/status/void-moderation-pending-mentions.json", "VoidBot CTB scheduler", ["debug status only"]),
],
commands: [
command("discord.inspect_room_obligations", "read-only", "Load current open room obligations and speech receipts through the provider surface."),
command("discord.inspect_delivery_targets", "read-only", "Load configured parent-owned delivery targets without sending speech."),
],
owners: {
state: "VoidBot typed self-state service",
inspection: "VoidBot for Discord-specific obligations; Huginn for Persona/.cc generic inspection",
presentation: "Eve/CultUI lowerings",
},
}),
provider({
id: "voidbot.archive",
title: "VoidBot Archive",
description: "Archived Discord corpus status, import/backfill health, bot-directed-prompt exclusion, and history retrieval caveats.",
status: "planned-witness",
surfaces: [
surface("voidbot.archive.import_health", "Archive import health", "Discord archive freshness, backfill status, and ingestion warnings."),
surface("voidbot.archive.retrieval_health", "History retrieval health", "Vector freshness and archive caveats for agent retrieval."),
],
witnesses: [
witness("voidbot.discord_archive", ".voidbot/rag/messages.json", "VoidBot raw archive", ["raw archive", "forensics"]),
witness("voidbot.history_vectors", "qdrant://voidbot/history", "Qdrant", ["semantic history vectors"]),
witness("voidbot.operations_health", ".voidbot/status/operations-health.json", "VoidBot watchdog", ["debug status only"]),
],
commands: [
command("archive.search_history", "read-only", "Search archived Discord history through native CultMesh/CultNet service commands; MCP is external-agent compatibility."),
command("archive.get_message_context", "read-only", "Fetch a bounded context window around an archived Discord message."),
],
owners: {
state: "VoidBot archive and retrieval stack",
inspection: "VoidBot native CultMesh/CultNet retrieval surface; MCP compatibility bridge for external agents",
presentation: "Eve/CultUI lowerings",
},
}),
provider({
id: "voidbot.source",
title: "VoidBot Source",
description: "Indexed repo/lore coverage, repo shard status, vector collection health, and source reindex jobs.",
status: "planned-witness",
surfaces: [
surface("voidbot.source.coverage", "Source coverage", "Indexed repository and lore coverage with shard freshness."),
surface("voidbot.source.reindex_jobs", "Source reindex jobs", "Detached indexing job status and hook reconciliation pressure."),
],
witnesses: [
witness("voidbot.source_archive_manifest", ".voidbot/rag/source-documents.json", "VoidBot source archive", ["manifest"]),
witness("voidbot.source_archive_shards", ".voidbot/rag/source-documents.repos/", "VoidBot source archive", ["per-repo source shards"]),
witness("voidbot.source_vectors", "qdrant://voidbot/source", "Qdrant", ["semantic source vectors"]),
witness("voidbot.source_hooks_status", ".voidbot/status/source-hooks/", "VoidBot source hook scripts", ["debug status only"]),
],
commands: [
command("source.search_sources", "read-only", "Search indexed source and lore repositories through native CultMesh/CultNet service commands."),
command("source.get_source_context", "read-only", "Fetch a bounded source chunk window from an indexed document."),
command("source.list_indexed_repos", "read-only", "List indexed source/lore repositories visible to VoidBot."),
],
owners: {
state: "VoidBot RAG/source archive stack",
inspection: "VoidBot native CultMesh/CultNet retrieval surface; MCP compatibility bridge for external agents",
presentation: "Eve/CultUI lowerings",
},
}),
provider({
id: "voidbot.repo_persona",
title: "VoidBot Repo Persona Compatibility",
description: "Registered Persona address book, repo-local state witnesses, channel grants, prompt assembly status, Bifrost digest availability, and Huginn inspection readiness.",
status: "planned-handoff",
surfaces: [
surface("voidbot.repo_persona.address_book", "Repo Persona address book", "Role-backed Discord identity registry and webhook persona compatibility data."),
surface("voidbot.repo_persona.state_witnesses", "Repo Persona state witnesses", "Repo-local .cc state paths and Huginn handoff readiness."),
surface("voidbot.repo_persona.prompt_assembly", "Prompt assembly", "Read-only status for state projection, channel grants, Bifrost digest reads, and current-room transcript attachment."),
],
witnesses: [
witness("voidbot.repo_discord_identities", "REPO_DISCORD_IDENTITIES_PATH", "VoidBot compatibility registry", ["Discord role ids", "webhook persona address book", "channel grants"]),
witness("voidbot.repo_persona_state", ".voidbot/private/repo-personas/<identity>.cc or repo-local Persona state path", "VoidBot compatibility carrier", ["legacy Persona .cc state witness"]),
witness("gamecult.persona_state.v0", "E:/Projects/EpiphanyAgent/schemas/cultnet/gamecult.persona_state.v0.schema.json", "Epiphany Persona schema", ["portable Persona state schema"]),
],
commands: [
command("repo_persona.list_identities", "read-only", "List registered repo Persona Discord identities and compatibility metadata."),
command("repo_persona.inspect_prompt_packet", "read-only", "Render an exact Persona prompt packet for diagnostics without queueing a turn."),
command("repo_persona.handoff_to_huginn", "handoff", "Ask Huginn to inspect, validate, migrate, or publish Persona/.cc state; VoidBot does not become the Persona steward."),
],
owners: {
state: "VoidBot compatibility registry for Discord addressing; repo-local owners for Persona state",
inspection: "Huginn for Persona/.cc stewardship",
presentation: "Eve/CultUI lowerings",
},
demotions: [
"VoidBot repo Persona MCP state reads are diagnostics for external agents, not canonical Persona inspection or native swarm access.",
"VoidBot registry data is a Discord compatibility projection, not portable Persona authority.",
"Persona publication and .cc inspection readiness belong to Huginn once the handoff path exists.",
],
}),
provider({
id: "voidbot.swarm",
title: "VoidBot Swarm",
description: "CTB initiative order, active turns, pause/heat/cadence controls, orchestrator organ health, and selected Persona state witness.",
status: "live",
surfaces: [
surface("voidbot.swarm.ctb_order", "CTB order", "Initiative order, active turns, recovery timing, pending mentions, and selected Persona witness."),
surface("voidbot.swarm.controls", "Swarm controls", "Pause, cadence, and manual turn request controls through the Eve binding."),
],
witnesses: [
witness("voidbot.swarm_state_snapshot", ".voidbot/status/cultmesh/voidbot-swarm-state.cc", "scripts/render-voidbot-swarm-dashboard.mjs", ["voidbot.swarm_state_snapshot", "gamecult.eve.provider_advertisement", "gamecult.eve.surface_state", "gamecult.eve.interface_binding"]),
witness("voidbot.repo_persona_heartbeat_state", ".voidbot/status/repo-persona-heartbeats.json", "VoidBot CTB scheduler", ["debug/status source"]),
witness("voidbot.orchestrator_state", ".voidbot/status/gamecult-orchestrator.json", "GameCult Local Orchestrator", ["debug/status source"]),
],
commands: [
command("swarm.inspect", "read-only", "Load the current CTB and selected Persona state surface."),
command("swarm.set_pause", "side-effecting", "Set the repo-controlled swarm pause flag through the provider-owned command boundary."),
command("swarm.set_cadence_multiplier", "side-effecting", "Update scheduler cadence through the provider-owned command boundary."),
command("swarm.force_turn", "side-effecting", "Queue a manual turn request through the provider-owned command boundary."),
],
owners: {
state: "VoidBot CTB heartbeat state and typed operation ports",
inspection: "VoidBot swarm renderer for swarm-only state; Huginn for Persona/.cc detail",
presentation: "Eve/CultUI lowerings",
},
}),
];
return {
documentType: fixtureDocumentType,
schemaId: fixtureSchemaId,
fixtureKind: "read-only-contract-export",
note: "This tracked fixture advertises VoidBot Verse provider boundaries. It is not live service state, not a dashboard, and not a Persona inspection authority.",
verseId,
rootVerse,
canonicalService,
locatedService,
cultMeshAddress: locatedService,
providerAdvertisement: {
documentType: providerAdvertisementDocumentType,
schemaId: providerAdvertisementSchemaId,
},
eveDocuments: {
surpersonaState: {
documentType: surpersonaStateDocumentType,
schemaId: surpersonaStateSchemaId,
},
interfaceBinding: {
documentType: interfaceBindingDocumentType,
schemaId: interfaceBindingSchemaId,
},
},
cultMeshKeys: {
providerCatalog: key("voidbot.providers"),
providerAdvertisements: providers.map((item) => key(item.providerId)),
surpersonaStates: providers.map((item) => key(item.providerId)),
interfaceBindings: providers.map((item) => key(item.providerId)),
liveSwarmStore: ".voidbot/status/cultmesh/voidbot-swarm-state.cc",
},
huginnInspectionHandoff: {
owner: "Huginn",
scope: [
"Persona-state schema availability",
".cc inspection and projection health",
"Persona migration pressure",
"CultMesh publication of Persona/.cc inspection surfaces",
"Eve DSL emission for typed-state inspection",
],
voidbotRole: [
"Carries Discord compatibility registry data.",
"Names repo Persona .cc witness paths.",
"Feeds context to Huginn when requested.",
"Does not become the Persona runtime steward.",
],
personaSchema: "E:/Projects/EpiphanyAgent/schemas/cultnet/gamecult.persona_state.v0.schema.json",
},
personaStateBoundaries: {
portableAuthority: "gamecult.persona_state.v0",
voidbotCompatibilityOnly: [
"repo Discord identity registry",
"webhook persona addressing",
"repo Persona .cc witness paths",
"legacy MCP state diagnostics",
],
forbiddenAuthority: [
"VoidBot static HTML as Persona truth",
"VoidBot private registry as portable Persona state",
"Odin mutation of provider state",
"Huginn inspection tools mutating VoidBot state outside advertised typed command ports",
],
},
providers,
};
}
function provider(input) {
const resourceBase = providerResourceBase(input.id);
const cultMeshAddress = `${locatedService}/${resourceBase}/eve/tui`;
return {
schemaVersion: providerAdvertisementSchemaId,
providerId: input.id,
verseId,
rootVerse,
canonicalService,
locatedService,
cultMeshAddress,
title: input.title,
description: input.description,
status: input.status,
endpoint: cultMeshAddress,
endpoints: [
endpoint("tui", cultMeshAddress, surpersonaStateSchemaId, ["tui", "agent"]),
endpoint("gui", `${locatedService}/${resourceBase}/eve/gui`, surpersonaStateSchemaId, ["gui", "eve-native", "browser"]),
],
routes: [
route("cultmesh-document", key(input.id).endpoint, "Legacy CultMesh document key; route only, not service identity."),
],
documents: [
document(providerAdvertisementDocumentType, providerAdvertisementSchemaId, input.id),
document(surpersonaStateDocumentType, surpersonaStateSchemaId, input.id),
document(interfaceBindingDocumentType, interfaceBindingSchemaId, input.id),
],
surfaces: input.surfaces,
commands: input.commands,
witnesses: input.witnesses,
owners: input.owners,
demotions: input.demotions ?? [],
};
}
function surface(id, title, purpose) {
return { id, title, purpose, presentationOwner: "Eve/CultUI" };
}
function witness(id, path, owner, contains) {
return { id, path, owner, contains };
}
function command(id, mode, purpose) {
return { id, mode, purpose, advertisedOnly: true };
}
function document(type, schemaId, keyValue) {
return { type, schemaId, key: keyValue };
}
function endpoint(kind, address, schemaId, lowerings) {
return { kind, address, schemaId, lowerings };
}
function route(kind, address, note) {
return { kind, address, note };
}
function providerResourceBase(providerId) {
return providerId.replace(/^voidbot\./u, "").replace(/_/gu, "-");
}
function key(value) {
return {
verseId,
key: value,
endpoint: `cultmesh://${verseId}/eve/providers/${value}`,
};
}
function validateCatalog(value) {
const requiredProviders = ["voidbot.discord", "voidbot.archive", "voidbot.source", "voidbot.repo_persona", "voidbot.swarm"];
const providerIds = new Set(value.providers.map((providerItem) => providerItem.providerId));
for (const providerId of requiredProviders) {
if (!providerIds.has(providerId)) {
throw new Error(`Missing provider advertisement for ${providerId}.`);
}
}
for (const providerItem of value.providers) {
for (const field of ["surfaces", "commands", "witnesses"]) {
if (!Array.isArray(providerItem[field]) || providerItem[field].length === 0) {
throw new Error(`${providerItem.providerId} must advertise ${field}.`);
}
}
}
if (!value.huginnInspectionHandoff?.owner || value.huginnInspectionHandoff.owner !== "Huginn") {
throw new Error("Huginn inspection handoff must be explicit.");
}
if (!value.personaStateBoundaries?.portableAuthority) {
throw new Error("Persona state boundary must name portable authority.");
}
}
function parseArgs(argv) {
const parsed = {};
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
if (arg === "--check") {
parsed.check = true;
continue;
}
if (!arg.startsWith("--")) {
throw new Error(`Unexpected argument: ${arg}`);
}
const key = arg.slice(2);
const next = argv[index + 1];
if (!next || next.startsWith("--")) {
throw new Error(`Missing value for --${key}`);
}
parsed[key] = next;
index += 1;
}
return parsed;
}