import { AGENDA, BLOCKING_SUFFIX, VENUE_ARCHIVE } from "../constants/blocking.constants.ts"; import { ARRIVE_NOT_VENUE, ARRIVE_NO_SUCCESSOR, CLAUSE_IS_RECEIVER, DEFER_NOT_VENUE, RAISE_AGENDA_MISSING, RAISE_BANNER_MISSING, RAISE_NOTHING_ADMISSIBLE, RAISE_SPECIMEN_MISSING, RAISE_TEMPLATE_MISSING, RELOCATE_ROOT_MISSING, RETRACT_NOT_VENUE, SUCCESSOR_NOT_VENUE, arrivalContended, arrivalHeld, arrivalSettled, arriveNoInheritedSection, arrivePredecessorMissing, arriveSectionEmpty, arriveSuccessorUnraised, arrived, authorityHeld, clauseDeferred, clauseRetracted, clauseTaken, deferVenueMissing, deferredSectionMissing, defersAlready, defersNothing, inheritContended, inheritNotVenue, inheritSectionMissing, inheritSectionUnbounded, inheritUnchanged, inheritUpdated, inheritVenueElsewhere, markerContended, raiseDestinationTaken, raiseNotAdmissible, raiseOrdinalMissing, raiseSuccessorUndeclared, raiseUnbound, raised as raisedMessage, receiverUnknown, recordAdded, recordAnchorMissing, recordContended, recordExists, recordSurfaceMissing, recordTemplateEmpty, recordTemplateMissing, relocateDestinationTaken, relocateMismatch, relocateNotVenue, relocateSourceMissing, relocated, retireContended, retireLineMissing, retireMissing, retireNotPlanning, retireNothingDistributed, retirePreview, retireVenueOpen, retired, retractClauseMissing, retractSectionMissing, retractVenueMissing, rosterContended, rosterLinesMissing, rosterNotVenue, rosterRegression, rosterResolved, rosterUnchanged, rosterVenueElsewhere, successorContended, successorDeclared, successorNotPlanned, successorSectionMissing, successorVenueMissing, } from "../strings/venue.strings.ts"; import { DEFERRAL_ARROW, DEFERRED_SECTION, DISTRIBUTES_FIELD, SIGN_OFF_BANNER, addressesSuccessor, clauseLines, declaredSuccessor, receiverResolves, sectionBound, unarrivedDeferrals, venueInvariant, } from "../validators/converge.validator.ts"; import { basename, dirname, resolve } from "node:path"; import { existsSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs"; import { isResolved, projectRoot, slotText, surfacePath, surfacePrefix } from "../../../config/surface.config.ts"; import { letters, rosterLine } from "./converge.runner.ts"; import { AGENT_INDEX } from "../constants/board.constants.ts"; import { CHECKLIST_CONCERN } from "../constants/checklist.constants.ts"; import { activeSeats } from "../analyzers/board.analyzer.ts"; import { fencedFlags } from "../predicates/fence.predicate.ts"; import { indexedLetters } from "../inspectors/index.inspector.ts"; import { readBoardContract } from "../readers/board.reader.ts"; import { venueFieldsFrom } from "../validators/venue.validator.ts"; const DEFERRED_HEADING = "## DEFERRED"; const PROTOCOL_BANNER = "═══════════════════ PROTOCOL (permanent) ═══════════════════"; const INHERITED_BANNER = "═══════════════════ INHERITED"; export interface RaiseOutcome { readonly code: number; readonly message: string; readonly raised: string | null; } const refusal = function refusal(reason: string): RaiseOutcome { return { code: 2, message: `REFUSED ${reason}\n`, raised: null }; }; const DIGITS = new Set("0123456789"); export const ordinalOf = function ordinalOf(name: string): string { for (const part of basename(name).split(".")) { if (part.length > 0 && DIGITS.has(part.charAt(0))) { return part; } } return ""; }; const inheritedBlock = function inheritedBlock(predecessorName: string, clauses: readonly string[]): string { const ordinal = ordinalOf(predecessorName); const carried = clauses.map((clause) => `- ${clause}`).join("\n"); return ( `${INHERITED_BANNER} (from venue ${ordinal.length === 0 ? predecessorName : ordinal}) ═══════════════════\n\n` + "**These are questions the predecessor deliberately left open, carried here BY NAME because a question " + "deferred to a venue that does not exist is deferred to nobody.** Each names its origin and its receiver. " + "None is a finding — the findings extracted to the accumulator under their own class headings and none of " + "that travels.\n\n" + `${carried}\n\n` ); }; const unclaimedBlock = function unclaimedBlock(invariant: string): string { return ( `${INHERITED_BANNER} (no venue defers to ${invariant}) ═══════════════════\n\n` + "**No venue on disk or in the archive defers a clause to this invariant, so the inherited set is EMPTY BY " + "DERIVATION rather than unstated.** A venue is raised at the position the SCHEDULE assigns and inherits " + "whatever a DEFERRAL sent here by name, and those are two questions with two answers — so a venue whose turn " + "arrives before anyone has deferred a question to it opens carrying nothing, which is a state a reader can " + "check rather than a blank a reader must interpret.\n\n" + `${RECORD_ABSENT}\n\n` ); }; const deferringVenues = function deferringVenues( directory: string, repoRoot: string, invariant: string, ): { names: string[]; clauses: string[] } { const names: string[] = []; const clauses: string[] = []; for (const root of [directory, resolve(repoRoot, VENUE_ARCHIVE)]) { if (!existsSync(root)) { continue; } for (const entry of readdirSync(root).sort()) { if (!entry.endsWith(BLOCKING_SUFFIX)) { continue; } const source = readFileSync(resolve(root, entry), "utf8"); const carried = clauseLines(source, DEFERRED_HEADING) .filter((line) => line.receiver === invariant) .map((line) => line.clause); if (carried.length === 0) { continue; } names.push(entry); clauses.push(...carried); } } return { clauses, names }; }; const AGENDA_MARK = "`"; const ROW_LEAD = "|"; const ROW_MARK = "|"; export const boundAuthority = function boundAuthority(repoRoot: string): string | null { if (!isResolved("convention", "venue_authority_concern")) { return null; } const concern = slotText("convention", "venue_authority_concern"); const index = resolve(repoRoot, AGENT_INDEX); if (!existsSync(index)) { return null; } for (const line of readFileSync(index, "utf8").split("\n")) { const trimmed = line.trim(); if (!trimmed.startsWith(ROW_MARK) || !trimmed.includes(concern)) { continue; } const letter = trimmed.slice(1, trimmed.indexOf(ROW_MARK, 1)).trim(); if (letter.length > 0) { return letter; } } return null; }; export const seatedLetters = function seatedLetters(repoRoot: string): string[] { const board = resolve(repoRoot, surfacePath("board")); const index = resolve(repoRoot, AGENT_INDEX); return [ ...activeSeats( existsSync(board) ? readFileSync(board, "utf8") : "", existsSync(index) ? readFileSync(index, "utf8") : "", ), ].sort(); }; export const venueAuthority = function venueAuthority(repoRoot: string): string | null { const bound = boundAuthority(repoRoot); if (bound === null) { return null; } const seated = seatedLetters(repoRoot); if (seated.includes(bound)) { return bound; } return seated[0] ?? bound; }; export const authorityRefusal = function authorityRefusal( repoRoot: string, caller: string, act: string, ): string | null { const holder = venueAuthority(repoRoot); if (holder === null || holder === caller) { return null; } const bound = boundAuthority(repoRoot); const succeeded = bound !== null && bound !== holder; return authorityHeld(act, holder, caller, succeeded ? bound : null); }; const siblingVenues = function siblingVenues(directory: string, repoRoot: string): string[] { const out: string[] = []; for (const root of [directory, resolve(repoRoot, VENUE_ARCHIVE)]) { if (!existsSync(root)) { continue; } for (const entry of readdirSync(root)) { if (entry.endsWith(BLOCKING_SUFFIX)) { out.push(entry); } } } return out; }; const PLANNED_STATE = "planned"; const stateCellOf = function stateCellOf(row: string): string { const cells = row.split(ROW_LEAD); if (cells.length < 3) { return ""; } const cell = (cells.at(-2) ?? "").trim(); if (!cell.startsWith(AGENDA_MARK)) { return cell; } const closes = cell.indexOf(AGENDA_MARK, 1); return closes === -1 ? cell.slice(1) : cell.slice(1, closes); }; interface AgendaRow { readonly invariant: string; readonly ordinal: string; readonly planned: boolean; } export const agendaRows = function agendaRows(agenda: string): AgendaRow[] { const out: AgendaRow[] = []; for (const line of agenda.split("\n")) { const trimmed = line.trim(); if (!trimmed.startsWith(ROW_LEAD)) { continue; } const opens = trimmed.indexOf(AGENDA_MARK); if (opens === -1) { continue; } const closes = trimmed.indexOf(AGENDA_MARK, opens + 1); if (closes === -1) { continue; } const invariant = trimmed.slice(opens + 1, closes).trim(); if (invariant.length === 0 || out.some((row) => row.invariant === invariant)) { continue; } const cell = trimmed.indexOf(ROW_LEAD, 1); const ordinal = cell === -1 ? "" : trimmed.slice(1, cell).trim(); out.push({ invariant, ordinal, planned: stateCellOf(trimmed).startsWith(PLANNED_STATE) }); } return out; }; export interface DisorderedRow { readonly invariant: string; readonly ordinal: string; readonly after: string; } const LETTER_SCALE = 100; export const ordinalValue = function ordinalValue(ordinal: string): number | null { let digits = ""; let cursor = 0; while (cursor < ordinal.length && ordinal.charAt(cursor) >= "0" && ordinal.charAt(cursor) <= "9") { digits += ordinal.charAt(cursor); cursor += 1; } if (digits.length === 0) { return null; } let suffix = 0; for (; cursor < ordinal.length; cursor += 1) { const char = ordinal.charAt(cursor); if (char < "a" || char > "z") { return null; } suffix = suffix * LETTER_SCALE + ((char.codePointAt(0) ?? 0) - ("a".codePointAt(0) ?? 0) + 1); } return Number(digits) + suffix / (LETTER_SCALE * LETTER_SCALE); }; export const disorderedRows = function disorderedRows(agenda: string): DisorderedRow[] { const out: DisorderedRow[] = []; let held: { value: number; invariant: string } | null = null; for (const row of agendaRows(agenda)) { const value = ordinalValue(row.ordinal); if (value === null) { continue; } if (held !== null && value < held.value) { out.push({ after: held.invariant, invariant: row.invariant, ordinal: row.ordinal }); continue; } held = { invariant: row.invariant, value }; } return out; }; export const agendaOrdinalOf = function agendaOrdinalOf(agenda: string, invariant: string): string { return agendaRows(agenda).find((row) => row.invariant === invariant)?.ordinal ?? ""; }; export const agendaInvariants = function agendaInvariants(agenda: string): string[] { return agendaRows(agenda).map((row) => row.invariant); }; export const plannedInvariants = function plannedInvariants(agenda: string): string[] { return agendaRows(agenda) .filter((row) => row.planned) .map((row) => row.invariant); }; export const admissibleInvariants = function admissibleInvariants(agenda: string, venues: readonly string[]): string[] { return agendaRows(agenda) .filter((row) => row.planned && !venues.some((venue) => venue.includes(row.invariant))) .map((row) => row.invariant); }; export const nextUnraised = function nextUnraised(agenda: string, venues: readonly string[]): string | null { return admissibleInvariants(agenda, venues)[0] ?? null; }; const RECORD_ABSENT = "—"; const LIST_MARKER = "- "; const GATE_HEADING = "## Gate"; const RECORD_CLOSE = "└─── END AGENT "; const afterLastRecord = function afterLastRecord(lines: readonly string[]): number { for (let index = lines.length - 1; index >= 0; index -= 1) { const trimmed = (lines[index] ?? "").trim(); if (!trimmed.startsWith(RECORD_CLOSE)) { continue; } if (trimmed.includes("<")) { continue; } return index + 1; } return -1; }; export const runRecord = function runRecord(options: { readonly repoRoot: string; readonly target: string; readonly absolute: string; readonly agent: string; }): RaiseOutcome { const onVenue = options.target.endsWith(BLOCKING_SUFFIX); const kind = onVenue ? "venue" : "board"; if (!existsSync(options.absolute)) { return refusal(recordSurfaceMissing(options.target)); } const source = readFileSync(options.absolute, "utf8"); if (source.includes(`AGENT ${options.agent} `)) { return { code: 0, message: recordExists(options.agent, options.target), raised: options.target }; } const templateSlot = onVenue ? "venue_template" : "board_template"; const templatePath = resolve(options.repoRoot, surfacePath(templateSlot)); if (!existsSync(templatePath)) { return refusal(recordTemplateMissing(kind)); } const template = readFileSync(templatePath, "utf8"); const fields = onVenue ? venueFieldsFrom(template) : [...readBoardContract(template).agentFields]; if (fields.length === 0) { return refusal(recordTemplateEmpty(kind)); } const lines = source.split("\n"); const at = onVenue ? lines.findIndex((line) => line.trim().startsWith(GATE_HEADING)) : afterLastRecord(lines); if (at === -1) { return refusal(recordAnchorMissing(options.target)); } const record = [ ...(onVenue ? [] : [""]), `┌─── AGENT ${options.agent} ─── one writer: ${options.agent} · others cite, never edit · anchored EDIT only, never a whole-file WRITE`, `Agent ${options.agent} — ACTIVE`, ...fields.map((field) => ` ${field}:${" ".repeat(Math.max(1, 8 - field.length))}${RECORD_ABSENT}`), `└─── END AGENT ${options.agent}`, ...(onVenue ? [""] : []), ]; const written = [...lines.slice(0, at), ...record, ...lines.slice(at)].join("\n"); const witness = readFileSync(options.absolute, "utf8"); if (witness !== source) { return { code: 2, message: recordContended(options.target), raised: null }; } writeFileSync(options.absolute, written, "utf8"); return { code: 0, message: recordAdded(options.agent, options.target, fields), raised: options.target }; }; const successorPath = function successorPath( absolute: string, predecessor: string, invariant: string, ): { name: string; path: string } { const directory = dirname(absolute); const found = existsSync(directory) ? readdirSync(directory).find( (entry) => entry.startsWith(`${invariant}.`) && entry.endsWith(BLOCKING_SUFFIX) && entry !== basename(predecessor), ) : undefined; const name = found ?? `${invariant}${BLOCKING_SUFFIX}`; return { name, path: resolve(directory, name) }; }; const SUCCESSOR_FIELD = "SUCCESSOR:"; export const runSuccessor = function runSuccessor(options: { readonly target: string; readonly absolute: string; readonly invariant: string; }): RaiseOutcome { if (!options.target.endsWith(BLOCKING_SUFFIX)) { return refusal(SUCCESSOR_NOT_VENUE); } if (!existsSync(options.absolute)) { return refusal(successorVenueMissing(options.target)); } const agendaPath = resolve(projectRoot(), AGENDA); const planned = plannedInvariants(existsSync(agendaPath) ? readFileSync(agendaPath, "utf8") : ""); if (!planned.includes(options.invariant)) { return refusal(successorNotPlanned(options.invariant)); } const before = readFileSync(options.absolute, "utf8"); const fenced = fencedFlags(before); const lines = before.split("\n"); let at = -1; for (let index = 0; index < lines.length; index += 1) { if (fenced[index] === true) { continue; } if (!(lines[index] ?? "").trim().startsWith(SUCCESSOR_FIELD)) { continue; } at = index; break; } const declaration = `${SUCCESSOR_FIELD} ${options.invariant}`; let written: string; if (at === -1) { let close = -1; let inside = false; for (let index = 0; index < lines.length; index += 1) { const trimmed = (lines[index] ?? "").trim(); if (trimmed.startsWith("## ") && trimmed.includes("SUCCESSOR")) { inside = true; continue; } if (inside && trimmed.startsWith("## ")) { break; } if (inside && fenced[index] === true) { close = index; } } if (close === -1) { return refusal(successorSectionMissing(options.target)); } written = [...lines.slice(0, close + 1), "", declaration, ...lines.slice(close + 1)].join("\n"); } else { written = [...lines.slice(0, at), declaration, ...lines.slice(at + 1)].join("\n"); } 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 runDefer = function runDefer(options: { readonly repoRoot: string; readonly target: string; readonly absolute: string; readonly clause: string; readonly receiver: string; }): RaiseOutcome { if (!options.target.endsWith(BLOCKING_SUFFIX)) { return refusal(DEFER_NOT_VENUE); } if (!existsSync(options.absolute)) { return refusal(deferVenueMissing(options.target)); } if (options.clause === RECORD_ABSENT) { const held = readFileSync(options.absolute, "utf8"); const section = sectionBound(held, DEFERRED_SECTION); if (section === null) { return refusal(deferredSectionMissing(options.target, DEFERRED_SECTION)); } const standing = clauseLines(held, DEFERRED_SECTION); if (standing.length > 0) { return refusal(defersAlready(options.target, standing.length)); } const lines = held.split("\n"); const marked = [ ...lines.slice(0, section.to), `${LIST_MARKER}${RECORD_ABSENT}`, ...lines.slice(section.to), ].join("\n"); const witnessed = readFileSync(options.absolute, "utf8"); if (witnessed !== held) { return refusal(markerContended(options.target)); } writeFileSync(options.absolute, marked, "utf8"); return { code: 0, message: defersNothing(options.target), raised: null }; } if (options.receiver === options.clause) { return refusal(CLAUSE_IS_RECEIVER); } const deferRoot = dirname(options.absolute); const siblingVenues = readdirSync(deferRoot, { withFileTypes: true }) .filter((entry) => entry.isFile() && entry.name.endsWith(BLOCKING_SUFFIX)) .map((entry) => entry.name); const deferBoard = resolve(options.repoRoot, surfacePath("board")); const deferIndex = resolve(options.repoRoot, surfacePath("agent_index")); const deferAgenda = resolve(options.repoRoot, AGENDA); if ( !receiverResolves( options.receiver, existsSync(deferBoard) ? readFileSync(deferBoard, "utf8") : "", existsSync(deferIndex) ? readFileSync(deferIndex, "utf8") : "", siblingVenues, plannedInvariants(existsSync(deferAgenda) ? readFileSync(deferAgenda, "utf8") : ""), ) ) { return refusal(receiverUnknown(options.receiver)); } const before = readFileSync(options.absolute, "utf8"); const bound = sectionBound(before, DEFERRED_SECTION); if (bound === null) { return refusal(deferredSectionMissing(options.target, DEFERRED_SECTION)); } for (const { clause } of clauseLines(before, DEFERRED_SECTION)) { if (clause !== options.clause) { continue; } return refusal(clauseTaken(options.target, options.clause)); } const lines = before.split("\n"); let at = bound.from; for (let index = bound.from + 1; index < bound.to && index < lines.length; index += 1) { if ((lines[index] ?? "").trim().startsWith("- ")) { at = index; } } if (at === bound.from) { at = bound.to - 1; } const written = [ ...lines.slice(0, at + 1), `- ${options.clause} ${DEFERRAL_ARROW} ${options.receiver}`, ...lines.slice(at + 1), ].join("\n"); const witness = readFileSync(options.absolute, "utf8"); if (witness !== before) { return refusal(markerContended(options.target)); } writeFileSync(options.absolute, written, "utf8"); return { code: 0, message: clauseDeferred(options.clause, options.receiver, options.target), raised: options.clause, }; }; export const runRetract = function runRetract(options: { readonly target: string; readonly absolute: string; readonly clause: string; }): RaiseOutcome { if (!options.target.endsWith(BLOCKING_SUFFIX)) { return refusal(RETRACT_NOT_VENUE); } if (!existsSync(options.absolute)) { return refusal(retractVenueMissing(options.target)); } const before = readFileSync(options.absolute, "utf8"); const bound = sectionBound(before, DEFERRED_SECTION); if (bound === null) { return refusal(retractSectionMissing(options.target, DEFERRED_SECTION)); } const lines = before.split("\n"); let at = -1; for (let index = bound.from + 1; index < bound.to && index < lines.length; index += 1) { const trimmed = (lines[index] ?? "").trim(); if (!trimmed.startsWith("- ")) { continue; } const arrow = trimmed.indexOf(DEFERRAL_ARROW); const clause = (arrow === -1 ? trimmed.slice(2) : trimmed.slice(2, arrow)).trim(); if (clause === options.clause) { at = index; } } if (at === -1) { return refusal(retractClauseMissing(options.target, options.clause)); } const written = [...lines.slice(0, at), ...lines.slice(at + 1)].join("\n"); const witness = readFileSync(options.absolute, "utf8"); if (witness !== before) { return refusal(markerContended(options.target)); } writeFileSync(options.absolute, written, "utf8"); return { code: 0, message: clauseRetracted(options.clause, options.target), raised: options.clause }; }; export const runArrive = function runArrive(options: { readonly predecessor: string; readonly absolute: string; }): RaiseOutcome { if (!options.predecessor.endsWith(BLOCKING_SUFFIX)) { return refusal(ARRIVE_NOT_VENUE); } if (!existsSync(options.absolute)) { return refusal(arrivePredecessorMissing(options.predecessor)); } const source = readFileSync(options.absolute, "utf8"); const invariant = declaredSuccessor(source); if (invariant.length === 0) { return refusal(ARRIVE_NO_SUCCESSOR); } const { name, path } = successorPath(options.absolute, options.predecessor, invariant); if (!existsSync(path)) { return refusal(arriveSuccessorUnraised(name)); } const witnessed = readFileSync(path, "utf8"); if (!witnessed.includes(INHERITED_BANNER)) { return refusal(arriveNoInheritedSection(name)); } const addressed = unarrivedDeferrals(source, witnessed); const missing = addressed.filter((entry) => addressesSuccessor(entry.receiver, name)); const elsewhere = addressed.filter((entry) => !addressesSuccessor(entry.receiver, name)); if (missing.length === 0 && elsewhere.length > 0) { return { code: 0, message: arrivalHeld( name, elsewhere.map((entry) => `${entry.clause} → ${entry.receiver}`), ), raised: null, }; } if (missing.length === 0) { return { code: 0, message: arrivalSettled(options.predecessor, name), raised: name }; } const lines = witnessed.split("\n"); let last = -1; let inside = false; for (let index = 0; index < lines.length; index += 1) { const trimmed = (lines[index] ?? "").trim(); if (trimmed.startsWith(INHERITED_BANNER)) { inside = true; continue; } if (!inside) { continue; } if (trimmed.startsWith("- ")) { last = index; } if (last !== -1 && trimmed.startsWith("═") && !trimmed.startsWith(INHERITED_BANNER)) { break; } } if (last === -1) { return refusal(arriveSectionEmpty(name)); } const carried = missing.map((entry) => `- ${entry.clause} ${DEFERRAL_ARROW} ${entry.receiver}`); lines.splice(last + 1, 0, ...carried); const current = readFileSync(path, "utf8"); if (current !== witnessed) { return { code: 2, message: arrivalContended(name), raised: null }; } writeFileSync(path, lines.join("\n"), "utf8"); return { code: 0, message: arrived( missing.length, options.predecessor, name, missing.map((entry) => entry.clause), ), raised: name, }; }; export const NOT_READ_FIELD = "NOT-READ:"; const AWAITING_FIELD = "READ AND AWAITING:"; export const rosterFor = function rosterFor( repoRoot: string, marked: readonly string[], ): { unread: string[]; read: string[] } { const board = resolve(repoRoot, surfacePath("board")); const index = resolve(repoRoot, surfacePath("agent_index")); const seats = activeSeats( existsSync(board) ? readFileSync(board, "utf8") : "", existsSync(index) ? readFileSync(index, "utf8") : "", ); const read = seats.filter((letter) => marked.includes(letter)); return { read, unread: seats.filter((letter) => !marked.includes(letter)) }; }; export const runRoster = function runRoster(options: { readonly repoRoot: string; readonly name: string; }): RaiseOutcome { if (!options.name.endsWith(BLOCKING_SUFFIX)) { return refusal(rosterNotVenue(options.name)); } const absolute = resolve(options.repoRoot, surfacePrefix(), options.name); if (!existsSync(absolute)) { return refusal(rosterVenueElsewhere(options.name)); } const before = readFileSync(absolute, "utf8"); const lines = before.split("\n"); const unreadAt = lines.findIndex((line) => line.startsWith(NOT_READ_FIELD)); const readAt = lines.findIndex((line) => line.startsWith(AWAITING_FIELD)); if (unreadAt === -1 || readAt === -1) { return refusal(rosterLinesMissing(options.name)); } const marked = letters(lines[readAt] ?? "", AWAITING_FIELD); const roster = rosterFor(options.repoRoot, marked); const seated = new Set([...roster.unread, ...roster.read]); const standing = letters(lines[unreadAt] ?? "", NOT_READ_FIELD).filter((letter) => seated.has(letter)); const regressing = roster.unread.filter((letter) => !standing.includes(letter)); if (standing.length > 0 && regressing.length > 0) { return refusal(rosterRegression(options.name, regressing)); } const written = [...lines]; written[unreadAt] = rosterLine(NOT_READ_FIELD, roster.unread); written[readAt] = rosterLine(AWAITING_FIELD, roster.read); const composed = written.join("\n"); if (composed === before) { return { code: 0, message: rosterUnchanged(options.name), raised: options.name }; } const witness = readFileSync(absolute, "utf8"); if (witness !== before) { return refusal(rosterContended(options.name)); } writeFileSync(absolute, composed, "utf8"); return { code: 0, message: rosterResolved( options.name, roster.unread.join(", ") || RECORD_ABSENT, roster.read.join(", ") || RECORD_ABSENT, ), raised: options.name, }; }; export const runInherit = function runInherit(options: { readonly repoRoot: string; readonly name: string; }): RaiseOutcome { if (!options.name.endsWith(BLOCKING_SUFFIX)) { return refusal(inheritNotVenue(options.name)); } const venueRoot = resolve(options.repoRoot, surfacePrefix()); const absolute = resolve(venueRoot, options.name); if (!existsSync(absolute)) { return refusal(inheritVenueElsewhere(options.name)); } const invariant = venueInvariant(options.name); const deferring = deferringVenues(venueRoot, options.repoRoot, invariant); const before = readFileSync(absolute, "utf8"); const lines = before.split("\n"); const opens = lines.findIndex((line) => line.startsWith(INHERITED_BANNER)); if (opens === -1) { return refusal(inheritSectionMissing(options.name)); } let closes = opens + 1; while (closes < lines.length && !(lines[closes] ?? "").startsWith(PROTOCOL_BANNER)) { closes += 1; } if (closes >= lines.length) { return refusal(inheritSectionUnbounded(options.name)); } const block = deferring.clauses.length === 0 ? unclaimedBlock(invariant) : inheritedBlock(deferring.names.join(", "), deferring.clauses); const written = [...lines.slice(0, opens), ...block.split("\n").slice(0, -1), ...lines.slice(closes)].join("\n"); if (written === before) { return { code: 0, message: inheritUnchanged(options.name), raised: options.name }; } const witness = readFileSync(absolute, "utf8"); if (witness !== before) { return refusal(inheritContended(options.name)); } writeFileSync(absolute, written, "utf8"); return { code: 0, message: inheritUpdated( options.name, deferring.clauses.length, deferring.names.length === 0 ? "no venue deferring here" : deferring.names.join(", "), ), raised: options.name, }; }; export const runRetire = function runRetire(options: { readonly repoRoot: string; readonly name: string; readonly declares: string; readonly live: readonly string[]; readonly citedBy: readonly string[]; readonly heal: boolean; }): RaiseOutcome { const planning = surfacePath("planning"); const source = `${planning}/${options.name}`; const absolute = resolve(options.repoRoot, source); if (!options.name.endsWith(CHECKLIST_CONCERN)) { return refusal(retireNotPlanning(options.name)); } if (!existsSync(absolute)) { return refusal(retireMissing(source)); } if (options.declares.length === 0) { return refusal(retireNothingDistributed(source)); } if (options.live.some((venue) => venue.endsWith(options.declares))) { return refusal(retireVenueOpen(source, options.declares)); } const pinning = options.citedBy.filter((citer) => citer.length > 0); const before = readFileSync(absolute, "utf8"); const lines = before.split("\n"); const at = lines.findIndex((line) => line.trim().startsWith(DISTRIBUTES_FIELD)); if (at === -1) { return refusal(retireLineMissing(source)); } const retiredLine = `${lines[at]?.slice(0, (lines[at] ?? "").indexOf(DISTRIBUTES_FIELD))}RETIRED: this surface distributed a venue ` + `that has since converged, and the declaration is removed so no planning surface names a venue outside the ` + `active tree. THE FILE STAYS AT THIS PATH PERMANENTLY${pinning.length > 0 ? ` because ${String(pinning.length)} pathed citation(s) in FROZEN surfaces name it: ${pinning.join(", ")}` : " because a planning surface is named by its own venue's distribution section, which freezes when that venue archives"}` + `. A PATHED CITATION FROM A FROZEN SURFACE PINS ITS TARGET AGAINST THE TARGET'S OWN DECLARED LIFETIME: no party ` + `may edit a frozen file, so the update half of a move is unavailable and a move degrades into a disconnection — ` + `and nothing reports it, because the reference walk skips an immutable path as a citation SOURCE, correctly, ` + `since a finding on a surface nobody may repair is a report nobody can drain. So retirement is this EDIT and ` + `never a move or a deletion: the spent finding clears, the file stays, and every pointer keeps resolving.`; const written = [...lines.slice(0, at), retiredLine, ...lines.slice(at + 1)].join("\n"); if (!options.heal) { return { code: 0, message: retirePreview(source, options.declares), raised: source }; } if (readFileSync(absolute, "utf8") !== before) { return refusal(retireContended(source)); } writeFileSync(absolute, written, "utf8"); return { code: 0, message: retired(source), raised: source }; }; export const runRelocate = function runRelocate(options: { readonly repoRoot: string; readonly name: string; }): RaiseOutcome { if (!options.name.endsWith(BLOCKING_SUFFIX)) { return refusal(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 refusal(RELOCATE_ROOT_MISSING); } if (existsSync(destination)) { return refusal(relocateDestinationTaken(options.name)); } if (!existsSync(stray)) { return refusal(relocateSourceMissing(options.name)); } const carried = readFileSync(stray, "utf8"); writeFileSync(destination, carried, "utf8"); const landed = readFileSync(destination, "utf8"); if (landed !== carried) { return refusal(relocateMismatch(options.name)); } rmSync(stray); return { code: 0, message: relocated(options.name), raised: options.name }; }; const RECORD_OPEN = "┌─── AGENT "; const RECORD_CLOSE_MARKER = "└─── END AGENT "; const LETTER_SLOT = ""; const STATE_SLOT = ""; const PLACEHOLDER_OPEN = "<"; export const recordSpecimen = function recordSpecimen(template: string): string[] { const lines = template.split("\n"); const opens = lines.findIndex((line) => line.trimStart().startsWith(RECORD_OPEN) && line.includes(LETTER_SLOT)); if (opens === -1) { return []; } const open = lines[opens] ?? ""; const indent = open.slice(0, open.length - open.trimStart().length); let closes = opens; while (closes < lines.length && !(lines[closes] ?? "").trimStart().startsWith(RECORD_CLOSE_MARKER)) { closes += 1; } if (closes >= lines.length) { return []; } return lines.slice(opens, closes + 1).map((line) => (line.startsWith(indent) ? line.slice(indent.length) : line)); }; export const seatRecord = function seatRecord(specimen: readonly string[], letter: string): string[] { return specimen.map((line) => { const named = line.split(LETTER_SLOT).join(letter).split(STATE_SLOT).join("ACTIVE"); const cut = named.indexOf(PLACEHOLDER_OPEN); return cut === -1 ? named : named.slice(0, cut).trimEnd(); }); }; export const runRaise = function runRaise(options: { readonly repoRoot: string; readonly declared: string | null; readonly seats: readonly string[]; }): RaiseOutcome { const agendaPath = resolve(options.repoRoot, AGENDA); if (!existsSync(agendaPath)) { return refusal(RAISE_AGENDA_MISSING); } const agenda = readFileSync(agendaPath, "utf8"); const venueRoot = resolve(options.repoRoot, surfacePrefix()); const admissible = admissibleInvariants(agenda, siblingVenues(venueRoot, options.repoRoot)); if (admissible.length === 0) { return refusal(RAISE_NOTHING_ADMISSIBLE); } const named = options.declared !== null && options.declared.length > 0 ? options.declared : null; if (named !== null && !admissible.includes(named)) { return refusal(raiseNotAdmissible(named, admissible)); } const invariant = named ?? admissible[0] ?? ""; const undeclared = readdirSync(venueRoot, { withFileTypes: true }) .filter((entry) => entry.isFile() && entry.name.endsWith(BLOCKING_SUFFIX)) .filter((entry) => declaredSuccessor(readFileSync(resolve(venueRoot, entry.name), "utf8")).length === 0) .map((entry) => entry.name); if (undeclared.length > 0) { return refusal(raiseSuccessorUndeclared(undeclared)); } const templatePath = resolve(options.repoRoot, surfacePath("venue_template")); if (!existsSync(templatePath)) { return refusal(RAISE_TEMPLATE_MISSING); } const ordinal = agendaOrdinalOf(agenda, invariant); if (ordinal.length === 0) { return refusal(raiseOrdinalMissing(invariant)); } const name = `${invariant}.${ordinal}${BLOCKING_SUFFIX}`; const destination = resolve(venueRoot, name); if (existsSync(destination)) { return refusal(raiseDestinationTaken(name)); } const deferring = deferringVenues(venueRoot, options.repoRoot, invariant); const { clauses } = deferring; const template = readFileSync(templatePath, "utf8"); if (!template.includes(PROTOCOL_BANNER)) { return refusal(RAISE_BANNER_MISSING); } const inherited = clauses.length === 0 ? unclaimedBlock(invariant) : inheritedBlock(deferring.names.join(", "), clauses); const composed = template.replace(PROTOCOL_BANNER, `${inherited}${PROTOCOL_BANNER}`); const participants = options.seats.length > 0 ? [...options.seats] : seatedLetters(options.repoRoot); const indexPath = resolve(options.repoRoot, AGENT_INDEX); const bound = indexedLetters(existsSync(indexPath) ? readFileSync(indexPath, "utf8") : "").letters; const unbound = participants.filter((letter) => !bound.has(letter)); if (unbound.length > 0) { return refusal(raiseUnbound(unbound)); } const specimen = recordSpecimen(template); if (specimen.length === 0) { return refusal(RAISE_SPECIMEN_MISSING); } const seated = participants.flatMap((letter) => [...seatRecord(specimen, letter), ""]); const raisedVenue = composed .split("\n") .flatMap((line) => { if (line.startsWith(SIGN_OFF_BANNER)) { return [...seated, line]; } return [line]; }) .map((line) => { if (line.startsWith(NOT_READ_FIELD)) { return rosterLine(NOT_READ_FIELD, participants); } if (line.startsWith(AWAITING_FIELD)) { return rosterLine(AWAITING_FIELD, []); } return line; }) .join("\n"); writeFileSync(destination, raisedVenue, "utf8"); return { code: 0, message: raisedMessage( name, participants, clauses.length, deferring.names.length === 0 ? "no venue deferring here" : deferring.names.join(", "), ), raised: name, }; };