tools/core/runners/venue.runner.ts

tools/core/runners/venue.runner.ts is a file in Coordination Surface. 137 lines of code and 24 definitions.

import {
    AGENDA,
    AWAITING_MARKER,
    BLOCKING_SUFFIX,
    PROTOCOL_BANNER,
    UNREAD_MARKER,
} from "../constants/blocking.constants.ts";
import {
    RAISE_SPECIMEN_MISSING,
    RELOCATE_ROOT_MISSING,
    SUCCESSOR_NOT_VENUE,
    raiseUnbound,
    raised as raisedMessage,
    relocateDestinationTaken,
    relocateMismatch,
    relocateNotVenue,
    relocateSourceMissing,
    relocated,
    successorContended,
    successorDeclared,
    successorNotPlanned,
    successorSectionMissing,
    successorVenueMissing,
} from "../strings/venue.strings.ts";
import type { RaiseOutcome, RaiseRequest } from "../types/venue.types.ts";
import { SIGN_OFF_BANNER, surfaceText, textIfPresent } from "../readers/venue.reader.ts";
import { SUCCESSOR_FIELD, successorWritten } from "../transformers/venue.transformer.ts";
import {
    blockFor,
    deferringLabel,
    deferringVenues,
    recordSpecimen,
    seatRecord,
} from "../generators/venue.generator.ts";
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { projectRoot, surfacePrefix } from "../../../config/surface.config.ts";
import { AGENT_INDEX } from "../constants/board.constants.ts";
import { indexedLetters } from "../inspectors/index.inspector.ts";
import { plannedInvariants } from "../readers/agenda.reader.ts";
import { raisePlan } from "../inspectors/venue.inspector.ts";
import { resolve } from "node:path";
import { rosterLine } from "./converge.runner.ts";
import { seatedLetters } from "../resolvers/venue.resolver.ts";
import { venueRefusal } from "../factories/venue.factory.ts";

export const runSuccessor = function runSuccessor(options: {
    readonly target: string;
    readonly absolute: string;
    readonly invariant: string;
}): RaiseOutcome {
    if (!options.target.endsWith(BLOCKING_SUFFIX)) {
        return venueRefusal(SUCCESSOR_NOT_VENUE);
    }

    if (!existsSync(options.absolute)) {
        return venueRefusal(successorVenueMissing(options.target));
    }

    const agenda = surfaceText(projectRoot(), AGENDA);
    const planned = plannedInvariants(agenda);
    if (!planned.includes(options.invariant)) {
        return venueRefusal(successorNotPlanned(options.invariant));
    }

    const before = readFileSync(options.absolute, "utf8");
    const written = successorWritten(before, `${SUCCESSOR_FIELD} ${options.invariant}`);
    if (written === null) {
        return venueRefusal(successorSectionMissing(options.target));
    }

    const witness = readFileSync(options.absolute, "utf8");
    if (witness !== before) {
        return { code: 2, message: successorContended(options.target), raised: null };
    }

    writeFileSync(options.absolute, written, "utf8");

    return { code: 0, message: successorDeclared(options.target, options.invariant), raised: null };
};

export const runRelocate = function runRelocate(options: {
    readonly repoRoot: string;
    readonly name: string;
}): RaiseOutcome {
    if (!options.name.endsWith(BLOCKING_SUFFIX)) {
        return venueRefusal(relocateNotVenue(options.name));
    }

    const venueRoot = resolve(options.repoRoot, surfacePrefix());
    const destination = resolve(venueRoot, options.name);
    const stray = resolve(options.repoRoot, options.name);

    if (!existsSync(venueRoot)) {
        return venueRefusal(RELOCATE_ROOT_MISSING);
    }

    if (existsSync(destination)) {
        return venueRefusal(relocateDestinationTaken(options.name));
    }

    if (!existsSync(stray)) {
        return venueRefusal(relocateSourceMissing(options.name));
    }

    const carried = readFileSync(stray, "utf8");
    writeFileSync(destination, carried, "utf8");

    const landed = readFileSync(destination, "utf8");
    if (landed !== carried) {
        return venueRefusal(relocateMismatch(options.name));
    }

    rmSync(stray);

    return { code: 0, message: relocated(options.name), raised: options.name };
};

const rosterStart = function rosterStart(line: string, participants: readonly string[]): string {
    if (line.startsWith(UNREAD_MARKER)) {
        return rosterLine(UNREAD_MARKER, participants);
    }
    return line.startsWith(AWAITING_MARKER) ? rosterLine(AWAITING_MARKER, []) : line;
};

export const runRaise = function runRaise(options: RaiseRequest): RaiseOutcome {
    const plan = raisePlan(options);
    if (!("template" in plan)) {
        return plan;
    }

    const { destination, invariant, name, template, venueRoot } = plan;
    const deferring = deferringVenues(venueRoot, options.repoRoot, invariant);
    const composed = template.replace(PROTOCOL_BANNER, `${blockFor(invariant, deferring)}${PROTOCOL_BANNER}`);
    const participants = options.seats.length > 0 ? [...options.seats] : seatedLetters(options.repoRoot);

    const bound = indexedLetters(textIfPresent(resolve(options.repoRoot, AGENT_INDEX))).letters;
    const unbound = participants.filter((letter) => !bound.has(letter));
    if (unbound.length > 0) {
        return venueRefusal(raiseUnbound(unbound));
    }

    const specimen = recordSpecimen(template);
    if (specimen.length === 0) {
        return venueRefusal(RAISE_SPECIMEN_MISSING);
    }

    const seated = participants.flatMap((letter) => [...seatRecord(specimen, letter), ""]);
    const raisedVenue = composed
        .split("\n")
        .flatMap((line) => (line.startsWith(SIGN_OFF_BANNER) ? [...seated, line] : [line]))
        .map((line) => rosterStart(line, participants))
        .join("\n");

    writeFileSync(destination, raisedVenue, "utf8");

    return {
        code: 0,
        message: raisedMessage(name, participants, deferring.clauses.length, deferringLabel(deferring)),
        raised: name,
    };
};