Intelligence

Artifacts

Browse the repository, read documents, and manage the governance folders. Source, runtime, and infrastructure are read-only.

Repository
README.md
CONSTITUTION_COMPLIANCE_AUDIT_V1.mdREADME.md
repositories/aaf-holdings/hq01/lib/missions/governance.ts
13.2 KB
import fs from "node:fs";
import path from "node:path";
import { missionDir, readMission } from "./store";
import type {
  BlockReason,
  Decision,
  Dependency,
  DependencyStatus,
  DependencyType,
  GovernanceEvent,
  GovernanceEventType,
  Kpi,
  KpiStatus,
  Risk,
  RiskLikelihood,
  RiskSeverity,
  RiskStatus,
  WithHistory,
} from "./types";

/**
 * Governance instruments (PASS M6): Risks, Decisions, Dependencies, KPIs.
 * Each is a mission-owned record with append-only history. Records only — no
 * intelligence, no automation, no automatic transitions. Decisions are
 * immutable (corrections create new decisions).
 */

export class GovernanceError extends Error {
  constructor(
    message: string,
    readonly status = 400,
  ) {
    super(message);
    this.name = "GovernanceError";
  }
}

function nowIso(): string {
  return new Date().toISOString();
}

/** A small filesystem store for one instrument kind under a mission. */
function instrument<T extends { id: string }>(
  kind: string,
  jsonName: string,
  prefix: string,
) {
  const idRe = new RegExp(`^${prefix}-\\d{6}$`);
  const dir = (mid: string) => path.join(missionDir(mid), kind);
  const objDir = (mid: string, id: string) => path.join(dir(mid), id);
  const jsonPath = (mid: string, id: string) => path.join(objDir(mid, id), jsonName);
  const histPath = (mid: string, id: string) =>
    path.join(objDir(mid, id), "history", "log.jsonl");

  function nextId(mid: string): string {
    let max = 0;
    let entries: fs.Dirent[] = [];
    try {
      entries = fs.readdirSync(dir(mid), { withFileTypes: true });
    } catch {
      /* none */
    }
    for (const e of entries) {
      if (e.isDirectory() && idRe.test(e.name)) {
        const n = Number(e.name.slice(prefix.length + 1));
        if (Number.isFinite(n) && n > max) max = n;
      }
    }
    return `${prefix}-${String(max + 1).padStart(6, "0")}`;
  }

  function write(mid: string, obj: T): void {
    const file = jsonPath(mid, obj.id);
    fs.mkdirSync(path.dirname(file), { recursive: true });
    const tmp = `${file}.tmp`;
    fs.writeFileSync(tmp, JSON.stringify(obj, null, 2) + "\n", "utf8");
    fs.renameSync(tmp, file);
  }

  function read(mid: string, id: string): T | null {
    if (!idRe.test(id)) return null;
    try {
      return JSON.parse(fs.readFileSync(jsonPath(mid, id), "utf8")) as T;
    } catch {
      return null;
    }
  }

  function list(mid: string): T[] {
    let entries: fs.Dirent[];
    try {
      entries = fs.readdirSync(dir(mid), { withFileTypes: true });
    } catch {
      return [];
    }
    return entries
      .filter((e) => e.isDirectory() && idRe.test(e.name))
      .map((e) => read(mid, e.name))
      .filter((x): x is T => Boolean(x))
      .sort((a, b) => a.id.localeCompare(b.id));
  }

  function append(
    mid: string,
    id: string,
    type: GovernanceEventType,
    extra: Partial<GovernanceEvent> = {},
  ): void {
    const file = histPath(mid, id);
    fs.mkdirSync(path.dirname(file), { recursive: true });
    const ev: GovernanceEvent = { type, at: nowIso(), ...extra };
    fs.appendFileSync(file, JSON.stringify(ev) + "\n", "utf8");
  }

  function history(mid: string, id: string): GovernanceEvent[] {
    let raw: string;
    try {
      raw = fs.readFileSync(histPath(mid, id), "utf8");
    } catch {
      return [];
    }
    return raw
      .split("\n")
      .filter(Boolean)
      .map((l) => {
        try {
          return JSON.parse(l) as GovernanceEvent;
        } catch {
          return null;
        }
      })
      .filter((e): e is GovernanceEvent => Boolean(e));
  }

  function listWithHistory(mid: string): WithHistory<T>[] {
    return list(mid).map((item) => ({
      item,
      history: history(mid, item.id).sort((a, b) => b.at.localeCompare(a.at)),
    }));
  }

  return { nextId, write, read, list, append, history, listWithHistory };
}

const risks = instrument<Risk>("risks", "risk.json", "RISK");
const decisions = instrument<Decision>("decisions", "decision.json", "DECISION");
const dependencies = instrument<Dependency>("dependencies", "dependency.json", "DEPENDENCY");
const kpis = instrument<Kpi>("kpis", "kpi.json", "KPI");

