trellis
Version:
Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications
95 lines • 3.8 kB
TypeScript
/**
* JSONL (newline-delimited) op log (P0).
* Shared by integration journal and per-lane journals (ADR 0001, ADR 0005).
*
* On-disk format is one op per line (`JSON.stringify(op) + "\n"`). Appends use
* `appendFileSync`, which is atomic for the small per-op payloads Trellis mints,
* so two processes can never clobber each other's writes — each simply adds its
* line. This replaces the old single-JSON-array format, whose `append()` rewrote
* the *entire* array from whatever was in memory; a long-lived process holding a
* stale in-memory copy could silently overwrite intervening appends from another
* process (the lost-backfill / lost-epic bug).
*
* Legacy JSON-array files are still readable: `load()` detects the `[` prefix and
* parses the array. The first `append()` after a legacy load migrates the file to
* JSONL in one atomic step, then continues appending lines.
*/
import type { VcsOp } from './types.js';
export interface RepairOptions {
confirmDestructive?: boolean;
/** Human ack that remote backup may be stale (repair gate). */
iKnow?: boolean;
/** Repo root for remote ack check (defaults from ops path). */
rootPath?: string;
/** Attempt ~/.trellis/oplog-mirror restore before truncating local file */
preferMirror?: boolean;
}
export interface RepairResult {
recovered: number;
lost: number;
/** Restored from local mirror before repair */
mirrorRestored?: number;
}
/**
* Backend-agnostic op log surface.
*
* Implementations may persist to filesystem (`JsonOpLog`), IndexedDB
* (`IdbOpLog`), or any other store. The contract:
*
* - `load()` returns `void | Promise<void>` so filesystem backends can stay
* synchronous while browser backends (IndexedDB, OPFS) are async. Callers
* that may use either backend should `await opLog.load()`.
* - `append()`, `readAll()`, `getLastOp()`, `count()` are sync — they
* operate on an in-memory cache the backend maintains. Sync reads are
* required by the engine, which does not await op-log access on hot paths.
* - `flush()` is optional — when present, awaiting it guarantees durability
* for backends with deferred writes (e.g. IndexedDB). Filesystem backends
* that write synchronously may omit it.
*
* Implementations are responsible for hash-deduplication on `append`.
*/
export interface OpLog {
load(): void | Promise<void>;
append(op: VcsOp): void;
readAll(): VcsOp[];
getLastOp(): VcsOp | undefined;
count(): number;
flush?(): Promise<void>;
}
export declare class JsonOpLog implements OpLog {
private ops;
private hashes;
private filePath;
private lockPath;
/** True when the on-disk file is still the legacy single JSON array. */
private legacy;
constructor(filePath: string);
get path(): string;
load(): void;
append(op: VcsOp): void;
readAll(): VcsOp[];
getLastOp(): VcsOp | undefined;
count(): number;
/** Parse either a legacy JSON array or a JSONL file into ops. */
private parseFile;
private parseJsonl;
/** Authoritative read of the on-disk journal (array or JSONL). */
private readDiskOps;
/** One-time write of the legacy array as JSONL, atomically. */
private migrateToJsonl;
private appendLineToDisk;
private backupCurrentFile;
private withLock;
static repair(filePath: string, opts?: RepairOptions): RepairResult;
/** Exposed for repair + tests */
static parseFileForRepair(raw: string): {
valid: string[];
corrupt: boolean;
dropped: number;
};
}
/** Lane journal op log — same implementation, distinct path (ADR 0001, ADR 0005). */
export declare class LaneOpLog extends JsonOpLog {
constructor(laneDir: string);
}
//# sourceMappingURL=op-log.d.ts.map