Files
stack/packages/mosaic/src/fleet/comms-onboarding.ts
jason.woltje adad486b6f
All checks were successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
fix(fleet): enforce exact comms authority (#787)
2026-07-16 00:32:23 +00:00

424 lines
14 KiB
TypeScript

/**
* Exact roster-resolved fleet communications contract (#766).
*
* The runtime composer and `mosaic fleet` command surface share the canonical
* v1 roster resolver. This module never probes tmux, guesses an SSH target, or
* mutates an active session.
*/
import { createHash } from 'node:crypto';
import { existsSync } from 'node:fs';
import { homedir, hostname } from 'node:os';
import { join } from 'node:path';
import { readRegularFileSecure } from './secure-file.js';
import {
parseFleetRosterV1,
resolveInstalledFleetRosterPath,
getRosterAgent,
type FleetAgent,
type FleetRoster,
} from './fleet-roster-v1.js';
export interface FleetCommsOptions {
/** Exact current roster member. */
selfName: string;
/** Canonically resolved roster. */
roster: FleetRoster;
/** Stable fleet-host baseline for members whose roster host is absent. */
localHost: string;
/** Absolute helper path in this installation. */
agentSendPath: string;
}
export interface CommsBlockResult {
ok: boolean;
output: string;
error?: string;
}
export interface ResolvedFleetIdentity {
readonly roster: FleetRoster;
readonly member: FleetAgent;
readonly requestedName: string;
readonly agentSendPath: string;
readonly localHost: string;
}
export interface FleetIdentityResult {
ok: boolean;
identity?: ResolvedFleetIdentity;
error?: string;
}
export interface PeerCommandResult {
ok: boolean;
command: string;
error?: string;
}
export const FLEET_COMMS_TOOLS_CONTRACT = 'fleet-comms-contract: 1';
const MAX_TOOLS_CONTRACT_BYTES = 256 * 1024;
function shortHostname(): string {
return hostname().split('.')[0] || 'localhost';
}
function resolvedHost(agent: FleetAgent, fleetHost: string): string {
return agent.host ?? fleetHost;
}
function displaySocket(socket: string): string {
return socket || '(default)';
}
function knownNames(roster: FleetRoster, except?: string): string {
return roster.agents
.filter((agent) => agent.name !== except)
.map((agent) => agent.name)
.join(', ');
}
function missingMemberError(roster: FleetRoster, selfName: string): string {
return `Agent "${selfName}" is not in the fleet roster. Known exact names: ${knownNames(roster)}. Select an exact roster name; do not infer or fuzzy-match a tmux session.`;
}
/** Render one shell argument without changing already-safe exact values. */
function shellArg(value: string): string {
if (/^[A-Za-z0-9_./:@=+-]+$/.test(value)) return value;
return `'${value.replaceAll("'", `'"'"'`)}'`;
}
/** Render the exact command for one peer. Throws rather than guessing. */
export function renderPeerReach(
peer: FleetAgent,
selfHost: string,
fleetHost: string,
rosterSocket: string,
agentSendPath: string,
): string {
const parts = [shellArg(agentSendPath)];
if (rosterSocket) parts.push('-L', shellArg(rosterSocket));
const peerHost = resolvedHost(peer, fleetHost);
if (peerHost !== selfHost) {
if (!peer.ssh) {
throw new Error(
`Cross-host peer "${peer.name}" (${peerHost}) requires an explicit roster ssh target; refusing to substitute its host value.`,
);
}
parts.push('-H', shellArg(peer.ssh));
}
parts.push('-s', shellArg(peer.name), '-m', '"…"');
return parts.join(' ');
}
/** Resolve one requested peer without fuzzy lookup. */
export function resolvePeerCommand(
roster: FleetRoster,
selfName: string,
peerName: string,
localHost: string,
agentSendPath: string,
): PeerCommandResult {
const self = roster.agents.find((agent) => agent.name === selfName);
if (!self) return { ok: false, command: '', error: missingMemberError(roster, selfName) };
const peer = roster.agents.find((agent) => agent.name === peerName && agent.name !== selfName);
if (!peer) {
return {
ok: false,
command: '',
error:
`Peer "${peerName}" is absent from the fleet roster. Known exact peer names: ${knownNames(roster, selfName)}. ` +
`Run \`mosaic agent comms-block ${selfName}\` to rediscover exact rendered rows; do not infer or fuzzy-match a tmux session.`,
};
}
try {
return {
ok: true,
command: renderPeerReach(
peer,
resolvedHost(self, localHost),
localHost,
roster.tmux.socketName,
agentSendPath,
),
};
} catch (error) {
return {
ok: false,
command: '',
error: error instanceof Error ? error.message : String(error),
};
}
}
interface ResolvedRow {
readonly peer: FleetAgent;
readonly host: string;
readonly socket: string;
readonly command: string;
}
function resolveRows(opts: FleetCommsOptions, self: FleetAgent): readonly ResolvedRow[] {
const selfHost = resolvedHost(self, opts.localHost);
return opts.roster.agents
.filter((agent) => agent.name !== opts.selfName)
.map(
(peer): ResolvedRow => ({
peer,
host: resolvedHost(peer, opts.localHost),
socket: opts.roster.tmux.socketName,
command: renderPeerReach(
peer,
selfHost,
opts.localHost,
opts.roster.tmux.socketName,
opts.agentSendPath,
),
}),
);
}
function commsGeneration(
self: FleetAgent,
selfHost: string,
selfSocket: string,
helper: string,
rows: readonly ResolvedRow[],
): string {
const canonical = JSON.stringify({
self: { ...self, resolvedHost: selfHost, resolvedSocket: selfSocket, helper },
peers: rows.map((row) => ({
...row.peer,
resolvedHost: row.host,
resolvedSocket: row.socket,
exactCommand: row.command,
})),
});
return createHash('sha256').update(canonical).digest('hex');
}
/** Build the authoritative Markdown contract for one exact roster member. */
export function buildFleetCommsBlock(opts: FleetCommsOptions): string {
const self = opts.roster.agents.find((agent) => agent.name === opts.selfName);
if (!self) throw new Error(missingMemberError(opts.roster, opts.selfName));
const rows = resolveRows(opts, self);
const selfHost = resolvedHost(self, opts.localHost);
const selfSocket = opts.roster.tmux.socketName;
const generation = commsGeneration(self, selfHost, selfSocket, opts.agentSendPath, rows);
const orchestrator = rows.find((row) => row.peer.className === 'orchestrator');
const peerSection =
rows.length === 0
? 'This roster has no peers. Do not invent a target.'
: `| Agent | Role | Host | Socket | Exact command |
| ----- | ---- | ---- | ------ | ------------- |
${rows
.map((row) => {
const pointOfContact = row.peer.className === 'orchestrator' ? ' ← point of contact' : '';
return `| ${row.peer.name} | ${row.peer.className}${pointOfContact} | ${row.host} | ${displaySocket(row.socket)} | \`${row.command}\` |`;
})
.join('\n')}`;
const contact = orchestrator
? `Your point of contact is **${orchestrator.peer.name}**. Select that exact peer row for status, questions, and decisions.`
: rows.length === 0
? 'No peer coordination target exists in this roster.'
: 'This roster has no orchestrator. Select an exact peer row for coordination.';
const soloAuthority =
rows.length === 0
? `\n## Solo authority boundaries\n\nThis member is normalized as role/class **${self.className}**. The roster grants no peer, orchestrator, or remote communication authority. Do not send, infer a target, or claim fleet coordination until an exact peer is added to the canonical roster and this block is recomposed.\n`
: '';
return `# Fleet Comms — authoritative exact targets
## Local identity
- Host: \`${selfHost}\`
- Agent/session: \`${self.name}\`
- Role/class: \`${self.className}\`
- tmux socket: \`${displaySocket(selfSocket)}\`
- Helper: \`${opts.agentSendPath}\`
- Comms generation: \`${generation}\`
The roster-resolved rows below are the only valid operational targets. Select the row whose Agent value
exactly matches the requested peer. Never invent, substitute, or fuzzy-match host, session, socket, SSH,
or helper-path values. If the peer is absent, stop and run \`mosaic agent comms-block ${self.name}\` to
rediscover this exact member's rows; if it is still absent, report the unknown peer.
## Peers
${peerSection}
${contact}
${soloAuthority}
## Context freshness
This block is a snapshot; Mosaic does not rewrite an active agent's context. Compare its Comms generation
with fresh output from \`mosaic agent comms-block ${self.name}\`. If they differ, report stale composed
context and have an authorized operator relaunch only this exact roster member with
\`mosaic fleet restart ${self.name}\`. Do not restart or mutate a session automatically.`;
}
function validateAgentSendHelper(path: string, mosaicHome: string): string | undefined {
try {
readRegularFileSecure(path, { root: mosaicHome, executable: true });
return undefined;
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
return `helper is unavailable or unsafe: ${path} (${reason})`;
}
}
function helperFailureGuidance(reason: string): string {
return `${reason}. Run \`mosaic update --repair-tools\` to restore the supported current-version helper and TOOLS contract, then retry exact-member composition; no active context or session was rewritten.`;
}
export function resolveFleetIdentity(
mosaicHome: string,
requestedName: string | undefined,
localHost: string = shortHostname(),
): FleetIdentityResult {
if (!requestedName) return { ok: true };
const agentSendPath = join(mosaicHome, 'tools', 'tmux', 'agent-send.sh');
const helperError = validateAgentSendHelper(agentSendPath, mosaicHome);
if (helperError) return { ok: false, error: helperFailureGuidance(helperError) };
let rosterPath: string;
try {
rosterPath = resolveInstalledFleetRosterPath(mosaicHome);
} catch (error) {
return {
ok: false,
error: `cannot inspect fleet roster.yaml: ${error instanceof Error ? error.message : String(error)}; refusing JSON fallback because fallback is allowed only when YAML is absent`,
};
}
if (!existsSync(rosterPath)) {
return {
ok: false,
error: `no fleet roster at ${join(mosaicHome, 'fleet', 'roster.yaml')} or ${join(mosaicHome, 'fleet', 'roster.json')}`,
};
}
let roster: FleetRoster;
try {
roster = parseFleetRosterV1(
readRegularFileSecure(rosterPath, { root: mosaicHome }).content.toString('utf8'),
rosterPath.endsWith('.json') ? 'json' : 'yaml',
);
} catch (error) {
return {
ok: false,
error: `invalid fleet roster at ${rosterPath}: ${error instanceof Error ? error.message : String(error)}`,
};
}
try {
return {
ok: true,
identity: {
roster,
member: getRosterAgent(roster, requestedName),
requestedName,
agentSendPath,
localHost,
},
};
} catch {
return { ok: false, error: missingMemberError(roster, requestedName) };
}
}
/** Render Fleet Comms from one already-resolved canonical member identity. */
export function buildResolvedFleetCommsBlock(identity: ResolvedFleetIdentity): string {
return buildFleetCommsBlock({
selfName: identity.member.name,
roster: identity.roster,
localHost: identity.localHost,
agentSendPath: identity.agentSendPath,
});
}
/**
* Read and resolve the installed roster for runtime composition. A requested
* fleet identity fails closed; only a genuinely non-fleet launch (no selfName)
* is a quiet no-op.
*/
export function readFleetCommsBlock(
mosaicHome: string,
selfName: string | undefined,
localHost: string = shortHostname(),
): CommsBlockResult {
const resolved = resolveFleetIdentity(mosaicHome, selfName, localHost);
if (!resolved.ok) return { ok: false, output: '', error: resolved.error };
if (!resolved.identity) return { ok: true, output: '' };
try {
return { ok: true, output: buildResolvedFleetCommsBlock(resolved.identity) };
} catch (error) {
return {
ok: false,
output: '',
error: error instanceof Error ? error.message : String(error),
};
}
}
/** Backing resolver for `mosaic agent comms-block <exact-member>`. */
export function resolveCommsBlock(
mosaicHome: string,
exactMember: string | undefined,
): CommsBlockResult {
if (!exactMember) {
return {
ok: false,
output: '',
error: 'comms-block requires an exact <exact-member> argument',
};
}
return readFleetCommsBlock(mosaicHome, exactMember);
}
function expectedContractVersion(content: Buffer | string): boolean {
return content.toString().includes(`<!-- ${FLEET_COMMS_TOOLS_CONTRACT} -->`);
}
function boundedContractDigest(
path: string,
mosaicHome: string,
): { digest?: string; versionOk: boolean } {
try {
const content = readRegularFileSecure(path, {
root: mosaicHome,
maxBytes: MAX_TOOLS_CONTRACT_BYTES,
}).content;
return {
digest: createHash('sha256').update(content).digest('hex'),
versionOk: expectedContractVersion(content),
};
} catch {
return { versionOk: false };
}
}
function replacementGuidance(): string {
return `Run \`mosaic update --repair-tools\` to make a digest-qualified backup and restore the supported current-version TOOLS contract, then have an authorized operator explicitly relaunch the exact roster member. The active context was not rewritten.`;
}
/** Detect preserved installed TOOLS.md drift without changing it. */
export function renderToolsContractStatus(mosaicHome: string): string {
const installedPath = join(mosaicHome, 'TOOLS.md');
const sourcePath = join(mosaicHome, 'defaults', 'TOOLS.md');
if (!existsSync(installedPath)) {
return `# Fleet Comms Installation Status\n\nInstalled TOOLS.md is missing at \`${installedPath}\`. ${replacementGuidance()}`;
}
const installed = boundedContractDigest(installedPath, mosaicHome);
const source = boundedContractDigest(sourcePath, mosaicHome);
if (!source.digest || !source.versionOk) {
return `# Fleet Comms Installation Status\n\nThe bounded framework source contract at \`${sourcePath}\` is unavailable or does not declare the expected \`${FLEET_COMMS_TOOLS_CONTRACT}\` version. Run \`mosaic update\` to restore framework source data, verify again, then have an authorized operator explicitly relaunch the exact roster member. The installed file and active context were not rewritten.`;
}
if (installed.versionOk && installed.digest === source.digest) return '';
return `# Fleet Comms Installation Status\n\nInstalled TOOLS.md is unavailable, has the wrong contract version, or does not byte-match the bounded framework source contract \`${FLEET_COMMS_TOOLS_CONTRACT}\`. ${replacementGuidance()}`;
}
export const DEFAULT_MOSAIC_HOME_FOR_COMMS = join(homedir(), '.config', 'mosaic');