Compare commits

..
Author SHA1 Message Date
JUSHUANGHUI LI 6bc3186975 test(daemon): quote harness fixture paths containing spaces (#2830)
Test-only. claude-context-collector.test.ts compares the collector's argv literally, and community-harness-runtime.integration.test.ts and runner-conformance-check.test.ts quote the fixture paths they place in shell text and YAML, so these tests pass from a checkout path containing spaces or #. Production code is unchanged: the paths the product builds are already quoted, and a preset's command or a conformance --runner is the author's own shell text to quote.
2026-11-09 05:31:43 -08:00
JUSHUANGHUI LI 00d6d25cce fix(scripts): run repository guards from paths containing spaces (#2828)
scripts/check-docs-guard.mjs, check-internal-leak-guard.mjs and mirror-skills.mjs decided whether they were the main module by comparing import.meta.url with argv[1] as written, so from a checkout path containing spaces, # or a symlink they silently exited 0 without checking anything. They now compare the resolved real paths of both sides, handle an absent argv[1], and still don't run when imported. A new script-entrypoints test runs each script from such paths, including through a symlink under --preserve-symlinks-main.
2026-11-09 05:31:15 -08:00
billbenetti 10736e1d9c perf(daemon): read queue listings, show and the parked route without per-row re-reads (#2825)
Queue listings, queue show and the parked-obligation route no longer re-read each row: the listing reads are batched, the parked route is a lean read of only what it shows, and both share one row projection (QueueRepository.projectRows and the shared waiting reads). Rows, values, key order, delivery outcome and failure detail are unchanged. Activity observation is not frozen: a listing still reads each owner's activity once, and that read can settle an expiry. Rebased on its own after #2824; part of the #161 queue-performance work.
2026-11-09 05:09:52 -08:00
JUSHUANGHUI LI 1fe9296b48 fix(proof): read retained judgments with CRLF line endings (#2835)
The retained judgment ledger's frontmatter delimiters required bare LF, so an otherwise identical judgment saved with CRLF line endings couldn't be read. They now accept an optional carriage return before each delimiter's newline. Nothing is normalised: the content hash is still over the parsed payload, field, sequence and lineage validation are unchanged, and whole-file, raw and addressed-span evidence hashing still reports changed bytes.
2026-11-09 05:07:12 -08:00
JUSHUANGHUI LI 369a129f69 perf(daemon): resolve Markdown section ends in one pass (#2833)
parseMarkdownSections found each section's end with headers.slice(idx + 1).find(...), which copies the rest of the heading list for every section, quadratic in heading count. It now walks the headings once from the end with a stack of later headings, popping those deeper than the current one, so the top is the nearest following heading with the same or a shallower level, exactly the heading the old find returned. Section recognition, addresses and materialisation are unchanged; the new cells cover nesting and a 10,000-section document.
2026-11-09 04:36:52 -08:00
21 changed files with 903 additions and 176 deletions

No files matched your search

+11 -1
View File
@@ -170,6 +170,16 @@ function atxHeading(line: string): { level: number; title: string } | null {
export function parseMarkdownSections(text: string): MarkdownSection[] {
const lines = text.split("\n");
const { hits: headers } = scanHeaders(lines);
// Resolve all section ends in one backwards pass. Copying the remaining
// header array for every section makes even sibling-only documents quadratic.
const fullEnds: number[] = [];
const following: HeaderHit[] = [];
for (let idx = headers.length - 1; idx >= 0; idx--) {
const header = headers[idx]!;
while (following.length && following[following.length - 1]!.level > header.level) following.pop();
fullEnds[idx] = following[following.length - 1]?.line ?? lines.length;
following.push(header);
}
const sections: MarkdownSection[] = [];
let currentH2: string | null = null;
for (let idx = 0; idx < headers.length; idx++) {
@@ -186,7 +196,7 @@ export function parseMarkdownSections(text: string): MarkdownSection[] {
// instead of being silently promoted to top-level addresses.
const headerPath = h.level === 2 ? [slug] : currentH2 !== null ? [currentH2, slug] : [slug];
// FULL span: to the next header with level <= this one (same-or-higher).
const fullEnd = headers.slice(idx + 1).find((n) => n.level <= h.level)?.line ?? lines.length;
const fullEnd = fullEnds[idx]!;
// OWN text: to the next header of ANY level.
const ownEnd = headers[idx + 1]?.line ?? lines.length;
sections.push({
@@ -128,7 +128,7 @@ function ledger(dir: string, io: ScopeFsDeps): Judgment[] {
const expected = `${String(receipts.length + 1).padStart(8, "0")}.md`;
if (name !== expected) throw new JudgmentError("journal_conflict", `${home}: expected ${expected}, found ${name}; inspect retained receipts`);
const raw = io.readFile(path.join(home, name));
const fm = raw && /^---\n([\s\S]*?)\n---(?:\n|$)/.exec(raw);
const fm = raw && /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(raw);
if (!fm) throw new JudgmentError("journal_invalid", `${home}/${name}: incomplete judgment`);
const obj = mapping(YAML.parse(fm[1]!), name), { id, ...payload } = obj;
if (id !== hash(payload) || obj.version !== 1 || obj.sequence !== receipts.length + 1 || typeof obj.itemId !== "string" || typeof obj.itemRevision !== "string" || typeof obj.policyRevision !== "string" || typeof obj.actor !== "string" || typeof obj.operationId !== "string" || typeof obj.intent !== "string" || !["accept", "reject", "withdraw"].includes(String(obj.verdict)) || !Array.isArray(obj.evidence)) throw new JudgmentError("journal_invalid", `${home}/${name}: malformed or changed judgment; inspect history`);
+48 -16
View File
@@ -66,7 +66,7 @@ import { physicalSessionName } from "./session-registry.js";
import type { QueueWakeFacts } from "./draft-aware-delivery.js";
import { derivePickup, type PickupReceipt } from "./queue-pickup.js";
import { withRecoveryIndex } from "./queue-recovery.js";
import { lastMeaningfulTransition, readWaitingView, type WaitingView, type WaitingActivityReader } from "./queue-waiting.js";
import { lastMeaningfulTransitionMany, meaningfulFor, readWaitingView, WaitingStatements, type WaitingView, type WaitingActivityReader, type WaitingReads } from "./queue-waiting.js";
import { isOpenRigSender, OPENRIG_SENDER, wrapPaneEnvelope } from "../lib/pane-envelope.js";
import { isBoundTerminalSeat } from "./terminal-seat.js";
import { renderQueueHandoffNudge } from "./queue-nudge-text.js";
@@ -4432,14 +4432,12 @@ export class QueueRepository {
.prepare("SELECT * FROM queue_items WHERE qitem_id = ?")
.get(qitemId) as QueueItemRow | undefined;
if (!row) return null;
const item = this.rowToItem(row);
// OPR.0.5.6.14 — the row FACE answers "did it reach them" in one read for
// gateway-routed rows; null for pane-bound (absence-governed, no key lies).
const ledger = this.deliveryOutcomeFor(item.qitemId);
// The same projection as a listing's, for one row: one read of its owner notifications and ledger.
const item = this.projectRows([row], true)[0]!;
return {
...item,
deliveryOutcome: ledger?.outcome ?? null,
...(ledger && ledger.outcome !== "posted" && ledger.outcome !== "accepted" ? { deliveryFailureDetail: ledger.detail } : {}),
...(item.replyTo ? { replyToFallback: this.replyToFallbackFor(item.qitemId) } : {}),
};
}
@@ -4584,18 +4582,25 @@ export class QueueRepository {
`SELECT ${columns} FROM queue_items ${where} ORDER BY ${orderBy} LIMIT ?`
)
.all(...params) as QueueItemRow[];
const items = this.projectRows(rows, !opts?.compact);
return opts?.compact ? items.map(compactRow) : items;
}
/** The listing projection (list, and show as a listing of one row): each row's item with its delivery outcome, the
* owner notifications, ledgers and shared reads read once for all the rows. includeWaiting: full rows (SELECT *). */
private projectRows(rows: QueueItemRow[], includeWaiting: boolean): QueueItem[] {
const owners = this.transitionLog.ownerNotificationsForQitems(rows.map((row) => row.qitem_id));
const ledgers = this.deliveryOutcomesFor(rows, owners, true);
const items = rows.map((r) => {
const reads = this.listingReads(rows, includeWaiting);
return rows.map((r) => {
const ledger = ledgers.get(r.qitem_id);
const item = this.rowToItem(r, !opts?.compact, { owners: owners.get(r.qitem_id) ?? [], ledger: ledger ?? null });
const item = this.rowToItem(r, includeWaiting, { owners: owners.get(r.qitem_id) ?? [], ledger: ledger ?? null, reads });
return {
...item,
deliveryOutcome: ledger?.outcome ?? null,
...(ledger && ledger.outcome !== "posted" && ledger.outcome !== "accepted" ? { deliveryFailureDetail: ledger.detail } : {}),
};
});
return opts?.compact ? items.map(compactRow) : items;
}
/**
@@ -5498,9 +5503,27 @@ export class QueueRepository {
try { return this.activityReader?.(session) ?? null; } catch { return null; }
}
/** What a listing reads once for all its rows instead of once per row. includeWaiting: the rows' waiting views read
* their qitem blockers' last meaningful transitions too. */
private listingReads(rows: QueueItemRow[], includeWaiting: boolean): ListingReads {
const ids = rows.map((r) => r.qitem_id);
if (includeWaiting) for (const r of rows) if (r.blocked_on?.startsWith("qitem-")) ids.push(r.blocked_on);
// Each owner's activity once per listing: a row and its waiting view (and every other row to that owner) read the
// same seat state. ownerActivity reads a failure as null, so one session's failure stays that session's.
const activities = new Map<string, ReturnType<WaitingActivityReader>>();
const activity = (session: string): ReturnType<WaitingActivityReader> => {
if (!activities.has(session)) activities.set(session, this.ownerActivity(session));
return activities.get(session) ?? null;
};
return { meaningful: lastMeaningfulTransitionMany(this.db, ids), statements: new WaitingStatements(this.db), activity };
}
/** listed: the row's owner notifications and ledger, when a listing read them for all its rows. */
waitingView(qitemId: string, listed?: ListedLedger): WaitingView | null {
const view = readWaitingView(this.db, qitemId, this.activityReader, target => this.acceptsDestination(target));
// The row in hand, when the listing passed it: its later re-reads below are served by it too.
const inHand = listed?.row && listed.row.qitem_id === qitemId ? listed.row : undefined;
const reads = listed?.reads && inHand ? { ...listed.reads, row: inHand } : listed?.reads;
const view = readWaitingView(this.db, qitemId, listed?.reads?.activity ?? this.activityReader, target => this.acceptsDestination(target), reads);
if (view && ["pending", "in-progress"].includes(view.state)) {
// #514 — the unclaimed sweep skips a pending ask already posted to its human in the current
// episode (same predicate as runStuckSweep), so the view must not promise that sweep.
@@ -5508,7 +5531,9 @@ export class QueueRepository {
const posted = episodeDelivery !== null;
const onSteering = !posted && view.state === "pending" && this.humanAskWithoutAlert(qitemId, listed);
const row = view.state === "pending" && !onSteering
? this.db.prepare(`SELECT state, ${this.hasHumanIntentColumn ? "human_intent" : "NULL AS human_intent"}, destination_session, last_nudge_result FROM queue_items WHERE qitem_id = ?`).get(qitemId) as { state: QueueItem["state"]; human_intent: QueueItem["humanIntent"]; destination_session: string; last_nudge_result: string | null } | undefined
? inHand
? { state: inHand.state as QueueItem["state"], human_intent: inHand.human_intent ?? null, destination_session: inHand.destination_session, last_nudge_result: inHand.last_nudge_result }
: this.db.prepare(`SELECT state, ${this.hasHumanIntentColumn ? "human_intent" : "NULL AS human_intent"}, destination_session, last_nudge_result FROM queue_items WHERE qitem_id = ?`).get(qitemId) as { state: QueueItem["state"]; human_intent: QueueItem["humanIntent"]; destination_session: string; last_nudge_result: string | null } | undefined
: undefined;
const humanUpdate = !!row && this.isRegisteredHumanUpdate({ state: row.state, humanIntent: row.human_intent, destinationSession: row.destination_session });
const noticeOutcome = !posted && !onSteering ? (listed ? listed.ledger?.outcome : this.deliveryOutcomeFor(qitemId)?.outcome) : undefined;
@@ -5535,7 +5560,7 @@ export class QueueRepository {
if (recovery && !localOnly) {
if (!awaitingHuman && !humanUpdate) view.laterBackstop = { ...view.nextBackstop, note: "Conditional safety net; current delivery/recovery ownership is evaluated first." };
view.nextBackstop = recovery;
} else if (!awaitingHuman && view.state === "pending" && (this.db.prepare("SELECT last_nudge_result FROM queue_items WHERE qitem_id = ?")
} else if (!awaitingHuman && view.state === "pending" && (inHand ?? this.db.prepare("SELECT last_nudge_result FROM queue_items WHERE qitem_id = ?")
.get(qitemId) as { last_nudge_result: string | null } | undefined)?.last_nudge_result === "retained:typing_guard") {
// Held at the seat on purpose: not stuck, and not picked up. Nothing replays it; reading or delivering it is a
// deliberate step.
@@ -5582,9 +5607,11 @@ export class QueueRepository {
private rowToItem(row: QueueItemRow, includeWaiting = true, listed?: ListedLedger): QueueItem {
// S04 — derive the pickup receipt at the ONE shared projection point (list/show/overdue
// all flow through here), so the park-vs-strand question is answered by the row face.
const meaningful = lastMeaningfulTransition(this.db, row.qitem_id);
const waiting = includeWaiting ? this.waitingView(row.qitem_id, listed) : null;
const activity = this.ownerActivity(row.destination_session);
const meaningful = meaningfulFor(this.db, row.qitem_id, listed?.reads);
// A listing's rows are its own SELECT *: the waiting view reads the row in hand instead of reading it again.
const waiting = includeWaiting ? this.waitingView(row.qitem_id, listed?.reads ? { ...listed, row } : listed) : null;
const listedActivity = listed?.reads?.activity;
const activity = listedActivity ? listedActivity(row.destination_session) : this.ownerActivity(row.destination_session);
const pickup = derivePickup({
state: row.state,
lastMeaningfulAt: meaningful?.at,
@@ -5650,8 +5677,13 @@ type LedgerItem = Pick<QueueItem, "qitemId" | "state" | "blockedOn" | "destinati
type DeliveryLedger = { outcome: "posted" | "accepted" | "transport-failed" | "never-posted"; detail: string; episode: { notificationKey: string; startedAt: string } | null };
/** A listed row's owner notifications (newest first) and its delivery ledger, read once for the whole listing. */
type ListedLedger = { owners: QueueTransition[]; ledger: DeliveryLedger | null };
/** What a listing reads once for all its rows (see QueueRepository.listingReads): the waiting view's reads, and each
* owner's activity, memoized for the listing. */
type ListingReads = WaitingReads & { activity?: (session: string) => ReturnType<WaitingActivityReader> };
/** A listed row's owner notifications (newest first) and its delivery ledger, read once for the whole listing, and the
* listing's shared reads. */
type ListedLedger = { owners: QueueTransition[]; ledger: DeliveryLedger | null; reads?: ListingReads; row?: QueueItemRow };
function ledgerItem(row: LedgerRow): LedgerItem {
return {
+83 -14
View File
@@ -27,15 +27,83 @@ export function lastMeaningfulTransition(db: Database.Database, id: string): { i
return row ? { id: row.transition_id, at: row.ts } : null;
}
/** lastMeaningfulTransition for many rows in one read: the same LAG window, partitioned by row, and the same filters.
* Every requested id is a key of the result; an id without a meaningful transition maps to null. */
export function lastMeaningfulTransitionMany(db: Database.Database, ids: readonly string[]): Map<string, { id: number; at: string } | null> {
const unique = [...new Set(ids)];
const out = new Map<string, { id: number; at: string } | null>(unique.map((id) => [id, null]));
if (unique.length === 0) return out;
if (!db.prepare("SELECT 1 FROM sqlite_master WHERE name = 'queue_transitions'").get()) return out;
const hasWakes = db.prepare("SELECT 1 FROM sqlite_master WHERE name = 'queue_transition_wakes'").get();
const rows = db.prepare(`SELECT qitem_id, transition_id, ts FROM (
SELECT t.qitem_id, t.transition_id, t.ts, ROW_NUMBER() OVER (PARTITION BY t.qitem_id ORDER BY t.transition_id DESC) AS newest FROM (
SELECT *, LAG(state) OVER (PARTITION BY qitem_id ORDER BY transition_id) AS previous_state FROM queue_transitions
WHERE qitem_id IN (SELECT value FROM json_each(?))
) t WHERE (t.actor_session NOT IN ('watchdog@system', 'wake-ladder@system', 'daemon@kernel', 'daemon@system') OR t.state IS NOT t.previous_state)
${hasWakes ? "AND NOT EXISTS (SELECT 1 FROM queue_transition_wakes w WHERE w.transition_id = t.transition_id AND w.phase = 'fired')" : ""}
) WHERE newest = 1`).all(JSON.stringify(unique)) as Array<{ qitem_id: string; transition_id: number; ts: string }>;
for (const row of rows) out.set(row.qitem_id, { id: row.transition_id, at: row.ts });
return out;
}
/** The waiting view's statements, each prepared once, and the schema checks they depend on, each made once. A listing
* holds one for all its rows; a single read uses a fresh one. A listing reads synchronously, so neither the statements
* nor the schema checks can go stale under it. */
export class WaitingStatements {
private readonly prepared = new Map<string, Database.Statement>();
private readonly tables = new Map<string, boolean>();
constructor(private readonly db: Database.Database) {}
statement(sql: string): Database.Statement {
let prepared = this.prepared.get(sql);
if (!prepared) {
prepared = this.db.prepare(sql);
this.prepared.set(sql, prepared);
}
return prepared;
}
hasTable(name: string): boolean {
let known = this.tables.get(name);
if (known === undefined) {
known = this.statement("SELECT 1 FROM sqlite_master WHERE name = ?").get(name) !== undefined;
this.tables.set(name, known);
}
return known;
}
}
/** The queue_items columns a waiting view reads from its own row. */
export interface WaitingRow {
qitem_id: string; source_session: string; destination_session: string; state: string; blocked_on: string | null; closure_required_at: string | null; tags: string | null;
}
/** What a listing has already read for all its rows, lent to each row's waiting view. */
export interface WaitingReads {
/** The row in hand (a listing's own SELECT *), read in the same synchronous pass; used only when its id is the view's. */
row?: WaitingRow;
/** The listing's statements and schema checks, shared by all its rows. */
statements?: WaitingStatements;
/** lastMeaningfulTransitionMany over the listing's rows and their qitem blockers; an id it lacks is read alone. */
meaningful?: ReadonlyMap<string, { id: number; at: string } | null>;
}
/** One row's last meaningful transition: from the listing's batch when it read this id, else read alone. */
export function meaningfulFor(db: Database.Database, id: string, reads?: WaitingReads): { id: number; at: string } | null {
const known = reads?.meaningful;
return known && known.has(id) ? known.get(id) ?? null : lastMeaningfulTransition(db, id);
}
/** Grace starts when a row becomes actionable again, not at its birth before a
* long healthy park. Same-state notes and deliveries cannot move this boundary. */
export function pendingSince(db: Database.Database, id: string): string | null {
if (!db.prepare("SELECT 1 FROM sqlite_master WHERE name = 'queue_transitions'").get()) return (db.prepare("SELECT ts_created FROM queue_items WHERE qitem_id = ?").get(id) as { ts_created: string } | undefined)?.ts_created ?? null;
const row = db.prepare(`SELECT ts, previous_state FROM (SELECT ts, state, transition_id,
export function pendingSince(db: Database.Database, id: string, statements = new WaitingStatements(db)): string | null {
const created = () => (statements.statement("SELECT ts_created FROM queue_items WHERE qitem_id = ?").get(id) as { ts_created: string } | undefined)?.ts_created ?? null;
if (!statements.hasTable("queue_transitions")) return created();
const row = statements.statement(`SELECT ts, previous_state FROM (SELECT ts, state, transition_id,
LAG(state) OVER (ORDER BY transition_id) AS previous_state FROM queue_transitions WHERE qitem_id = ?)
WHERE state = 'pending' AND previous_state IS NOT 'pending' ORDER BY transition_id DESC LIMIT 1`)
.get(id) as { ts: string; previous_state: string | null } | undefined;
if (!row || row.previous_state === null) return (db.prepare("SELECT ts_created FROM queue_items WHERE qitem_id = ?").get(id) as { ts_created: string } | undefined)?.ts_created ?? null;
if (!row || row.previous_state === null) return created();
return row.ts;
}
function unclaimedSeconds(): number {
@@ -67,13 +135,14 @@ export interface WaitingView {
attention?: { scope: string; revision: string; source: "last observed by wait timer" };
}
export function readWaitingView(db: Database.Database, id: string, readActivity?: WaitingActivityReader, acceptsDestination?: (destination: string) => boolean): WaitingView | null {
const row = db.prepare(`SELECT qitem_id, source_session, destination_session, state, blocked_on, closure_required_at, tags
FROM queue_items WHERE qitem_id = ?`).get(id) as {
qitem_id: string; source_session: string; destination_session: string; state: string; blocked_on: string | null; closure_required_at: string | null; tags: string | null;
} | undefined;
export function readWaitingView(db: Database.Database, id: string, readActivity?: WaitingActivityReader, acceptsDestination?: (destination: string) => boolean,
reads?: WaitingReads): WaitingView | null {
const statements = reads?.statements ?? new WaitingStatements(db);
const held = reads?.row;
const row = held && held.qitem_id === id ? held : statements.statement(`SELECT qitem_id, source_session, destination_session, state, blocked_on, closure_required_at, tags
FROM queue_items WHERE qitem_id = ?`).get(id) as WaitingRow | undefined;
if (!row) return null;
const blocker = row.blocked_on?.startsWith("qitem-") ? db.prepare(
const blocker = row.blocked_on?.startsWith("qitem-") ? statements.statement(
"SELECT destination_session, state FROM queue_items WHERE qitem_id = ?",
).get(row.blocked_on) as { destination_session: string; state: string } | undefined : undefined;
let recoveryOwner: string | null = null;
@@ -82,10 +151,10 @@ export function readWaitingView(db: Database.Database, id: string, readActivity?
const subject = row.blocked_on ? blocker?.destination_session ?? row.blocked_on : row.destination_session;
let observed: ReturnType<WaitingActivityReader> = null;
try { observed = readActivity?.(subject) ?? null; } catch { /* unavailable remains unknown */ }
const meaningful = lastMeaningfulTransition(db, row.blocked_on?.startsWith("qitem-") ? row.blocked_on : id);
const meaningful = meaningfulFor(db, row.blocked_on?.startsWith("qitem-") ? row.blocked_on : id, reads);
const sweepInterval = sweepSeconds();
const view: WaitingView = {
obligation: id, owner: row.destination_session, state: row.state, actionableSince: row.state === "pending" ? pendingSince(db, id) : null,
obligation: id, owner: row.destination_session, state: row.state, actionableSince: row.state === "pending" ? pendingSince(db, id, statements) : null,
blocker: row.blocked_on ? { ref: row.blocked_on, owner: blocker?.destination_session ?? null, state: blocker?.state ?? null } : null,
lastMeaningfulChange: meaningful,
liveness: { subject, activity: observed?.activity ?? "unknown", needsInput: observed?.needsInput ?? { count: 0, reason: null }, confidence: observed && observed.activity !== "unknown" ? "oracle" : "unknown" },
@@ -100,8 +169,8 @@ export function readWaitingView(db: Database.Database, id: string, readActivity?
view.nextBackstop.mechanism = row.state === "pending" ? "queue-stuck-sweep:unclaimed" : "queue-stuck-sweep:pickup (activity checked at detection)";
}
if (row.state === "blocked") {
const hasTimers = db.prepare("SELECT 1 FROM sqlite_master WHERE name = 'queue_transition_wakes'").get();
const timer = hasTimers ? db.prepare(`SELECT j.job_id, j.policy, j.last_evaluation_at, j.registered_at, j.interval_seconds, j.scan_interval_seconds, j.spec_yaml
const hasTimers = statements.hasTable("queue_transition_wakes");
const timer = hasTimers ? statements.statement(`SELECT j.job_id, j.policy, j.last_evaluation_at, j.registered_at, j.interval_seconds, j.scan_interval_seconds, j.spec_yaml
FROM queue_transition_wakes w JOIN watchdog_jobs j ON j.job_id = w.wake_ref
WHERE w.qitem_id = ? AND w.phase = 'armed' AND j.state = 'active'
ORDER BY w.transition_id DESC LIMIT 1`).get(id) as { job_id: string; policy: WatchdogJob["policy"]; last_evaluation_at: string | null; registered_at: string; interval_seconds: number; scan_interval_seconds: number | null; spec_yaml: string } | undefined : undefined;
+23 -49
View File
@@ -10,7 +10,7 @@
// {type:"user"|"assistant", message:{role, content}} where content is a STRING or an array of
// {type:"text"|"thinking"|"tool_use", text?} blocks (only "text" carries user-visible content).
// Codex rollout lines use {payload:{type:"message", role, content}} — handled by the same reader.
import { openSync, fstatSync, readSync, closeSync } from "node:fs";
import { existsSync, readFileSync } from "node:fs";
export interface JsonlExchange {
role: string;
@@ -55,60 +55,34 @@ function toExchange(obj: unknown): JsonlExchange | null {
return null;
}
/** Read complete lines newest-first; join bytes before UTF-8 decoding, including long records. */
function* readLinesBackwards(path: string): Generator<string> {
const fd = openSync(path, "r");
try {
let position = fstatSync(fd).size;
let fragments: Buffer[] = [];
while (position > 0) {
const chunk = Buffer.allocUnsafe(Math.min(64 * 1024, position));
position -= chunk.length;
let read = 0;
while (read < chunk.length) {
const count = readSync(fd, chunk, read, chunk.length - read, position + read);
if (count === 0) throw new Error("transcript changed before the recap read completed");
read += count;
}
let end = chunk.length;
while (end > 0) {
const newline = chunk.lastIndexOf(10, end - 1);
if (newline === -1) break;
const tail = chunk.subarray(newline + 1, end);
yield (fragments.length ? Buffer.concat([tail, ...fragments.reverse()]) : tail).toString("utf8");
fragments = [];
end = newline;
}
if (end > 0) fragments.push(chunk.subarray(0, end));
}
if (fragments.length) yield Buffer.concat(fragments.reverse()).toString("utf8");
} finally {
closeSync(fd);
}
}
/**
* Parse the LAST `n` visible exchanges without reading the whole provider session transcript.
* Memory follows the selected exchanges and longest visited line, not the whole file.
* Honest-degraded: any read/decoding failure returns []; never return a partial recap.
* Unparseable/metadata/text-less lines are skipped, as before.
* Parse a provider session JSONL at `path` into the LAST `n` {role, content} exchanges (bounded recap).
* Honest-degraded: missing/unreadable file → []; unparseable/metadata/text-less lines are skipped.
*/
export function parseJsonlExchanges(path: string, n: number): JsonlExchange[] {
if (n <= 0) return [];
const exchanges: JsonlExchange[] = [];
if (n <= 0 || !existsSync(path)) return [];
let raw: string;
try {
for (const line of readLinesBackwards(path)) {
let obj: unknown;
try {
obj = JSON.parse(line);
} catch {
continue; // corrupt/blank line — skip, never throw
}
const ex = toExchange(obj);
if (ex && exchanges.push(ex) >= n) break;
}
raw = readFileSync(path, "utf8");
} catch {
return [];
}
const exchanges: JsonlExchange[] = [];
// Only decode the tail needed for the recap, not every exchange in a long session.
let end = raw.length;
while (end > 0) {
const start = raw.lastIndexOf("\n", end - 1) + 1;
const line = raw.slice(start, end);
end = start - 1;
if (line.trim().length === 0) continue;
let obj: unknown;
try {
obj = JSON.parse(line);
} catch {
continue; // corrupt line — skip, never throw
}
const ex = toExchange(obj);
if (ex && exchanges.push(ex) >= n) break;
}
return exchanges.reverse();
}
+4 -4
View File
@@ -950,7 +950,7 @@ activityRoutes.get("/parked", (c) => {
| undefined;
const queueRepo = c.get("queueRepo" as never) as
| {
list: (opts: { destinationSession?: string; state?: string[]; limit?: number }) => Array<{ qitemId: string; state: string; summary?: string | null }>;
listOpenObligations: (destinationSession: string, limit: number) => Array<{ qitemId: string; state: "pending" | "in-progress" | "blocked"; summary: string | null }>;
getParkWakeStatus: (qitemId: string) => import("../domain/queue-wake-repository.js").ParkWakeStatus | null;
parkWakeServedByOwnReminder?: (qitemId: string) => boolean;
}
@@ -967,10 +967,10 @@ activityRoutes.get("/parked", (c) => {
const { diagnoseSeatParked, diagnoseRigParked, PARKED_OBLIGATION_LIMIT } = parkedQuery;
const deps = {
getSeatState: (id: string) => nodeExecutorConfirmedStopped(rigRepo.db, id) ? null : oracle.getSeatState(id),
// The obligation face only: the same rows, order and limit as a full list() of this destination's open states,
// without the per-row projections (pickup, waiting view, delivery ledger) the diagnosis never reads.
listOpenObligations: (destination: string, limit: number) => ({
rows: queueRepo
.list({ destinationSession: destination, state: ["pending", "in-progress", "blocked"], limit })
.map((r) => ({ qitemId: r.qitemId, state: r.state as "pending" | "in-progress" | "blocked", summary: r.summary ?? null })),
rows: queueRepo.listOpenObligations(destination, limit),
limit,
}),
getParkWake: (qitemId: string) => queueRepo.getParkWakeStatus(qitemId),
@@ -0,0 +1,202 @@
// The parked route reads its obligation face with QueueRepository.listOpenObligations (qitem id, state and summary)
// instead of a full list() that derives pickup, waiting view and delivery ledger for every row it never shows.
// The response must be the same, byte for byte, as the one the full list() produced. Real queue, rig, session and
// identity repositories on a full test DB; the only difference between the two apps is the obligation reader.
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import type Database from "better-sqlite3";
import { Hono } from "hono";
import { createFullTestDb } from "./helpers/test-app.js";
import { activityRoutes } from "../src/routes/activity.js";
import { RigRepository } from "../src/domain/rig-repository.js";
import { SessionRegistry } from "../src/domain/session-registry.js";
import { SeatIdentityStore } from "../src/domain/seat-identity-store.js";
import { SeatActivityService } from "../src/domain/seat-activity-service.js";
import { EventBus } from "../src/domain/event-bus.js";
import { QueueRepository } from "../src/domain/queue-repository.js";
import { WatchdogJobsRepository } from "../src/domain/watchdog-jobs-repository.js";
import { WatchdogHistoryLog } from "../src/domain/watchdog-history-log.js";
import { watchdogHistorySchema } from "../src/db/migrations/032_watchdog_history.js";
import { queueItemSummarySchema } from "../src/db/migrations/044_queue_item_summary.js";
const RIG = "park-rig";
const IDLE = "dev-impl@park-rig"; // idle at its prompt: parked whenever it owes work
const UNKNOWN = "dev-qa@park-rig"; // no activity evidence: indeterminate
const STOPPED = "dev-ops@park-rig"; // idle evidence, but its session is confirmed missing
const ALIAS = "dev-old@park-rig"; // an earlier address of IDLE, resolved by resolveSessionAddresses
describe("GET /api/activity/parked reads the obligation face without the full list()", () => {
let db: Database.Database;
let queue: QueueRepository;
let jobs: WatchdogJobsRepository;
let history: WatchdogHistoryLog;
let oracle: SeatActivityService;
let clock = 0;
beforeEach(() => {
db = createFullTestDb();
db.exec(watchdogHistorySchema.sql); // the full test DB doesn't include the engine's audit table
db.exec(queueItemSummarySchema.sql); // nor the summary column, which the obligation face carries
const rigRepo = new RigRepository(db);
const sessions = new SessionRegistry(db);
const rig = rigRepo.createRig(RIG);
rigRepo.createRig("empty-rig");
const now = Date.parse("2026-11-07T09:00:00.000Z");
oracle = new SeatActivityService({ tmux: { readPaneLastActivity: async () => null }, defaultWindowSeconds: 3, now: () => new Date(now) });
for (const [logicalId, session] of [["dev.impl", IDLE], ["dev.qa", UNKNOWN], ["dev.ops", STOPPED]] as const) {
const node = rigRepo.addNode(rig.id, logicalId, { runtime: "claude-code" });
const registered = sessions.registerSession(node.id, session);
sessions.updateStatus(registered.id, "running");
if (session === UNKNOWN) continue;
oracle.declareRungInventory({ seatNodeId: node.id, sessionName: session }, {
adapterId: "claude-code-adapter", runtime: "claude-code",
rungs: [{ rung: "window-sampling", lifecycleCoverage: "full", initialTrust: "authoritative" }],
});
oracle.reportEvidence({ seatNodeId: node.id, sessionName: session, rung: "window-sampling",
sourceId: "tmux:window-activity", seq: 1, observedAt: new Date(now).toISOString(), activity: "idle-at-prompt" });
if (session === STOPPED) {
sessions.updateBinding(node.id, { tmuxSession: session, tmuxPane: "%7", attachmentType: "tmux" } as never);
new SeatIdentityStore(db).upsert({
nodeId: node.id, sessionName: session, verdict: "pane_missing", reason: "session_missing", evidenceSource: "tmux_session",
evidence: { registeredPane: "%7", observedPid: null, observedCommand: null, matchedLayer: null },
observedAt: new Date(Date.now() + 1000).toISOString(),
} as never);
}
}
const gen = (session: string) => sessions.currentOccupantGenerationForSession(session);
queue = new QueueRepository(db, new EventBus(db), {
validateRig: () => true,
resolveOccupantGeneration: gen,
resolveSessionAddresses: (name: string) => (name === IDLE ? [IDLE, ALIAS] : [name]),
} as never);
jobs = new WatchdogJobsRepository(db, undefined, gen);
queue.attachWatchdogJobsRepository(jobs);
history = new WatchdogHistoryLog(db);
clock = 0;
});
afterEach(() => db.close());
function app(queueRepo: unknown): Hono {
const a = new Hono();
a.use("*", async (c, next) => {
c.set("seatActivityService" as never, oracle as never);
c.set("queueRepo" as never, queueRepo as never);
c.set("rigRepo" as never, { db } as never);
await next();
});
a.route("/api/activity", activityRoutes);
return a;
}
/** The obligation reader the route used before: the full list() of the destination's open states, mapped down. */
function fullListFace() {
return {
listOpenObligations: (destinationSession: string, limit: number) =>
queue.list({ destinationSession, state: ["pending", "in-progress", "blocked"], limit })
.map((r) => ({ qitemId: r.qitemId, state: r.state, summary: r.summary ?? null })),
getParkWakeStatus: (qitemId: string) => queue.getParkWakeStatus(qitemId),
parkWakeServedByOwnReminder: (qitemId: string) => queue.parkWakeServedByOwnReminder(qitemId),
};
}
/** Both responses, as bytes; they must be equal. Returns the parsed body for the case's own assertions. */
async function sameResponse(path: string): Promise<{ status: number; body: any }> {
const lean = await app(queue).request(path);
const full = await app(fullListFace()).request(path);
const leanText = await lean.text();
expect(lean.status).toBe(full.status);
expect(leanText).toBe(await full.text());
return { status: lean.status, body: JSON.parse(leanText) };
}
async function row(destinationSession = IDLE, summary = "owed"): Promise<string> {
return (await queue.create({ sourceSession: "orch-lead@park-rig", destinationSession, body: "work", summary, nudge: false } as never)).qitemId;
}
function reminder(target = IDLE): string {
return jobs.register({
policy: "periodic-reminder",
specYaml: ["policy: periodic-reminder", "target:", ` session: ${JSON.stringify(target)}`, "message: \"check your rows\"", ""].join("\n"),
targetSession: target, intervalSeconds: 300, registeredBySession: target,
} as never).jobId;
}
function park(qitemId: string, watchdogId: string) {
queue.update({ qitemId, actorSession: IDLE, state: "blocked", blockedOn: "external:ci", transitionNote: "continuation: check CI",
wakeWatchdogId: watchdogId } as never);
}
function fire(jobId: string) {
const evaluatedAt = new Date(Date.UTC(2026, 10, 7, 3, 0, clock++)).toISOString();
history.record({ jobId, evaluatedAt, outcome: "sent", deliveryStatus: "ok", deliveryTargetSession: IDLE } as never);
queue.recordWatchdogWakeAttempt(jobId, "ok");
}
it("0 rows: the rig and the seat read the same, and the board is clean", async () => {
const rig = await sameResponse(`/api/activity/parked?rig=${RIG}`);
expect(rig.status).toBe(200);
expect(rig.body.rig.seats.find((s: { sessionName: string }) => s.sessionName === IDLE).obligations.openCount).toBe(0);
const seat = await sameResponse(`/api/activity/parked?seat=${encodeURIComponent(IDLE)}`);
expect(seat.body.seat).toMatchObject({ parked: false, confidence: { obligations: "complete" } });
});
it("1 row: the same parked verdict, item and summary", async () => {
const q = await row(IDLE, "the one owed row");
const { body } = await sameResponse(`/api/activity/parked?seat=${encodeURIComponent(IDLE)}`);
expect(body.seat.parked).toBe(true);
expect(body.seat.obligations.items).toEqual([{ qitemId: q, state: "pending", summary: "the one owed row" }]);
});
it("more than the limit (500): the same 500 rows, newest first, and truncation-possible; ties on ts_created included", async () => {
const insert = db.prepare(`INSERT INTO queue_items (qitem_id, ts_created, ts_updated, source_session, destination_session, state, body, summary)
VALUES (?, ?, ?, 'orch-lead@park-rig', ?, ?, 'work', ?)`);
const states = ["pending", "in-progress", "blocked"];
db.transaction(() => {
for (let i = 0; i < 510; i++) {
// Two rows per millisecond, so the ORDER BY ts_created DESC window has ties at its edge.
const ts = new Date(Date.UTC(2026, 10, 1, 0, 0, 0, Math.floor(i / 2))).toISOString();
insert.run(`qitem-bulk-${String(i).padStart(4, "0")}`, ts, ts, i % 7 === 0 ? ALIAS : IDLE, states[i % 3], i % 5 === 0 ? null : `row ${i}`);
}
})();
const { body } = await sameResponse(`/api/activity/parked?seat=${encodeURIComponent(IDLE)}`);
expect(body.seat.obligations.openCount + body.seat.obligations.heldCount).toBe(500);
expect(body.seat.confidence.obligations).toBe("truncation-possible");
expect(body.seat.obligations.complete).toBe(false);
});
it("a held row with a live wake reads healthy on both", async () => {
const q = await row();
park(q, reminder());
const { body } = await sameResponse(`/api/activity/parked?seat=${encodeURIComponent(IDLE)}`);
expect(body.seat.obligations.held[0]).toMatchObject({ qitemId: q, healthy: true, wake: { live: true } });
});
it("a held row served by its owner's own reminder reads the same", async () => {
const q = await row();
const job = reminder();
park(q, job);
fire(job);
const { body } = await sameResponse(`/api/activity/parked?seat=${encodeURIComponent(IDLE)}`);
expect(body.seat.obligations.held[0]).toMatchObject({ qitemId: q, healthy: true, servedByOwnReminder: true });
});
it("an indeterminate seat (no activity evidence) reads the same", async () => {
await row(UNKNOWN);
const { body } = await sameResponse(`/api/activity/parked?rig=${RIG}`);
expect(body.rig.seats.find((s: { sessionName: string }) => s.sessionName === UNKNOWN).parked).toBe("indeterminate");
});
it("a seat confirmed stopped reads the same (indeterminate despite its idle evidence)", async () => {
await row(STOPPED);
const { body } = await sameResponse(`/api/activity/parked?seat=${encodeURIComponent(STOPPED)}`);
expect(body.seat.parked).toBe("indeterminate");
});
it("a rig without seats reads the same", async () => {
const { status, body } = await sameResponse("/api/activity/parked?rig=empty-rig");
expect(status).toBe(200);
expect(body.rig).toMatchObject({ parked: false, seats: [] });
});
it("rows addressed to an alias of the seat are found the same way (resolveSessionAddresses)", async () => {
const q = await row(ALIAS, "sent to the old address");
const { body } = await sameResponse(`/api/activity/parked?seat=${encodeURIComponent(IDLE)}`);
expect(body.seat.obligations.items).toEqual([{ qitemId: q, state: "pending", summary: "sent to the old address" }]);
});
});
@@ -51,8 +51,8 @@ describe("Wave-O B2 — parked diagnosis is RIG-SCOPED with the scope named", ()
// The obligation face: ONLY dev-b@rig-b owes work.
const queueRepo = {
list: (opts: { destinationSession?: string }) =>
opts.destinationSession === "dev-b@rig-b"
listOpenObligations: (destinationSession: string) =>
destinationSession === "dev-b@rig-b"
? [{ qitemId: "qitem-b-1", state: "pending", summary: "owed in rig-b" }]
: [],
};
@@ -30,7 +30,7 @@ function makeApp(opts: { withDeps: boolean; rows?: Array<{ qitemId: string; stat
app.use("*", async (c, next) => {
if (opts.withDeps) {
c.set("seatActivityService" as never, svc as never);
c.set("queueRepo" as never, { list: () => opts.rows ?? [] } as never);
c.set("queueRepo" as never, { listOpenObligations: () => opts.rows ?? [] } as never);
c.set("rigRepo" as never, { db: { prepare: (sql: string) => ({
get: () => ({ id: "rig-1", name: "v-openrig-build" }),
all: () => (sql.includes("SELECT name FROM rigs") ? [] : [{ node_id: SEAT, session_name: SESSION }]),
@@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { mkdirSync, writeFileSync, readFileSync, existsSync, rmSync, mkdtempSync, copyFileSync, chmodSync, statSync, readdirSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { execSync, spawn, spawnSync } from "node:child_process";
import { execFileSync, spawn, spawnSync } from "node:child_process";
import { ClaudeCodeAdapter } from "../src/adapters/claude-code-adapter.js";
import { shellQuote } from "../src/adapters/shell-quote.js";
import { replaceFile } from "../src/adapters/replace-file.js";
@@ -100,7 +100,7 @@ describe("Claude Status Line Collector Script", () => {
// T1: Parses valid status line and writes sidecar
it("parses valid Claude status line JSON and writes sidecar file", () => {
const outputPath = join(tmpDir, "context", "test.json");
execSync(`echo '${VALID_STATUS_LINE}' | node ${collectorPath} ${outputPath}`, { encoding: "utf-8" });
execFileSync(process.execPath, [collectorPath, outputPath], { input: VALID_STATUS_LINE, encoding: "utf-8" });
expect(existsSync(outputPath)).toBe(true);
const content = JSON.parse(readFileSync(outputPath, "utf-8"));
@@ -128,7 +128,7 @@ describe("Claude Status Line Collector Script", () => {
// T2: Atomic write (no .tmp left behind on success)
it("writes atomically — no .tmp file left behind", () => {
const outputPath = join(tmpDir, "context", "atomic.json");
execSync(`echo '${VALID_STATUS_LINE}' | node ${collectorPath} ${outputPath}`, { encoding: "utf-8" });
execFileSync(process.execPath, [collectorPath, outputPath], { input: VALID_STATUS_LINE, encoding: "utf-8" });
expect(existsSync(outputPath)).toBe(true);
expect(existsSync(outputPath + ".tmp")).toBe(false);
@@ -165,7 +165,7 @@ describe("Claude Status Line Collector Script", () => {
it("writes to a session-specific sidecar when given a context directory", () => {
const outputDir = join(tmpDir, "context");
execSync(`echo '${VALID_STATUS_LINE}' | node ${collectorPath} ${outputDir}`, { encoding: "utf-8" });
execFileSync(process.execPath, [collectorPath, outputDir], { input: VALID_STATUS_LINE, encoding: "utf-8" });
const sessionSidecar = join(outputDir, "[email protected]");
expect(existsSync(sessionSidecar)).toBe(true);
@@ -236,7 +236,7 @@ describe("Claude Status Line Collector Script", () => {
// T3b: No output path arg — silent exit
it("silently exits with no output path argument", () => {
// Should not throw
execSync(`echo '${VALID_STATUS_LINE}' | node ${collectorPath}`, { encoding: "utf-8" });
execFileSync(process.execPath, [collectorPath], { input: VALID_STATUS_LINE, encoding: "utf-8" });
});
it("logs a shape error when a directory target cannot be resolved to a session sidecar", () => {
@@ -30,7 +30,7 @@ it("keeps a visibly running Codex turn working when the window timestamp is quie
const app = new Hono();
app.use("*", async (c, next) => {
c.set("seatActivityService" as never, oracle as never);
c.set("queueRepo" as never, { list: () => [{ qitemId: "work", state: "in-progress", summary: "Work" }] } as never);
c.set("queueRepo" as never, { listOpenObligations: () => [{ qitemId: "work", state: "in-progress", summary: "Work" }] } as never);
c.set("rigRepo" as never, { db: { prepare: (sql: string) => ({
get: () => ({ id: "rig-1", name: "fixture" }),
all: () => sql.includes("SELECT name FROM rigs") ? [] : [{ node_id: node, session_name: session }],
@@ -4,6 +4,7 @@ import { cpSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } f
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { prepareHermeticEnv, type HermeticScaffold } from "./helpers/hermetic-env.js";
import { shellQuote } from "../src/adapters/shell-quote.js";
import { runRig, spawnScenarioDaemon, type ScenarioDaemon } from "./helpers/scenario-daemon.js";
// One community harness end to end, through the real CLI, daemon, SQLite and a private tmux server: a synthetic
@@ -11,7 +12,7 @@ import { runRig, spawnScenarioDaemon, type ScenarioDaemon } from "./helpers/scen
// preset and a built-in name collision. Nothing here runs a real community harness or touches a real home.
const here = dirname(fileURLToPath(import.meta.url));
const rigBin = resolve(here, "../../cli/dist/bin-wrapper.js");
const appScript = resolve(here, "fixtures/community-harness/synth-harness.mjs");
const appFixture = resolve(here, "fixtures/community-harness/synth-harness.mjs");
const agents = resolve(here, "../../test-system/scenarios/agents");
const session = "work-worker@community-harness-rig";
const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
@@ -20,6 +21,7 @@ describe("a community harness from a data preset", () => {
let scaffold: HermeticScaffold;
let daemon: ScenarioDaemon;
let rigRoot: string;
let appScript: string;
let rigId: string;
const rig = (args: string[], timeout?: number) => runRig(args, daemon.readEnv, rigBin, timeout);
@@ -51,9 +53,11 @@ describe("a community harness from a data preset", () => {
scaffold = prepareHermeticEnv({ baseEnv: { PATH: process.env.PATH, TERM: "xterm-256color" } });
rigRoot = join(scaffold.root, "rig");
mkdirSync(rigRoot);
appScript = join(rigRoot, "synth #harness.mjs");
cpSync(appFixture, appScript);
cpSync(agents, join(rigRoot, "agents"), { recursive: true });
writePreset("synth-harness.yaml", [
"format: 1", "name: synth-harness", `command: node ${appScript}`, "maintainer: Example Maintainer",
"format: 1", "name: synth-harness", `command: ${JSON.stringify(`node ${shellQuote(appScript)}`)}`, "maintainer: Example Maintainer",
"limits:", " - resume unsupported", " - fork unsupported", " - no approval-prompt detection", "",
].join("\n"));
const previousDir = join(scaffold.env.OPENRIG_HOME!, ["agent", "apps"].join("-"));
@@ -84,7 +88,7 @@ exec "$@"
}
it("prefixes a runner-less preset with resolved literal wrapper argv", async () => {
writePreset("wrapped-plain.yaml", `format: 1\nname: wrapped-plain\ncommand: node ${appScript}\nmaintainer: Example\n`);
writePreset("wrapped-plain.yaml", `format: 1\nname: wrapped-plain\ncommand: ${JSON.stringify(`node ${shellQuote(appScript)}`)}\nmaintainer: Example\n`);
const wrapper = recordingWrapper("plain");
const up = await rig(["up", specFor("wrapped-plain-rig", "wrapped-plain", wrapper.argv), "--cwd", rigRoot, "--json", "--yes"], 120_000);
expect(up.code, JSON.stringify(up)).toBe(0);
@@ -95,7 +99,7 @@ exec "$@"
it("keeps a capable runner outside the wrapper and wraps its real native child", async () => {
const runner = resolve(here, "fixtures/runner-conformance/minimal-acp-runner.mjs");
const agent = resolve(here, "../../../docs/reference/runner-conformance/fake-acp-agent.mjs");
writePreset("wrapped-runner.yaml", `format: 1\nname: wrapped-runner\ncommand: node ${runner} --agent 'node ${agent}'\nmaintainer: Example\nrunner: true\nlaunch_result: true\nlaunch_wrapper: true\n`);
writePreset("wrapped-runner.yaml", `format: 1\nname: wrapped-runner\ncommand: ${JSON.stringify(`node ${shellQuote(runner)} --agent ${shellQuote(`node ${shellQuote(agent)}`)}`)}\nmaintainer: Example\nrunner: true\nlaunch_result: true\nlaunch_wrapper: true\n`);
const wrapper = recordingWrapper("runner");
const up = await rig(["up", specFor("wrapped-runner-rig", "wrapped-runner", wrapper.argv), "--cwd", rigRoot, "--json", "--yes"], 120_000);
expect(up.code, JSON.stringify(up)).toBe(0);
@@ -112,7 +116,7 @@ exec "$@"
const marker = join(rigRoot, "incapable-started");
const launcher = join(rigRoot, "incapable.mjs");
writeFileSync(launcher, `import { writeFileSync } from 'node:fs'; writeFileSync(${JSON.stringify(marker)}, 'started');`);
writePreset("incapable-runner.yaml", `format: 1\nname: incapable-runner\ncommand: node ${launcher}\nmaintainer: Example\nrunner: true\nlaunch_result: true\n${capability}`);
writePreset("incapable-runner.yaml", `format: 1\nname: incapable-runner\ncommand: ${JSON.stringify(`node ${shellQuote(launcher)}`)}\nmaintainer: Example\nrunner: true\nlaunch_result: true\n${capability}`);
const wrapper = recordingWrapper("incapable");
const up = await rig(["up", specFor("incapable-runner-rig", "incapable-runner", wrapper.argv), "--cwd", rigRoot, "--json", "--yes"], 120_000);
expect(up.code, JSON.stringify(up)).not.toBe(0);
@@ -148,7 +152,7 @@ exec "$@"
});
it("hands the composed first turn to a real launcher without typing it into its pane", async () => {
writePreset("synth-first.yaml", `format: 1\nname: synth-first\ncommand: node ${appScript}\nmaintainer: Example\nfirst_input: true\n`);
writePreset("synth-first.yaml", `format: 1\nname: synth-first\ncommand: ${JSON.stringify(`node ${shellQuote(appScript)}`)}\nmaintainer: Example\nfirst_input: true\n`);
const up = await rig(["up", specFor("first-input-rig", "synth-first"), "--cwd", rigRoot, "--json", "--yes"], 120_000);
expect(up.code, JSON.stringify(up)).toBe(0);
const inputDir = join(scaffold.env.OPENRIG_HOME!, "state", "first-input", "work-worker%40first-input-rig");
@@ -322,7 +326,7 @@ exec "$@"
expect(seat).not.toHaveProperty("communityHarness");
};
await check();
writePreset("free-harness.yaml", ["format: 1", "name: free-harness", `command: node ${appScript}`, "maintainer: Example Maintainer", ""].join("\n"));
writePreset("free-harness.yaml", ["format: 1", "name: free-harness", `command: ${JSON.stringify(`node ${shellQuote(appScript)}`)}`, "maintainer: Example Maintainer", ""].join("\n"));
await sleep(4_000);
await check();
// A relaunch doesn't start the same-named app in its place.
+22 -1
View File
@@ -254,7 +254,6 @@ describe("validateMarkdownAddressability — the compose gate", () => {
});
});
describe("Markdown block boundaries in context addresses", () => {
it("keeps inline backtick spans from swallowing later sections", () => {
const text = "## Setup\n```literal ` backticks```\n## Deploy\ndeploy instructions\n";
@@ -280,3 +279,25 @@ describe("Markdown block boundaries in context addresses", () => {
expect(resolveAddress(text.replace("\n#\n", "\n# Named\n"), ["independent"]).text).toContain("independent instructions");
});
});
describe("section boundaries in large documents", () => {
it("keeps sibling spans independent", () => {
const blocks = Array.from({ length: 10000 }, (_, i) => `## Section ${i}\nbody ${i}\n`);
const sections = parseMarkdownSections(blocks.join(""));
expect(sections).toHaveLength(blocks.length);
sections.forEach((section, i) => {
const expected = i === blocks.length - 1 ? blocks[i] : blocks[i]!.trimEnd();
expect(section.text).toBe(expected);
expect(section.ownText).toBe(expected);
});
});
it("ends nested spans at the nearest same-or-higher heading, including H1", () => {
const text = "## Parent\nintro\n### Child\nchild\n#### Detail\ndetail\n### Sibling\nsibling\n# Boundary\nend";
expect(parseMarkdownSections(text).map(({ title, text, ownText }) => ({ title, text, ownText }))).toEqual([
{ title: "Parent", text: "## Parent\nintro\n### Child\nchild\n#### Detail\ndetail\n### Sibling\nsibling", ownText: "## Parent\nintro" },
{ title: "Child", text: "### Child\nchild\n#### Detail\ndetail", ownText: "### Child\nchild" },
{ title: "Sibling", text: "### Sibling\nsibling", ownText: "### Sibling\nsibling" },
]);
});
});
@@ -81,6 +81,18 @@ describe("attributed proof judgments and derived readiness", () => {
expect(files.map(p => fs.readFileSync(p, "utf8"))).toEqual(before);
expect(fs.readFileSync(join(f.alpha, "proof/judgments/00000001.md"), "utf8")).toContain(a.judgment.id);
});
it("retains judgment identity and correction history after CRLF conversion", () => {
const f = fixture(), accepted = f.judge();
const receipt = join(f.alpha, "proof/judgments/00000001.md");
f.write(receipt, fs.readFileSync(receipt, "utf8").replace(/\n/g, "\r\n"));
const current = readSliceReadiness(f.alpha);
expect(current).toEqual(accepted.readiness);
const correction = f.judge(f.input(undefined, "reject"));
expect(correction.judgment.previous).toBe(accepted.judgment.id);
expect(correction.readiness.items[0]!.state).toBe("rejected");
f.write(receipt, fs.readFileSync(receipt, "utf8").replace("verdict: accept", "verdict: reject"));
expect(readSliceReadiness(f.alpha).issues.join()).toContain("changed judgment");
});
it("rejects unauthorized actors, wrong revisions and missing evidence without a receipt", () => {
const f = fixture();
expect(() => f.judge(f.input(), "other@trial")).toThrow("not a judge");
@@ -0,0 +1,379 @@
// A listing reads what every row needs once for all its rows instead of once per row: the last meaningful transitions
// in one windowed query, and (with it) the waiting view's statements, the owners' activity and the row itself. Each
// batched read must answer exactly what the per-row read answers, and a listing must project the same items.
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import Database from "better-sqlite3";
import { migrate } from "../src/db/migrate.js";
import { ALL_MIGRATIONS } from "../src/db/all-migrations.js";
import { queueItemsSchema } from "../src/db/migrations/024_queue_items.js";
import { queueTransitionsSchema } from "../src/db/migrations/025_queue_transitions.js";
import { EventBus } from "../src/domain/event-bus.js";
import { QueueRepository } from "../src/domain/queue-repository.js";
import { lastMeaningfulTransition, lastMeaningfulTransitionMany } from "../src/domain/queue-waiting.js";
let db: Database.Database;
const at = (s: number) => new Date(Date.UTC(2026, 10, 7, 9, 0, s)).toISOString();
function item(id: string, o: { state?: string; blockedOn?: string | null } = {}) {
db.prepare(`INSERT INTO queue_items(qitem_id, ts_created, ts_updated, source_session, destination_session, state, body, blocked_on)
VALUES (?, ?, ?, 'sender@r', 'worker@r', ?, 'body', ?)`).run(id, at(0), at(0), o.state ?? "pending", o.blockedOn ?? null);
}
function transition(qitemId: string, state: string, actor: string, s: number): number {
return Number(db.prepare(`INSERT INTO queue_transitions(qitem_id, ts, state, actor_session) VALUES (?, ?, ?, ?)`)
.run(qitemId, at(s), state, actor).lastInsertRowid);
}
function wake(transitionId: number, qitemId: string, phase: "armed" | "fired") {
db.prepare(`INSERT INTO queue_transition_wakes(transition_id, qitem_id, phase, wake_kind, wake_ref) VALUES (?, ?, ?, 'timer', 'job-1')`)
.run(transitionId, qitemId, phase);
}
/** The batched answer and the per-row answer, for the same ids. */
function both(ids: string[]) {
return {
many: Object.fromEntries(lastMeaningfulTransitionMany(db, ids)),
perRow: Object.fromEntries(ids.map((id) => [id, lastMeaningfulTransition(db, id)])),
};
}
describe("lastMeaningfulTransitionMany answers what lastMeaningfulTransition answers", () => {
beforeEach(() => { db = new Database(":memory:"); migrate(db, ALL_MIGRATIONS); });
afterEach(() => db.close());
it("covers a row without transitions, system actors repeating a state, a state change by a system actor, and an author note", () => {
item("q-none");
item("q-system-only"); // every transition by a system actor, same state: the first one counts
transition("q-system-only", "pending", "daemon@system", 1);
transition("q-system-only", "pending", "watchdog@system", 2);
transition("q-system-only", "pending", "wake-ladder@system", 3);
item("q-mixed"); // an author, then system bookkeeping in the same state
transition("q-mixed", "pending", "sender@r", 1);
transition("q-mixed", "in-progress", "worker@r", 2);
transition("q-mixed", "in-progress", "daemon@kernel", 3);
transition("q-mixed", "in-progress", "watchdog@system", 4);
item("q-system-moves"); // a system actor that changes the state is meaningful
transition("q-system-moves", "pending", "sender@r", 1);
transition("q-system-moves", "blocked", "worker@r", 2);
transition("q-system-moves", "pending", "daemon@system", 3);
const { many, perRow } = both(["q-none", "q-system-only", "q-mixed", "q-system-moves", "q-unknown"]);
expect(many).toEqual(perRow);
expect(many["q-none"]).toBeNull();
expect(many["q-unknown"]).toBeNull();
expect(many["q-system-only"]).not.toBeNull();
});
it("skips a transition whose wake fired, but not one whose wake is only armed", () => {
item("q-fired");
transition("q-fired", "pending", "sender@r", 1);
const parked = transition("q-fired", "blocked", "worker@r", 2);
wake(parked, "q-fired", "armed");
const fired = transition("q-fired", "pending", "worker@r", 3);
wake(fired, "q-fired", "fired");
item("q-armed");
const armed = transition("q-armed", "blocked", "worker@r", 1);
wake(armed, "q-armed", "armed");
const { many, perRow } = both(["q-fired", "q-armed"]);
expect(many).toEqual(perRow);
expect(many["q-fired"]).toEqual({ id: parked, at: at(2) });
});
it("an empty listing reads nothing and returns an empty map", () => {
const spy = vi.spyOn(db, "prepare");
expect(lastMeaningfulTransitionMany(db, []).size).toBe(0);
expect(spy).not.toHaveBeenCalled();
});
it("an id asked twice (a listed row that is also another row's blocker) maps to its one transition", () => {
item("q-blocker");
const t = transition("q-blocker", "in-progress", "worker@r", 1);
item("q-waiter", { state: "blocked", blockedOn: "q-blocker" });
transition("q-waiter", "blocked", "worker@r", 2);
const ids = ["q-blocker", "q-waiter", "q-blocker"];
const { many, perRow } = both(ids);
expect(many).toEqual(perRow);
expect(many["q-blocker"]).toEqual({ id: t, at: at(1) });
expect(lastMeaningfulTransitionMany(db, ids).size).toBe(2);
});
it("reads in a constant number of statements, whatever the number of rows", () => {
for (let i = 0; i < 40; i++) { item(`q-${i}`); transition(`q-${i}`, "pending", "sender@r", i); }
const spy = vi.spyOn(db, "prepare");
lastMeaningfulTransitionMany(db, Array.from({ length: 40 }, (_, i) => `q-${i}`));
expect(spy.mock.calls.length).toBeLessThanOrEqual(3);
});
});
describe("lastMeaningfulTransitionMany on older schemas", () => {
afterEach(() => db.close());
it("a DB without queue_transition_wakes reads every transition as the per-row read does", () => {
db = new Database(":memory:");
migrate(db, [queueItemsSchema, queueTransitionsSchema]);
item("q-a");
transition("q-a", "pending", "sender@r", 1);
transition("q-a", "pending", "watchdog@system", 2);
item("q-b");
const { many, perRow } = both(["q-a", "q-b"]);
expect(many).toEqual(perRow);
expect(many["q-a"]).not.toBeNull();
});
it("a DB without queue_transitions maps every id to null", () => {
db = new Database(":memory:");
migrate(db, [queueItemsSchema]);
item("q-a");
expect(Object.fromEntries(lastMeaningfulTransitionMany(db, ["q-a"]))).toEqual({ "q-a": lastMeaningfulTransition(db, "q-a") });
expect(lastMeaningfulTransition(db, "q-a")).toBeNull();
});
});
describe("a listing projects the same items with its shared reads as with per-row reads", () => {
let repo: QueueRepository;
beforeEach(() => {
vi.useFakeTimers({ toFake: ["Date"] });
vi.setSystemTime(new Date("2026-11-07T09:30:00.000Z"));
db = new Database(":memory:");
migrate(db, ALL_MIGRATIONS);
repo = new QueueRepository(db, new EventBus(db), { validateRig: () => true, loadHumanRegistry: () => ({ ok: true, entities: [] }) } as never);
});
afterEach(() => { vi.useRealTimers(); vi.restoreAllMocks(); db.close(); });
/** The listing as it reads now, and the same listing with its shared reads withheld (every row read alone). */
function listedBothWays(opts: Parameters<QueueRepository["list"]>[0]) {
const shared = repo.list(opts);
const own = repo as unknown as { listingReads: unknown };
own.listingReads = () => undefined;
try {
return { shared, perRow: repo.list(opts) };
} finally {
delete (own as { listingReads?: unknown }).listingReads;
}
}
async function seed() {
const make = (destinationSession: string, body: string) =>
repo.create({ sourceSession: "orch@r", destinationSession, body, nudge: false } as never).then((q) => q.qitemId);
const fresh = await make("worker@r", "fresh");
const claimed = await make("worker@r", "claimed");
repo.update({ qitemId: claimed, actorSession: "worker@r", state: "in-progress" } as never);
const blocker = await make("other@r", "the blocker");
const waiter = await make("worker@r", "waits on the blocker");
repo.update({ qitemId: waiter, actorSession: "worker@r", state: "blocked", blockedOn: blocker, transitionNote: "continuation: wait" } as never);
const timed = await make("worker@r", "parked on a timer");
repo.update({ qitemId: timed, actorSession: "worker@r", state: "blocked", blockedOn: "external:ci", transitionNote: "continuation: CI",
wakeAfterSeconds: 600 } as never);
const done = await make("worker@r", "done");
repo.update({ qitemId: done, actorSession: "worker@r", state: "done", closureReason: "no-follow-on" } as never);
return { fresh, claimed, blocker, waiter, timed, done };
}
it("full and compact listings, with blockers inside and outside the listing", async () => {
await seed();
for (const opts of [{}, { compact: true as const }, { destinationSession: "worker@r" }, { destinationSession: "worker@r", compact: true as const },
{ state: ["blocked" as const] }, { rig: "r" }]) {
const { shared, perRow } = listedBothWays(opts as never);
expect(shared).toEqual(perRow);
}
});
it("an empty listing", () => {
const { shared, perRow } = listedBothWays({});
expect(shared).toEqual([]);
expect(perRow).toEqual([]);
});
});
describe("a listing prepares the waiting view's statements once, not once per row", () => {
let repo: QueueRepository;
beforeEach(() => {
db = new Database(":memory:");
migrate(db, ALL_MIGRATIONS);
repo = new QueueRepository(db, new EventBus(db), { validateRig: () => true, loadHumanRegistry: () => ({ ok: true, entities: [] }) } as never);
});
afterEach(() => { vi.restoreAllMocks(); db.close(); });
/** How many times each kind of statement is prepared while `run` executes. */
function preparedWhile(run: () => unknown): Record<string, number> {
const prepare = db.prepare.bind(db);
const kinds: Record<string, (sql: string) => boolean> = {
pendingSince: (sql) => sql.includes("SELECT ts, previous_state FROM"),
blocker: (sql) => sql.includes("SELECT destination_session, state FROM queue_items WHERE qitem_id = ?"),
timer: (sql) => sql.includes("FROM queue_transition_wakes w JOIN watchdog_jobs j"),
waitingRow: (sql) => sql.includes("SELECT qitem_id, source_session, destination_session, state, blocked_on, closure_required_at, tags"),
schemaCheck: (sql) => /sqlite_master WHERE name = (\?|'queue_transitions'|'queue_transition_wakes')/.test(sql),
};
const counts = Object.fromEntries(Object.keys(kinds).map((k) => [k, 0]));
const spy = vi.spyOn(db, "prepare").mockImplementation(((sql: string) => {
for (const [kind, match] of Object.entries(kinds)) if (match(sql)) counts[kind]!++;
return prepare(sql);
}) as typeof db.prepare);
try { run(); } finally { spy.mockRestore(); }
return counts;
}
it("30 pending rows, 30 rows blocked on another row and 30 parked on a timer", async () => {
const make = (body: string) => repo.create({ sourceSession: "orch@r", destinationSession: "worker@r", body, nudge: false } as never)
.then((q) => q.qitemId);
for (let i = 0; i < 30; i++) {
await make(`pending ${i}`);
const blocker = await make(`blocker ${i}`);
repo.update({ qitemId: await make(`waits ${i}`), actorSession: "worker@r", state: "blocked", blockedOn: blocker, transitionNote: "continuation: wait" } as never);
repo.update({ qitemId: await make(`timed ${i}`), actorSession: "worker@r", state: "blocked", blockedOn: "external:ci", transitionNote: "continuation: CI",
wakeAfterSeconds: 600 } as never);
}
const counts = preparedWhile(() => repo.list({ limit: 500 }));
expect(counts.pendingSince).toBeLessThanOrEqual(1);
expect(counts.blocker).toBeLessThanOrEqual(1);
expect(counts.timer).toBeLessThanOrEqual(1);
expect(counts.waitingRow).toBeLessThanOrEqual(1);
// lastMeaningfulTransitionMany's two checks and the waiting statements' one per table, whatever the row count.
expect(counts.schemaCheck).toBeLessThanOrEqual(4);
});
});
describe("a listing reads each owner's activity once", () => {
let repo: QueueRepository;
beforeEach(() => {
vi.useFakeTimers({ toFake: ["Date"] });
vi.setSystemTime(new Date("2026-11-07T09:30:00.000Z"));
db = new Database(":memory:");
migrate(db, ALL_MIGRATIONS);
repo = new QueueRepository(db, new EventBus(db), { validateRig: () => true, loadHumanRegistry: () => ({ ok: true, entities: [] }) } as never);
});
afterEach(() => { vi.useRealTimers(); vi.restoreAllMocks(); db.close(); });
const idle = { activity: "idle-at-prompt", needsInput: { count: 0, reason: null }, decidedBy: "window-sampling" };
const make = (destinationSession: string) =>
repo.create({ sourceSession: "orch@r", destinationSession, body: "work", nudge: false } as never).then((q) => q.qitemId);
it("one read per distinct session, for the pickup receipt and the waiting view alike", async () => {
for (let i = 0; i < 10; i++) { await make("a@r"); await make("b@r"); }
const reader = vi.fn((session: string) => (session ? idle : null) as never);
repo.attachActivityReader(reader);
repo.list({});
expect(reader.mock.calls.map(([s]) => s).sort()).toEqual(["a@r", "b@r"]);
repo.list({ compact: true });
expect(reader).toHaveBeenCalledTimes(4); // a new listing reads again
});
it("a reader that throws for one session leaves the other sessions' rows as they were, and that session's rows unknown", async () => {
await make("ok@r"); await make("broken@r"); await make("ok@r");
repo.attachActivityReader((session) => { if (session === "broken@r") throw new Error("fixture: seat state unreadable"); return idle as never; });
const shared = repo.list({});
const own = repo as unknown as { listingReads: unknown };
own.listingReads = () => undefined;
const perRow = repo.list({});
delete (own as { listingReads?: unknown }).listingReads;
expect(shared).toEqual(perRow);
const liveness = (owner: string) => shared.filter((i) => i.destinationSession === owner).map((i) => i.waiting?.liveness.confidence);
expect(liveness("ok@r")).toEqual(["oracle", "oracle"]);
expect(liveness("broken@r")).toEqual(["unknown"]);
});
});
describe("a listed row's waiting view reads the row in hand instead of reading it again", () => {
let repo: QueueRepository;
beforeEach(() => {
vi.useFakeTimers({ toFake: ["Date"] });
vi.setSystemTime(new Date("2026-11-07T09:30:00.000Z"));
db = new Database(":memory:");
migrate(db, ALL_MIGRATIONS);
repo = new QueueRepository(db, new EventBus(db), { validateRig: () => true, loadHumanRegistry: () => ({ ok: true, entities: [] }) } as never);
});
afterEach(() => { vi.useRealTimers(); vi.restoreAllMocks(); db.close(); });
const make = (destinationSession: string, body = "work") =>
repo.create({ sourceSession: "orch@r", destinationSession, body, nudge: false } as never).then((q) => q.qitemId);
const rereads = (sql: string) =>
sql.includes("SELECT qitem_id, source_session, destination_session, state, blocked_on, closure_required_at, tags")
|| /SELECT state, (human_intent|NULL AS human_intent), destination_session, last_nudge_result FROM queue_items WHERE qitem_id = \?/.test(sql)
|| sql.includes("SELECT last_nudge_result FROM queue_items WHERE qitem_id = ?");
it("prepares none of the waiting view's own-row reads, and projects what the per-row read projects", async () => {
const plain = await make("worker@r");
const held = await make("worker@r");
db.prepare("UPDATE queue_items SET last_nudge_result = 'retained:typing_guard' WHERE qitem_id = ?").run(held);
const claimed = await make("worker@r");
repo.update({ qitemId: claimed, actorSession: "worker@r", state: "in-progress" } as never);
const prepare = db.prepare.bind(db);
let n = 0;
const spy = vi.spyOn(db, "prepare").mockImplementation(((sql: string) => { if (rereads(sql)) n++; return prepare(sql); }) as typeof db.prepare);
const shared = repo.list({});
spy.mockRestore();
expect(n).toBe(0);
const own = repo as unknown as { listingReads: unknown };
own.listingReads = () => undefined;
const perRow = repo.list({});
delete (own as { listingReads?: unknown }).listingReads;
expect(shared).toEqual(perRow);
expect(shared.find((i) => i.qitemId === held)?.waiting?.nextBackstop).toMatchObject({ mechanism: "held by the seat's typing guard", held: true });
expect(shared.find((i) => i.qitemId === plain)?.waiting?.state).toBe("pending");
});
it("a single-row read outside a listing still reads its own row", async () => {
const q = await make("worker@r");
const prepare = db.prepare.bind(db);
let n = 0;
const spy = vi.spyOn(db, "prepare").mockImplementation(((sql: string) => { if (rereads(sql)) n++; return prepare(sql); }) as typeof db.prepare);
expect(repo.waitingView(q)?.state).toBe("pending");
spy.mockRestore();
expect(n).toBeGreaterThan(0);
});
});
describe("show (getById) is the listing projection of one row", () => {
let repo: QueueRepository;
beforeEach(() => {
vi.useFakeTimers({ toFake: ["Date"] });
vi.setSystemTime(new Date("2026-11-07T09:30:00.000Z"));
db = new Database(":memory:");
migrate(db, ALL_MIGRATIONS);
repo = new QueueRepository(db, new EventBus(db), { validateRig: () => true, loadHumanRegistry: () => ({ ok: true, entities: [] }) } as never);
});
afterEach(() => { vi.useRealTimers(); vi.restoreAllMocks(); db.close(); });
/** getById as it read before: the per-row projection, then the delivery outcome read on its own. */
function getByIdPerRow(qitemId: string) {
const own = repo as unknown as { rowToItem: (row: unknown) => Record<string, unknown> };
const row = db.prepare("SELECT * FROM queue_items WHERE qitem_id = ?").get(qitemId);
const item = own.rowToItem.call(repo, row);
const ledger = repo.deliveryOutcomeFor(qitemId);
return { ...item, deliveryOutcome: ledger?.outcome ?? null, ...(ledger && ledger.outcome !== "posted" && ledger.outcome !== "accepted" ? { deliveryFailureDetail: ledger.detail } : {}) };
}
it("answers what the per-row projection answered: pending, in-progress, blocked, parked, done and handed-off", async () => {
const make = (destinationSession: string, body: string) =>
repo.create({ sourceSession: "orch@r", destinationSession, body, nudge: false } as never).then((q) => q.qitemId);
const fresh = await make("worker@r", "fresh");
const claimed = await make("worker@r", "claimed");
repo.update({ qitemId: claimed, actorSession: "worker@r", state: "in-progress" } as never);
const blocker = await make("other@r", "the blocker");
const waiter = await make("worker@r", "waits");
repo.update({ qitemId: waiter, actorSession: "worker@r", state: "blocked", blockedOn: blocker, transitionNote: "continuation: wait" } as never);
const timed = await make("worker@r", "timer");
repo.update({ qitemId: timed, actorSession: "worker@r", state: "blocked", blockedOn: "external:ci", transitionNote: "continuation: CI",
wakeAfterSeconds: 600 } as never);
const done = await make("worker@r", "done");
repo.update({ qitemId: done, actorSession: "worker@r", state: "done", closureReason: "no-follow-on" } as never);
const passed = await make("worker@r", "passed on");
const { created: successor } = await repo.handoff({ qitemId: passed, fromSession: "worker@r", toSession: "next@r", nudge: false });
const ids = [fresh, claimed, blocker, waiter, timed, done, passed, successor.qitemId];
// Each state the face can show is here, so the comparison below covers it.
expect(ids.map((id) => repo.getById(id)?.state)).toEqual(
["pending", "in-progress", "pending", "blocked", "blocked", "done", "handed-off", "pending"]);
expect(repo.getById(timed)?.pickup?.state).toBe("parked");
for (const id of ids) expect(repo.getById(id)).toEqual(getByIdPerRow(id));
expect(repo.getById("qitem-missing")).toBeNull();
});
it("reads the row's last meaningful transition once, not once for the pickup and once for the waiting view", async () => {
const q = (await repo.create({ sourceSession: "orch@r", destinationSession: "worker@r", body: "work", nudge: false } as never)).qitemId;
const prepare = db.prepare.bind(db);
let n = 0;
const spy = vi.spyOn(db, "prepare").mockImplementation(((sql: string) => {
if (sql.includes("actor_session NOT IN ('watchdog@system'")) n++;
return prepare(sql);
}) as typeof db.prepare);
repo.getById(q);
spy.mockRestore();
expect(n).toBe(1);
});
});
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import { execFile, spawnSync } from "node:child_process";
import { resolve } from "node:path";
import { promisify } from "node:util";
import { shellQuote } from "../src/adapters/shell-quote.js";
// The runner conformance check that ships in docs/reference, run against a test double that keeps the contract, and
// against copies of it that each break one promise: the check must pass the first and catch every break.
@@ -15,7 +16,7 @@ type Result = { id: string; status: "pass" | "fail" | "skip" | "advisory"; met?:
type Report = { results: Result[]; summary: { pass: number; fail: number; skip: number; advisory: number } };
async function check(args: string[], runnerFlags = "", nodeArgs: string[] = [], timeoutMs = 90_000): Promise<{ code: number; report: Report }> {
const runner = `${process.execPath} ${RUNNER} --agent {agent}${runnerFlags}`;
const runner = `${shellQuote(process.execPath)} ${shellQuote(RUNNER)} --agent {agent}${runnerFlags}`;
try {
const { stdout } = await promisify(execFile)(process.execPath, [...nodeArgs, CHECK, "--runner", runner, "--name", "minimal", "--json", ...args], { timeout: timeoutMs });
return { code: 0, report: JSON.parse(stdout) };
+3 -69
View File
@@ -1,14 +1,9 @@
import { describe, it, expect, afterEach, vi } from "vitest";
import { mkdtempSync, rmSync, writeFileSync, openSync, writeSync, closeSync, statSync, readSync } from "node:fs";
import { describe, it, expect, afterEach } from "vitest";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { parseJsonlExchanges } from "../src/domain/session-jsonl.js";
vi.mock("node:fs", async (importOriginal) => {
const fs = await importOriginal<typeof import("node:fs")>();
return { ...fs, readSync: vi.fn(fs.readSync) };
});
// Seat-handover boot recap: read the PROVIDER session JSONL (claude sidecar
// transcript_path / codex rollout_path) into the last-N {role, content} exchanges for the boot recap.
// Defensive / honest-degraded: metadata + thinking/tool_use-only lines carry no user text and are
@@ -16,7 +11,7 @@ vi.mock("node:fs", async (importOriginal) => {
// claude-projects line shape ({type,message:{role,content}}; content string OR [{type,text}] blocks).
const dirs: string[] = [];
afterEach(() => { vi.mocked(readSync).mockReset(); for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true }); });
afterEach(() => { for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true }); });
function fixture(lines: unknown[]): string {
const d = mkdtempSync(join(tmpdir(), "sj-"));
dirs.push(d);
@@ -91,67 +86,6 @@ describe("parseJsonlExchanges — claude-projects role/content shape", () => {
});
});
describe("recap reads bounded chunks from the end", () => {
it("reads the last six exchanges from a sparse transcript larger than the string limit", () => {
const p = fixture([]);
const expected = Array.from({ length: 6 }, (_, i) => ({ role: "user", content: String(i) }));
const tail = "\n" + expected.map((message) => JSON.stringify({ message })).join("\n")
+ '\n{"type":"metadata"}\n{partial';
const fd = openSync(p, "w");
try {
writeSync(fd, tail, 512 * 1024 * 1024 + 4096);
} finally {
closeSync(fd);
}
expect(statSync(p).size).toBeGreaterThan(512 * 1024 * 1024);
expect(parseJsonlExchanges(p, 6)).toEqual(expected);
const requestedBytes = vi.mocked(readSync).mock.calls.reduce((sum, call) => sum + Number(call[3] ?? 0), 0);
expect(requestedBytes).toBeLessThan(1024 * 1024);
});
it.each(["", "\n", "\r\n"])("keeps UTF-8 and whole records across chunks with ending %j", (ending) => {
const p = fixture([]);
const long = "🍵é".repeat(40_000);
const expected = [{ role: "user", content: "first" }, { role: "assistant", content: long }];
writeFileSync(p, [
JSON.stringify({ message: expected[0] }),
'{"message":{"role":"user",', // Physical JSONL fragments stay corrupt; never join separate lines.
'"content":"not an exchange"}}',
JSON.stringify({ message: expected[1] }),
JSON.stringify({ type: "metadata", padding: "x".repeat(150_000) }),
"{partial",
].join("\r\n") + ending);
expect(parseJsonlExchanges(p, 6)).toEqual(expected);
expect(parseJsonlExchanges(p, 1)).toEqual(expected.slice(-1));
});
it("completes a short positional read before decoding", async () => {
const fs = await vi.importActual<typeof import("node:fs")>("node:fs");
const p = fixture([{ message: { role: "user", content: "🍵" } }]);
vi.mocked(readSync).mockImplementationOnce((fd, buffer) => fs.readSync(fd, buffer, 0, 1, 0));
expect(parseJsonlExchanges(p, 6)).toEqual([{ role: "user", content: "🍵" }]);
expect(readSync).toHaveBeenCalledTimes(2);
});
it("returns no partial recap if the file becomes unreadable after a visible exchange", async () => {
const fs = await vi.importActual<typeof import("node:fs")>("node:fs");
const p = fixture([
{ message: { role: "user", content: "first" } },
{ type: "metadata", padding: "x".repeat(150_000) },
{ message: { role: "assistant", content: "last" } },
]);
vi.mocked(readSync).mockImplementationOnce(fs.readSync).mockReturnValueOnce(0);
expect(parseJsonlExchanges(p, 6)).toEqual([]);
expect(readSync).toHaveBeenCalledTimes(2);
});
it("reports no exchanges for an unreadable record or metadata-only chunks", () => {
const p = fixture([{ type: "metadata", padding: "x".repeat(150_000) }]);
expect(parseJsonlExchanges(p, 6)).toEqual([]);
expect(parseJsonlExchanges(join(p, ".."), 6)).toEqual([]);
});
});
describe("recap tail boundaries", () => {
it.each(["", "\n", "\r\n"])("keeps chronological order with trailing %j", (ending) => {
const p = fixture([]);
+3 -1
View File
@@ -1,4 +1,6 @@
import { existsSync, realpathSync } from "node:fs";
import { execFileSync } from "node:child_process";
import { fileURLToPath } from "node:url";
/**
* Exact tracked docs paths allowed outside the three durable roots.
@@ -57,6 +59,6 @@ export function main() {
process.exitCode = 1;
}
if (import.meta.url === `file://${process.argv[1]}`) {
if (process.argv[1] && existsSync(process.argv[1]) && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) {
main();
}
+3 -2
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env node
import { execFileSync } from "node:child_process";
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync } from "node:fs";
import { dirname, isAbsolute, relative, resolve } from "node:path";
import {
buildInternalLeakMessage,
@@ -200,7 +201,7 @@ function splitNul(bytes) {
.sort();
}
if (import.meta.url === `file://${resolve(process.argv[1])}`) {
if (process.argv[1] && existsSync(process.argv[1]) && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) {
try {
process.exitCode = main();
} catch (error) {
+3 -1
View File
@@ -1,4 +1,5 @@
import { execFileSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import { createHash } from "node:crypto";
import {
chmodSync,
@@ -8,6 +9,7 @@ import {
mkdtempSync,
readFileSync,
readdirSync,
realpathSync,
rmSync,
lstatSync,
writeFileSync,
@@ -555,7 +557,7 @@ export async function main(argv = process.argv.slice(2), dependencies = {}) {
}
}
if (import.meta.url === `file://${resolve(process.argv[1])}`) {
if (process.argv[1] && existsSync(process.argv[1]) && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) {
try {
await main();
} catch (err) {
+84
View File
@@ -0,0 +1,84 @@
import test from "node:test";
import assert from "node:assert/strict";
import { execFileSync, spawnSync } from "node:child_process";
import { copyFileSync, mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
const entrypoints = ["check-docs-guard.mjs", "check-internal-leak-guard.mjs", "mirror-skills.mjs"];
function fixture(t) {
const root = mkdtempSync(join(tmpdir(), "openrig scripts # "));
t.after(() => rmSync(root, { recursive: true, force: true }));
const scripts = join(root, "scripts");
mkdirSync(scripts);
for (const file of [...entrypoints, "internal-leak-scanner.mjs", "leak-guard-rules.mjs"]) {
copyFileSync(new URL(file, import.meta.url), join(scripts, file));
}
const env = { ...process.env };
delete env.OPENRIG_LEAK_GUARD_RULES;
return {
root,
run: (file, ...args) => spawnSync(process.execPath, [join(scripts, file), ...args], {
cwd: root, encoding: "utf8", env,
}),
};
}
test("docs guard rejects tracked plans from a checkout path containing spaces and #", (t) => {
const { root, run } = fixture(t);
mkdirSync(join(root, "docs/plans"), { recursive: true });
writeFileSync(join(root, "docs/plans/example.md"), "local plan\n");
execFileSync("git", ["init", "-q"], { cwd: root });
execFileSync("git", ["add", "."], { cwd: root });
const result = run("check-docs-guard.mjs");
assert.equal(result.status, 1, result.stderr);
assert.match(result.stderr, /docs\/plans\/example\.md/);
});
test("leak guard reports a missing rules file instead of silently succeeding", (t) => {
const { root, run } = fixture(t);
const result = run("check-internal-leak-guard.mjs", "--repo", root, "--rules", join(root, "missing.json"), "--mode", "full");
assert.equal(result.status, 1, result.stderr);
assert.match(result.stderr, /Rules file not found/);
});
test("mirror check reports missing generated inputs instead of silently succeeding", (t) => {
const { run } = fixture(t);
const result = run("mirror-skills.mjs", "--check");
assert.equal(result.status, 1, result.stderr);
assert.match(result.stderr, /ENOENT.*skill-edge-layout\.generated\.json/);
});
for (const file of entrypoints) {
for (const args of [[], ["missing-entrypoint.mjs"]]) {
test(`${file} can be imported with ${args.length ? "a nonexistent" : "no"} entrypoint argument`, (t) => {
const { root } = fixture(t);
const url = pathToFileURL(join(root, "scripts", file)).href;
const result = spawnSync(process.execPath, ["--input-type=module", "-e", `await import(${JSON.stringify(url)})`, ...args], {
cwd: root, encoding: "utf8",
});
assert.equal(result.status, 0, result.stderr);
assert.equal(result.stdout, "");
assert.equal(result.stderr, "");
});
}
}
for (const [file, args, error] of [
["check-docs-guard.mjs", [], /not a git repository/],
["check-internal-leak-guard.mjs", ["--repo", ".", "--rules", "missing.json", "--mode", "full"], /Rules file not found/],
["mirror-skills.mjs", ["--check"], /ENOENT.*skill-edge-layout\.generated\.json/],
]) {
test(`${file} runs through a preserved symlink`, (t) => {
const { root } = fixture(t);
const link = join(root, "scripts", "linked.mjs");
symlinkSync(file, link);
const result = spawnSync(process.execPath, ["--preserve-symlinks-main", link, ...args], {
cwd: root, encoding: "utf8",
});
assert.equal(result.status, 1, result.stderr);
assert.match(result.stderr, error);
});
}