Files
stack/extensions/mosaic-core/lib/journal.ts
T

63 lines
2.0 KiB
TypeScript

// lib/journal.ts — per-incarnation append-only JSONL journal (PRD R6, NG-3).
//
// One file per incarnation identity (the NG-1 process-launch identity —
// NEVER PI_SESSION_ID) under the XDG state home:
// ${XDG_STATE_HOME:-~/.local/state}/mosaic-core/<incarnationId>.jsonl
//
// Append-only is structural: the only write primitive is appendFileSync of a
// newline-terminated JSON line. No read-modify-write, no truncation, no
// rewrite — a journal that can be edited is a journal that cannot prove
// anything. Event payloads are pre-sanitized by their producers (the denial
// event carries tool+reason only; proposals pass the proposal sanitizer).
import { appendFileSync, mkdirSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
export type JournalKind = "reconciliation" | "denial" | "improvement-proposal";
export interface JournalEvent {
ts: string;
incarnationId: string;
kind: JournalKind;
[field: string]: unknown;
}
export interface JournalIO {
mkdirSync(path: string, opts: { recursive: boolean }): void;
appendFileSync(path: string, data: string): void;
}
function defaultIO(): JournalIO {
return { mkdirSync, appendFileSync };
}
export function journalDir(stateHome: string | undefined, home: string = homedir()): string {
return join(stateHome ?? join(home, ".local", "state"), "mosaic-core");
}
export interface Journal {
path: string;
append(event: Omit<JournalEvent, "ts" | "incarnationId">): void;
}
export function createJournal(opts: {
incarnationId: string;
stateHome?: string;
io?: JournalIO;
now?: () => string;
}): Journal {
const io = opts.io ?? defaultIO();
const now = opts.now ?? (() => new Date().toISOString());
const dir = journalDir(opts.stateHome);
const path = join(dir, `${opts.incarnationId}.jsonl`);
io.mkdirSync(dir, { recursive: true });
return {
path,
append(event) {
const line = `${JSON.stringify({ ts: now(), incarnationId: opts.incarnationId, ...event })}\n`;
io.appendFileSync(path, line);
},
};
}