function ensureMission(mid: string): void {
  if (!readMission(mid)) throw new GovernanceError("Mission not found.", 404);
}

// --- Risks -----------------------------------------------------------------

export interface CreateRiskInput {
  title: string;
  description: string;
  severity: RiskSeverity;
  likelihood: RiskLikelihood;
  owner_executive?: string;
  mitigation?: string;
}
export interface UpdateRiskInput {
  title?: string;
  description?: string;
  severity?: RiskSeverity;
  likelihood?: RiskLikelihood;
  owner_executive?: string | null;
  status?: RiskStatus;
  mitigation?: string;
}

export function createRisk(mid: string, input: CreateRiskInput): Risk {
  ensureMission(mid);
  if (!input.title?.trim()) throw new GovernanceError("Title is required.");
  const id = risks.nextId(mid);
  const created = nowIso();
  const risk: Risk = {
    id,
    mission_id: mid,
    title: input.title.trim(),
    description: input.description?.trim() || "",
    severity: input.severity || "Medium",
    likelihood: input.likelihood || "Medium",
    owner_executive: input.owner_executive?.trim() || null,
    status: "Open",
    mitigation: input.mitigation?.trim() || "",
    created_at: created,
    updated_at: created,
  };
  risks.write(mid, risk);
  risks.append(mid, id, "created", { detail: risk.title });
  return risk;
}

export function updateRisk(mid: string, id: string, patch: UpdateRiskInput): Risk {
  const risk = risks.read(mid, id);
  if (!risk) throw new GovernanceError("Risk not found.", 404);
  const next: Risk = { ...risk };
  const prevStatus = risk.status;
  if (patch.title !== undefined) next.title = patch.title.trim();
  if (patch.description !== undefined) next.description = patch.description.trim();
  if (patch.severity !== undefined) next.severity = patch.severity;
  if (patch.likelihood !== undefined) next.likelihood = patch.likelihood;
  if (patch.mitigation !== undefined) next.mitigation = patch.mitigation.trim();
  if (patch.owner_executive !== undefined) next.owner_executive = patch.owner_executive?.trim() || null;
  if (patch.status !== undefined) next.status = patch.status;
  next.updated_at = nowIso();
  risks.write(mid, next);
  risks.append(mid, id, "updated");
  if (patch.status !== undefined && patch.status !== prevStatus) {
    risks.append(mid, id, "state_changed", { previous: prevStatus, next: patch.status });
  }
  return next;
}

export const listRisks = (mid: string) => risks.list(mid);
export const listRisksWithHistory = (mid: string) => risks.listWithHistory(mid);

// --- Decisions (immutable) -------------------------------------------------

export interface CreateDecisionInput {
  title: string;
  description?: string;
  decision: string;
  rationale?: string;
  alternatives_considered?: string[];
  approved_by: string;
}

export function createDecision(mid: string, input: CreateDecisionInput): Decision {
  ensureMission(mid);
  if (!input.title?.trim()) throw new GovernanceError("Title is required.");
  if (!input.decision?.trim()) throw new GovernanceError("Decision is required.");
  if (!input.approved_by?.trim()) throw new GovernanceError("Approved By is required.");
  const id = decisions.nextId(mid);
  const decision: Decision = {
    id,
    mission_id: mid,
    title: input.title.trim(),
    description: input.description?.trim() || "",
    decision: input.decision.trim(),
    rationale: input.rationale?.trim() || "",
    alternatives_considered: (input.alternatives_considered ?? [])
      .map((a) => a.trim())
      .filter(Boolean),
    approved_by: input.approved_by.trim(),
    created_at: nowIso(),
  };
  decisions.write(mid, decision);
  decisions.append(mid, id, "created", { detail: decision.title });
  return decision;
}

export const listDecisions = (mid: string) => decisions.list(mid);
export const listDecisionsWithHistory = (mid: string) => decisions.listWithHistory(mid);

// --- Dependencies ----------------------------------------------------------

export interface CreateDependencyInput {
  depends_on: string;
  type: DependencyType;
  description?: string;
  blocking?: boolean;
}
export interface UpdateDependencyInput {
  depends_on?: string;
  type?: DependencyType;
  description?: string;
  blocking?: boolean;
  status?: DependencyStatus;
}

export function createDependency(mid: string, input: CreateDependencyInput): Dependency {
  ensureMission(mid);
  if (!input.depends_on?.trim()) throw new GovernanceError("Depends On is required.");
  const id = dependencies.nextId(mid);
  const created = nowIso();
  const dep: Dependency = {
    id,
    mission_id: mid,
    depends_on: input.depends_on.trim(),
    type: input.type || "External",
    status: "Pending",
    blocking: Boolean(input.blocking),
    description: input.description?.trim() || "",
    created_at: created,
    updated_at: created,
  };
  dependencies.write(mid, dep);
  dependencies.append(mid, id, "created", { detail: `${dep.type}: ${dep.depends_on}` });
  return dep;
}

