63 lines
2.0 KiB
TypeScript
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);
|
|
},
|
|
};
|
|
}
|