/** * Strict validation for every record on the control plane. * * Everything parsed here is worker-controlled or disk-resident and therefore * untrusted: a control file can be truncated by a crash, hand-edited, written by a * different pi version, or forged. Two rules apply to all of it (E37): * * - A record is validated *before* it is allowed to cause a signal, a status * mutation, a session abort, or a path. * - Validation failure never throws into a caller and never counts as consent. * An invalid record is quarantined and reported, not acted on. * * Centralised because the same records are read from three places (the worker * inbox, `agi_steer`, and the supervisor) and a validator that only some readers * use is not a validator. */ import * as path from "node:path"; import { MAX_STEER_BYTES } from "../worker/status.ts"; import type { RunPaths } from "../worker/status.ts"; export type ControlKind = "interrupt" | "stop"; /** R-CTRL-1 marker payload, after validation. */ export interface ControlMarker { action: ControlKind; ts: string; source: "orchestrator" | "user"; /** R-TOOL-18: required for `stop`, absent for `interrupt`. */ reason?: string; } export interface SteerRequestRecord { reqId: string; seq: string; ts: string; message: string; source: "orchestrator" | "user"; /** False queues into the active turn; true aborts it before delivery. */ interrupt: boolean; } export interface SteerCapabilityRecord { pid: number; readyAt: string; supported: boolean; } export type Validated = { ok: true; value: T } | { ok: false; reason: string }; function isIsoTimestamp(value: unknown): value is string { if (typeof value !== "string" || value.length === 0) return false; const parsed = Date.parse(value); return !Number.isNaN(parsed); } function objectOf(raw: string): Validated> { let parsed: unknown; try { parsed = JSON.parse(raw); } catch (error) { return { ok: false, reason: `not valid JSON: ${(error as Error).message}` }; } if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) { return { ok: false, reason: "not a JSON object" }; } return { ok: true, value: parsed as Record }; } const MARKER_FIELDS = new Set(["action", "ts", "source", "reason"]); /** * A stop marker aborts the session and latches a terminal state. Consuming it by * *existence* meant a single stray byte in `control/stop.json` terminally stopped a * worker, so the payload is parsed and cross-checked against the file it came from. */ export function parseControlMarker(raw: string, expected: ControlKind): Validated { const parsed = objectOf(raw); if (!parsed.ok) return { ok: false, reason: `${expected} marker is ${parsed.reason}` }; const record = parsed.value; const unknown = Object.keys(record).filter((key) => !MARKER_FIELDS.has(key)); if (unknown.length > 0) return { ok: false, reason: `${expected} marker has unknown field(s): ${unknown.join(", ")}` }; if (record.action !== expected) { return { ok: false, reason: `${expected} marker declares action '${String(record.action)}'` }; } if (!isIsoTimestamp(record.ts)) return { ok: false, reason: `${expected} marker has an invalid ts` }; if (record.source !== "orchestrator" && record.source !== "user") { return { ok: false, reason: `${expected} marker has an invalid source '${String(record.source)}'` }; } if (expected === "stop") { // R-TOOL-18: the reason is the durable record of why this worker was stopped. if (typeof record.reason !== "string" || record.reason.trim().length === 0) { return { ok: false, reason: "stop marker is missing its required reason" }; } } else if (record.reason !== undefined && typeof record.reason !== "string") { return { ok: false, reason: "interrupt marker reason must be a string when present" }; } return { ok: true, value: { action: expected, ts: record.ts, source: record.source, ...(typeof record.reason === "string" ? { reason: record.reason } : {}), }, }; } const REQUEST_FIELDS = new Set(["reqId", "seq", "ts", "message", "source", "interrupt"]); /** * A reqId becomes part of an ack filename (base64url-encoded) and appears in tool * output, so it is constrained to a safe opaque token. The *format* is deliberately * not pinned to `newControlRequestId`'s shape: what matters for R-CTRL-8 ordering is * the filename cross-check below, not who minted the id. */ const REQ_ID_PATTERN = /^[A-Za-z0-9_.:-]{1,128}$/; export function steerFileName(record: Pick): string { return `${record.seq}-${Buffer.from(record.reqId).toString("base64url")}.json`; } /** * Validate a steer record *and* the filename it was stored under. * * The filename carries the R-CTRL-8 ordering, so a record whose `seq` disagrees * with its own name can reorder delivery; and the worker-side reader is the last * place that can still enforce R-TOOL-16's size cap before an unbounded message is * formatted and pushed into the runtime. */ export function parseSteerRequest(raw: string, file?: string): Validated { const parsed = objectOf(raw); if (!parsed.ok) return { ok: false, reason: `steer request is ${parsed.reason}` }; const record = parsed.value; const unknown = Object.keys(record).filter((key) => !REQUEST_FIELDS.has(key)); if (unknown.length > 0) return { ok: false, reason: `steer request has unknown field(s): ${unknown.join(", ")}` }; if (typeof record.reqId !== "string" || !REQ_ID_PATTERN.test(record.reqId)) { return { ok: false, reason: `steer request has an invalid reqId '${String(record.reqId)}'` }; } if (typeof record.seq !== "string" || !/^[0-9]{13}$/.test(record.seq)) { return { ok: false, reason: `steer request has an invalid seq '${String(record.seq)}'` }; } if (!isIsoTimestamp(record.ts)) return { ok: false, reason: "steer request has an invalid ts" }; if (typeof record.message !== "string" || record.message.length === 0) { return { ok: false, reason: "steer request message must be a non-empty string" }; } const bytes = Buffer.byteLength(record.message, "utf8"); if (bytes > MAX_STEER_BYTES) { return { ok: false, reason: `steer request message is ${bytes} bytes; maximum is ${MAX_STEER_BYTES}` }; } if (record.source !== "orchestrator" && record.source !== "user") { return { ok: false, reason: `steer request has an invalid source '${String(record.source)}'` }; } // Requests written before non-interrupting steering existed always meant the // aborting behavior, so absence selects the legacy behavior rather than the new // tool default. if (record.interrupt !== undefined && typeof record.interrupt !== "boolean") { return { ok: false, reason: `steer request has an invalid interrupt flag '${String(record.interrupt)}'` }; } const value: SteerRequestRecord = { reqId: record.reqId, seq: record.seq, ts: record.ts, message: record.message, source: record.source, interrupt: record.interrupt === undefined ? true : record.interrupt, }; if (file !== undefined) { const expected = steerFileName(value); if (path.basename(file) !== expected) { return { ok: false, reason: `steer request filename '${path.basename(file)}' is not the canonical '${expected}' for its contents` }; } } return { ok: true, value }; } const CAPABILITY_FIELDS = new Set(["pid", "readyAt", "supported"]); /** * R-CTRL-10. `supported: false` is the one record that can *destroy* a queued * steer, so it is only believed when it demonstrably describes the live worker. * A stale record from a previous run of the same task, or a forged one naming * someone else's pid, must read as "not ready", never as "can never work". */ export function parseSteerCapability(raw: string): Validated { const parsed = objectOf(raw); if (!parsed.ok) return { ok: false, reason: `steer capability is ${parsed.reason}` }; const record = parsed.value; const unknown = Object.keys(record).filter((key) => !CAPABILITY_FIELDS.has(key)); if (unknown.length > 0) return { ok: false, reason: `steer capability has unknown field(s): ${unknown.join(", ")}` }; if (typeof record.pid !== "number" || !Number.isInteger(record.pid) || record.pid <= 0) { return { ok: false, reason: `steer capability has an invalid pid '${String(record.pid)}'` }; } if (!isIsoTimestamp(record.readyAt)) return { ok: false, reason: "steer capability has an invalid readyAt" }; if (typeof record.supported !== "boolean") return { ok: false, reason: "steer capability supported must be a boolean" }; return { ok: true, value: { pid: record.pid, readyAt: record.readyAt, supported: record.supported } }; } /** * Where an invalid control record goes. It is moved rather than deleted so the * bytes survive for diagnosis, and moved rather than left in place so the same * malformed file cannot be re-examined on every 250 ms poll forever. */ export function quarantinePath(paths: RunPaths, file: string, now = Date.now()): string { return path.join(paths.control, "quarantine", `${now}-${path.basename(file)}`); }