Heimdall/src/index.ts
Meta Vens 55732a901d Presence: the publisher sequence is in-process, the state root keeps no owner-only file, an Idunn generation never self-enrolls
nextPublisherSequence read and rewrote <dataRoot>/runtime-presence-sequence at
mode 0600, which the next generation (a fresh DynamicUser uid in the shared
state group) cannot open. Odin compares publisher_sequence only within one
activation and Idunn mints a new activation per start, so PublisherSequence
derives it from the observation clock, never below the previous value plus
one, as Muninn does. openProviderHealthIdentity under
GAMECULT_IDUNN_RUNTIME_BUNDLE now refuses when the presence descriptor is
absent instead of enrolling an identity Idunn does not trust.

Adds the cross-generation test (root only) and the sequence, no-dataRoot and
missing-descriptor tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:25:11 +02:00

134 lines
5.5 KiB
TypeScript

import { resolveWriteLease, WriteLeaseNotHeldError } from "./process-write-lease.js";
import { buildApp } from "./app.js";
import { loadConfig } from "./config.js";
import { publishIdunnRudpHealth } from "./idunn-rudp-health.js";
import { publishHeimdallOdinState } from "./odin-publication.js";
import { startHeimdallPrivateCommandPlane } from "./private-command-plane.js";
import type { CultNetOperationServer } from "cultnet-ts";
import { createHeimdallRuntimePulse, buildHeimdallHealthDetail, publishHeimdallVerseState } from "./verse-state.js";
import {
buildPresenceStatement,
PublisherSequence,
readRuntimeBundleFacts,
type RuntimeBundleFacts,
} from "./runtime-presence.js";
const config = loadConfig();
const app = await buildApp({ config });
const versePulseIntervalMs = 60_000;
const healthPulseIntervalMs = 10_000;
let privateCommands: CultNetOperationServer | undefined;
try {
privateCommands = await startHeimdallPrivateCommandPlane(app, config);
app.addHook("onClose", async () => privateCommands?.close());
await app.listen({ host: config.host, port: config.port });
app.log.info(`Heimdall listening on ${config.host}:${config.port}`);
app.log.info(`Heimdall private CultNet commands listening at ${privateCommands.endpoint}`);
await publishVerseState().catch((error) => {
console.error("Heimdall verse publication failed on startup:", error);
});
await publishHealthState().catch((error) => {
console.error("Heimdall signed health publication failed on startup:", error);
});
const verseTimer = setInterval(() => {
void publishVerseState().catch((error) => {
console.error("Heimdall verse publication failed on interval:", error);
});
}, versePulseIntervalMs);
void runHealthPulseLoop();
verseTimer.unref?.();
} catch (error) {
await privateCommands?.close().catch(() => undefined);
// Write to stderr directly as well as through the logger. Pino buffers, and
// setting exitCode lets the process exit before that buffer flushes, so a
// startup failure logged only through app.log disappears entirely. Under a
// supervisor that is the worst possible failure: the unit dies, the journal
// holds nothing, and the operator is told only that it is not running.
console.error("Heimdall failed to start:", error);
app.log.error(error);
process.exitCode = 1;
}
async function publishVerseState(): Promise<void> {
const pulse = createHeimdallRuntimePulse(config);
try {
await publishHeimdallVerseState(config, pulse);
} catch (error) {
// A warming candidate does not hold the write lease, and will not until
// Idunn fences the incumbent. That is the expected state for most of a
// deployment, not a fault: decline the write, say so once per pulse at
// info, and keep serving. Health publication continues either way, which
// is what lets Idunn observe the candidate and promote it.
if (error instanceof WriteLeaseNotHeldError) {
app.log.info(error.message);
return;
}
throw error;
}
await publishHeimdallOdinState(config, pulse);
}
/**
* Read once and keep: the bundle is Idunn's immutable record of this launch and
* cannot change while the process lives, so re-reading it every pulse would be
* work that can only ever return the same answer or fail.
*/
let runtimeBundleFacts: RuntimeBundleFacts | undefined;
const presenceSequence = new PublisherSequence();
async function publishHealthState(): Promise<void> {
const pulse = createHeimdallRuntimePulse(config);
// "active" is a claim about owning the target's state, not about having
// started. A candidate that has not been granted the write lease is serving
// but is not the owner, and Idunn must observe `warming` before it fences the
// incumbent — a candidate that reports active from its first pulse cannot be
// promoted correctly, because the warming evidence the transaction needs
// never appears.
const lease = await resolveWriteLease({
leasePath: config.idunnWriteLeasePath,
runtimeBundlePath: config.idunnRuntimeBundlePath,
});
const detail = lease.mayWrite
? buildHeimdallHealthDetail(config, pulse)
: `Heimdall candidate warming; ${lease.reason}`;
if (!config.idunnRuntimeBundlePath) {
// Outside Idunn there is no launch to attest and nothing that admits the
// statement; publishing is simply not part of that world.
return;
}
runtimeBundleFacts ??= await readRuntimeBundleFacts(config.idunnRuntimeBundlePath);
const observedAtUnixMillis = Date.parse(pulse.updatedAt);
const presence = await buildPresenceStatement({
bundle: runtimeBundleFacts,
state: lease.mayWrite ? "active" : "warming",
detail,
boundEndpoint: `http://${config.host}:${config.port}`,
// A warming candidate holds no lease and must not claim one; the contract
// refuses the pair, and Idunn needs the warming statement to promote it.
writeLeaseSha256: null,
observedAtUnixMillis,
publisherSequence: presenceSequence.next(observedAtUnixMillis),
providerHealthIdentityPath: config.providerHealthIdentityPath,
});
await publishIdunnRudpHealth(config, {
daemonId: config.daemonId,
state: lease.mayWrite ? "active" : "warming",
detail,
observedAt: pulse.updatedAt,
presence,
});
}
async function runHealthPulseLoop(): Promise<void> {
for (;;) {
await new Promise((resolve) => setTimeout(resolve, healthPulseIntervalMs));
await publishHealthState().catch((error) => {
console.error("Heimdall signed health publication failed on interval:", error);
});
}
}