export function updateDependency(mid: string, id: string, patch: UpdateDependencyInput): Dependency {
  const dep = dependencies.read(mid, id);
  if (!dep) throw new GovernanceError("Dependency not found.", 404);
  const next: Dependency = { ...dep };
  const prevStatus = dep.status;
  if (patch.depends_on !== undefined) next.depends_on = patch.depends_on.trim();
  if (patch.type !== undefined) next.type = patch.type;
  if (patch.description !== undefined) next.description = patch.description.trim();
  if (patch.blocking !== undefined) next.blocking = patch.blocking;
  if (patch.status !== undefined) next.status = patch.status;
  next.updated_at = nowIso();
  dependencies.write(mid, next);
  dependencies.append(mid, id, "updated");
  if (patch.status !== undefined && patch.status !== prevStatus) {
    dependencies.append(mid, id, "state_changed", { previous: prevStatus, next: patch.status });
  }
  return next;
}

export const listDependencies = (mid: string) => dependencies.list(mid);
export const listDependenciesWithHistory = (mid: string) => dependencies.listWithHistory(mid);

// --- KPIs (informational) --------------------------------------------------

export interface CreateKpiInput {
  name: string;
  description?: string;
  current_value?: string;
  target_value?: string;
  unit?: string;
  status?: KpiStatus;
}
export interface UpdateKpiInput {
  name?: string;
  description?: string;
  current_value?: string;
  target_value?: string;
  unit?: string;
  status?: KpiStatus;
}

export function createKpi(mid: string, input: CreateKpiInput): Kpi {
  ensureMission(mid);
  if (!input.name?.trim()) throw new GovernanceError("Name is required.");
  const id = kpis.nextId(mid);
  const created = nowIso();
  const kpi: Kpi = {
    id,
    mission_id: mid,
    name: input.name.trim(),
    description: input.description?.trim() || "",
    current_value: input.current_value?.trim() || "",
    target_value: input.target_value?.trim() || "",
    unit: input.unit?.trim() || "",
    status: input.status || "On Track",
    created_at: created,
    updated_at: created,
  };
  kpis.write(mid, kpi);
  kpis.append(mid, id, "created", { detail: kpi.name });
  return kpi;
}

export function updateKpi(mid: string, id: string, patch: UpdateKpiInput): Kpi {
  const kpi = kpis.read(mid, id);
  if (!kpi) throw new GovernanceError("KPI not found.", 404);
  const next: Kpi = { ...kpi };
  const prevStatus = kpi.status;
  if (patch.name !== undefined) next.name = patch.name.trim();
  if (patch.description !== undefined) next.description = patch.description.trim();
  if (patch.current_value !== undefined) next.current_value = patch.current_value.trim();
  if (patch.target_value !== undefined) next.target_value = patch.target_value.trim();
  if (patch.unit !== undefined) next.unit = patch.unit.trim();
  if (patch.status !== undefined) next.status = patch.status;
  next.updated_at = nowIso();
  kpis.write(mid, next);
  kpis.append(mid, id, "updated", { detail: `${next.current_value}${next.unit ? " " + next.unit : ""}` });
  if (patch.status !== undefined && patch.status !== prevStatus) {
    kpis.append(mid, id, "state_changed", { previous: prevStatus, next: patch.status });
  }
  return next;
}

export const listKpis = (mid: string) => kpis.list(mid);
export const listKpisWithHistory = (mid: string) => kpis.listWithHistory(mid);

// --- Mission block reasons (governance, never automatic) -------------------

/**
 * Reasons the mission is eligible to be Blocked: an active blocking dependency,
 * or a critical unresolved risk. These are displayed to the operator; the Block
 * transition itself is always manual.
 */
export function missionBlockReasons(mid: string): BlockReason[] {
  const reasons: BlockReason[] = [];
  for (const d of dependencies.list(mid)) {
    if (d.blocking && d.status === "Active") {
      reasons.push({
        kind: "dependency",
        id: d.id,
        detail: `Active blocking dependency: ${d.type} — ${d.depends_on}`,
      });
    }
  }
  for (const r of risks.list(mid)) {
    if (r.severity === "Critical" && !["Resolved", "Closed", "Accepted"].includes(r.status)) {
      reasons.push({
        kind: "risk",
        id: r.id,
        detail: `Critical unresolved risk: ${r.title}`,
      });
    }
  }
  return reasons;
}

root · /srv/aaf