Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/dream-cycle/LEDGER.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
| 2026-08-17 | evaluation-adapters | Given the Darwin evaluator at commit `569285b` configured with `--sandbox mock - | NONE | NONE | yes | ACCEPT | | 7ecbf140d180 | |
| 2026-08-17 | evaluation-adapters | Given the Darwin evaluator at commit `569285b` configured with `--sandbox mock - | NONE | NONE | yes | ACCEPT | | 9bf1005b390a | |
| 2026-08-28 | ledger-signals | fate reconciliation (agentbox operator audit): judgment-broker queue showed #9/#11/#15 as pending-merge; their changes landed 2026-08-13..15 (#7/#9 consolidated into the fork by human merge, #11/#15 merged in upstream ruvnet/dream-machine whose PR numbering this ledger borrowed) — recording terminal fates so the queue clears | NONE | NONE | no | INCONCLUSIVE | | operator | #7:MERGED #9:MERGED #11:MERGED #15:MERGED |
| 2026-09-01 | ledger-signals | INCONCLUSIVE — see report | NONE | NONE | yes | INCONCLUSIVE | | bdb6735f5b3b | |
114 changes: 114 additions & 0 deletions packages/ledger/src/rowContract.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { existsSync, readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { describe, expect, it } from "vitest";
import { parseRow, validateLedger, validateRow } from "./rowContract";

const ENFORCE_FROM = "2026-09-06";

function rulesOf(cells: string[]): string[] {
return validateRow(cells, ENFORCE_FROM).map((v) => v.rule);
}

const COMPLIANT = [
"2026-09-06",
"ledger-signals",
"row-contract validator added; prior rows show format drift",
"NONE",
"pending",
"yes",
"ACCEPT",
"guards cross-night memory",
"0123456789ab",
"",
];

describe("rowContract unit rules", () => {
it("accepts a compliant row", () => {
expect(rulesOf(COMPLIANT)).toEqual([]);
});

it("rejects pointer-only findings", () => {
const cells = [...COMPLIANT];
cells[2] = "INCONCLUSIVE — see report";
expect(rulesOf(cells)).toContain("finding-pointer");
});

it("rejects frozen-hypothesis leakage into the finding column", () => {
const cells = [...COMPLIANT];
cells[2] = "Given the Darwin evaluator at commit `7c30573a2d73c8fa4c67a43042d7c0b204eefa13`";
expect(rulesOf(cells)).toContain("finding-hypothesis-leak");
});

it("rejects findings longer than 80 chars", () => {
const cells = [...COMPLIANT];
cells[2] = "x".repeat(81);
expect(rulesOf(cells)).toContain("finding-too-long");
});

it("rejects ACCEPT rows that do not track a PR", () => {
const cells = [...COMPLIANT];
cells[4] = "NONE";
expect(rulesOf(cells)).toContain("accept-without-pr");
});

it("rejects prose in the prior-night fates column", () => {
const cells = [...COMPLIANT];
cells[9] = "merged #7 by human";
expect(rulesOf(cells)).toContain("fates-grammar");
});

it("accepts well-formed fate tokens", () => {
const cells = [...COMPLIANT];
cells[9] = "#7:MERGED #8:OPEN #9:STALE";
expect(rulesOf(cells)).toEqual([]);
});

it("grandfathers rows dated before the enforcement cutoff", () => {
const cells = [...COMPLIANT];
cells[0] = "2026-09-01";
cells[2] = "INCONCLUSIVE — see report";
expect(rulesOf(cells)).toEqual([]);
});

it("parses header and separator lines without flagging them", () => {
const text = [
"| date | deep | finding | issue | PR | evaluated? | verdict | effect | witness | prior-night fates |",
"| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |",
"| " + COMPLIANT.join(" | ") + " |",
].join("\n");
expect(validateLedger(text, ENFORCE_FROM)).toEqual([]);
expect(parseRow(text.split("\n")[0] ?? "")).toEqual([
"date",
"deep",
"finding",
"issue",
"PR",
"evaluated?",
"verdict",
"effect",
"witness",
"prior-night fates",
]);
});
});

describe("rowContract on the real ledger", () => {
const ledgerPath = findLedger();

it.skipIf(ledgerPath === null)("docs/dream-cycle/LEDGER.md complies from the cutoff", () => {
const text = readFileSync(ledgerPath as string, "utf8");
expect(validateLedger(text, ENFORCE_FROM)).toEqual([]);
});
});

function findLedger(): string | null {
let dir = process.cwd();
for (let i = 0; i < 6; i += 1) {
const candidate = join(dir, "docs", "dream-cycle", "LEDGER.md");
if (existsSync(candidate)) return candidate;
const parent = dirname(dir);
if (parent === dir) return null;
dir = parent;
}
return null;
}
74 changes: 74 additions & 0 deletions packages/ledger/src/rowContract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Ledger row contract validator — dream-cycle 2026-09-06, deep: ledger-signals.
// Enforces the machine-readable invariants of docs/dream-cycle/LEDGER.md rows:
// finding is concrete and self-contained (<= 80 chars, no pointers, no
// frozen-hypothesis leakage); prior-night fates are token-only (#N:FATE with
// FATE in MERGED|CLOSED|OPEN|STALE); ACCEPT rows track a PR and carry a witness.
// Rows dated before `enforceFrom` are grandfathered.

export interface RowViolation {
date: string;
rule: string;
detail: string;
}

const FATE_TOKEN = /^#\d+:(MERGED|CLOSED|OPEN|STALE)$/;

/** Parse a markdown table line into its 10 ledger cells, or null if not a row. */
export function parseRow(line: string): string[] | null {
const trimmed = line.trim();
if (!trimmed.startsWith("|")) return null;
const cells = trimmed.split("|").map((cell) => cell.trim());
if (cells.length < 12) return null;
return cells.slice(1, 11);
}

/** Validate one parsed row (10 cells). Rows dated before enforceFrom are skipped. */
export function validateRow(cells: string[], enforceFrom: string): RowViolation[] {
const violations: RowViolation[] = [];
const at = (index: number): string => (cells[index] ?? "");
const date = at(0);
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return violations;
if (date < enforceFrom) return violations;

const flag = (rule: string, detail: string): void => {
violations.push({ date, rule, detail });
};

const finding = at(2);
if (finding.length === 0) flag("finding-empty", "finding column is empty");
if (finding.length > 80) flag("finding-too-long", `${finding.length} chars > 80`);
if (/\bsee\s+(report|gist)\b/i.test(finding) || /^(see|gist)\b/i.test(finding)) {
flag("finding-pointer", "finding points elsewhere instead of stating the result");
}
if (/^given\b/i.test(finding)) {
flag("finding-hypothesis-leak", "finding column contains frozen-hypothesis text");
}

const verdict = at(6);
if (!/^(ACCEPT|REJECT|INCONCLUSIVE)$/.test(verdict)) {
flag("verdict-vocab", `unrecognised verdict "${verdict}"`);
}
if (verdict === "ACCEPT" && (at(4) === "NONE" || at(4) === "")) {
flag("accept-without-pr", "ACCEPT rows must record PR as pending or #N");
}
if (verdict === "ACCEPT" && at(8) === "") {
flag("accept-without-witness", "ACCEPT rows must carry a witness");
}

const fates = at(9);
if (fates !== "" && !fates.split(/\s+/).every((token) => FATE_TOKEN.test(token))) {
flag("fates-grammar", "prior-night fates must be space-separated #N:FATE tokens only");
}

return violations;
}

/** Validate a whole ledger file body. */
export function validateLedger(text: string, enforceFrom: string): RowViolation[] {
const violations: RowViolation[] = [];
for (const line of text.split(/\r?\n/)) {
const cells = parseRow(line);
if (cells !== null) violations.push(...validateRow(cells, enforceFrom));
}
return violations;
}
Loading