Intelligence
Artifacts
Browse the repository, read documents, and manage the governance folders. Source, runtime, and infrastructure are read-only.
Repository
repositories/aaf-holdings/hq01/lib/missions/work-orders.ts
5.2 KB
import fs from "node:fs";
import path from "node:path";
import { missionDir, readMission } from "./store";
import { getObjective } from "./objectives";
import type {
CreateWorkOrderInput,
ExecChainEvent,
ExecChainEventType,
WorkOrder,
WorkOrderStatus,
WorkOrderView,
} from "./types";
/**
* Work Orders (PASS M3) — translate an objective into executable organizational
* work. Stored flat under each mission: MISSION-xxxxx/work-orders/WORKORDER-...,
* linked to their objective by id. Not assignments, not workers.
*/
export class WorkOrderError extends Error {
constructor(
message: string,
readonly status = 400,
) {
super(message);
this.name = "WorkOrderError";
}
}
const WID_RE = /^WORKORDER-\d{6}$/;
function nowIso(): string {
return new Date().toISOString();
}
function workOrdersDir(mid: string): string {
return path.join(missionDir(mid), "work-orders");
}
function woDir(mid: string, wid: string): string {
return path.join(workOrdersDir(mid), wid);
}
function woJson(mid: string, wid: string): string {
return path.join(woDir(mid, wid), "work-order.json");
}
function woHistory(mid: string, wid: string): string {
return path.join(woDir(mid, wid), "history", "log.jsonl");
}
function writeJsonAtomic(file: string, data: unknown): void {
fs.mkdirSync(path.dirname(file), { recursive: true });
const tmp = `${file}.tmp`;
fs.writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n", "utf8");
fs.renameSync(tmp, file);
}
function append(
mid: string,
wid: string,
type: ExecChainEventType,
extra: Partial<ExecChainEvent> = {},
): void {
const file = woHistory(mid, wid);
fs.mkdirSync(path.dirname(file), { recursive: true });
const ev: ExecChainEvent = { type, at: nowIso(), ...extra };
fs.appendFileSync(file, JSON.stringify(ev) + "\n", "utf8");
}
function nextId(mid: string): string {
let max = 0;
let entries: fs.Dirent[] = [];
try {
entries = fs.readdirSync(workOrdersDir(mid), { withFileTypes: true });
} catch {
/* none */
}
for (const e of entries) {
if (e.isDirectory() && WID_RE.test(e.name)) {
const n = Number(e.name.slice("WORKORDER-".length));
if (Number.isFinite(n) && n > max) max = n;
}
}
return `WORKORDER-${String(max + 1).padStart(6, "0")}`;
}
export function readWorkOrder(mid: string, wid: string): WorkOrder | null {
if (!WID_RE.test(wid)) return null;
try {
return JSON.parse(fs.readFileSync(woJson(mid, wid), "utf8")) as WorkOrder;
} catch {
return null;
}
}
export function listWorkOrders(mid: string): WorkOrder[] {
let entries: fs.Dirent[];
try {
entries = fs.readdirSync(workOrdersDir(mid), { withFileTypes: true });
} catch {
return [];
}
return entries
.filter((e) => e.isDirectory() && WID_RE.test(e.name))
.map((e) => readWorkOrder(mid, e.name))
.filter((w): w is WorkOrder => Boolean(w))
.sort((a, b) => a.id.localeCompare(b.id));
}
function readHistory(mid: string, wid: string): ExecChainEvent[] {
let raw: string;
try {
raw = fs.readFileSync(woHistory(mid, wid), "utf8");
} catch {
return [];
}
return raw
.split("\n")
.filter(Boolean)
.map((l) => {
try {
return JSON.parse(l) as ExecChainEvent;
} catch {
return null;
}
})
.filter((e): e is ExecChainEvent => Boolean(e));
}
export function getWorkOrderView(mid: string, wid: string): WorkOrderView | null {
const work_order = readWorkOrder(mid, wid);
if (!work_order) return null;
return {
work_order,
history: readHistory(mid, wid).sort((a, b) => b.at.localeCompare(a.at)),
};
}
export function createWorkOrder(
mid: string,
input: CreateWorkOrderInput,
): WorkOrder {
if (!readMission(mid)) throw new WorkOrderError("Mission not found.", 404);
if (!input.objective_id || !getObjective(mid, input.objective_id)) {
throw new WorkOrderError("A valid objective is required.", 400);
}
const title = input.title?.trim();
if (!title) throw new WorkOrderError("Title is required.");
const description = input.description?.trim();
if (!description) throw new WorkOrderError("Description is required.");
const owner = input.owner_executive?.trim();
if (!owner) throw new WorkOrderError("Owner executive is required.");
if (!input.priority) throw new WorkOrderError("Priority is required.");
const id = nextId(mid);
const created = nowIso();
const wo: WorkOrder = {
id,
mission_id: mid,
objective_id: input.objective_id,
title,
description,
owner_executive: owner,
priority: input.priority,
status: "Draft",
created_at: created,
updated_at: created,
};
fs.mkdirSync(path.join(woDir(mid, id), "history"), { recursive: true });
writeJsonAtomic(woJson(mid, id), wo);
append(mid, id, "work_order_created", { detail: title });
return wo;
}
export function setWorkOrderStatus(
mid: string,
wid: string,
to: WorkOrderStatus,
): WorkOrder {
const wo = readWorkOrder(mid, wid);
if (!wo) throw new WorkOrderError("Work order not found.", 404);
const from = wo.status;
const next: WorkOrder = { ...wo, status: to, updated_at: nowIso() };
writeJsonAtomic(woJson(mid, wid), next);
append(mid, wid, "state_changed", {
previous_state: from,
new_state: to,
detail: `${from} → ${to}`,
});
return next;
}
root · /srv/aaf