tickmarkr 2.6.7 → 2.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
1
2
  import { existsSync, linkSync, mkdirSync, readdirSync, readFileSync, readlinkSync, realpathSync, renameSync, rmdirSync, rmSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
2
3
  import { randomUUID } from "node:crypto";
3
4
  import { dirname, join, resolve } from "node:path";
@@ -77,6 +78,8 @@ export class OrcaError extends Error {
77
78
  code;
78
79
  runtimeId;
79
80
  launchCause;
81
+ /** v2.6.8 T1: the CLI was killed at the timeout it was handed — the one evidence that a deadline interrupted it. */
82
+ timedOut;
80
83
  constructor(family, reason, raw, opts = {}) {
81
84
  super(`orca ${family} failed — ${reason}; raw response:\n${raw}`);
82
85
  this.family = family;
@@ -86,6 +89,7 @@ export class OrcaError extends Error {
86
89
  this.code = opts.code;
87
90
  this.runtimeId = opts.runtimeId;
88
91
  this.launchCause = opts.launchCause;
92
+ this.timedOut = opts.timedOut;
89
93
  }
90
94
  }
91
95
  /** The slot cannot be addressed: dead/unknown terminal record, or a handle that cannot be recovered
@@ -209,7 +213,9 @@ export const CHECKOUT_MARK = "TICKMARKR_CHECKOUT";
209
213
  // carries no whitespace or quotes, survives renderer wrapping (hex rows re-join losslessly), and is
210
214
  // either complete (length matches, terminator present) or nothing. A prefix of another checkout can
211
215
  // never decode to this one; `A` versus `A B`, `…--T1` versus `…--T10` are different frames.
212
- const CHECKOUT_FRAME_RE = /TICKMARKR_CHECKOUT (\d+):([0-9a-f]*)(;?)/g;
216
+ // v2.6.8 T1: the header separator is optional — a terminal that wraps the frame AT it leaves the space
217
+ // at a row edge, where joinWrapped trims it — so where the frame wraps never decides what it is.
218
+ const CHECKOUT_FRAME_RE = /TICKMARKR_CHECKOUT ?(\d+):([0-9a-f]*)(;?)/g;
213
219
  const PROOF_PAGES = 16; // pages read from the oldest cursor before the proof is declared absent
214
220
  // E1: the ceiling for the wrapper shell's startup proof. A loaded host can print a review seat's proof
215
221
  // long after the old 2 s window closed (the window a healthy seat was retired on); a proof first
@@ -228,8 +234,110 @@ const MARK_TRACE = CHECKOUT_MARK.slice(0, CHECKOUT_MARK.indexOf("_") + 2);
228
234
  function misplacedProof(text, checkout) {
229
235
  const { complete, incomplete } = checkoutFrames(text);
230
236
  const joined = joinWrapped(text);
237
+ const frames = [...joined.matchAll(CHECKOUT_FRAME_RE)];
231
238
  return incomplete || complete.some((c) => c !== checkout)
232
- || joined.split(MARK_TRACE).length - 1 > [...joined.matchAll(CHECKOUT_FRAME_RE)].length;
239
+ || joined.split(MARK_TRACE).length - 1 > frames.length || danglingMarkRow(text, joined, frames);
240
+ }
241
+ /** v2.6.8 T1: a row that is only a mark prefix too short to carry MARK_TRACE (`T` … `TICKMARKR_`) — the
242
+ * same partial frame ceilingCutFrame types as a cut — is placement evidence unless the rejoined text
243
+ * continues it into the mark there, or it lies inside a frame a wrap split (`…CHECKOU` | `T` | ` 40:…;`).
244
+ * ponytail: a long line whose last wrapped row is just `T` refuses too; fail-closed costs a relaunch. */
245
+ function danglingMarkRow(text, joined, frames) {
246
+ let at = 0;
247
+ for (const row of text.split("\n").map(joinWrapped)) {
248
+ const start = at;
249
+ at += row.length;
250
+ if (row.length === 0 || row.length >= MARK_TRACE.length || !MARK_TRACE.startsWith(row))
251
+ continue;
252
+ if (joined.startsWith(MARK_TRACE, start))
253
+ continue;
254
+ if (!frames.some((m) => m.index < start && start < m.index + m[0].length))
255
+ return true;
256
+ }
257
+ return false;
258
+ }
259
+ /** v2.6.8 T1: is `fragment` (rows rejoined) a strict prefix of the OWN proof frame `own` — the frame the
260
+ * unread rows could still complete into the proof? The separator is optional exactly as in
261
+ * CHECKOUT_FRAME_RE. A prefix no continuation can make the own frame (`TICKMARKR_CHECKOUT 1:ff`, a foreign
262
+ * length or hex) is placement evidence whatever the unread rows hold — never a cut. */
263
+ function framePrefix(fragment, own) {
264
+ return fragment.length > 0 && [own, own.replace(" ", "")].some((frame) => fragment.length < frame.length && frame.startsWith(fragment));
265
+ }
266
+ /** v2.6.8 T1: the evidence that paging left a frame unread — the paged text ENDS in a strict prefix of
267
+ * the own frame (cutFrameRow), and nothing read before it — the pages' reading of a position or the anchor's —
268
+ * nor the anchor's rows past it, is placement evidence. A malformed marker read whole is never a prefix,
269
+ * so it stays a placement refusal. A row that merely continues the cut frame (`TICKMARKR_CHECKOU`, then
270
+ * `T`) is never taken for a new frame.
271
+ * ponytail: the typed command's echo cut exactly after its `TICKMARKR` printf argument reads the same,
272
+ * and so does a last row that merely reads `T`; either way the proof line is unread at the ceiling. */
273
+ function ceilingCutFrame(pages, tail, checkout) {
274
+ const k = cutFrameRow(pages.rows, checkoutProofLine(checkout));
275
+ if (k === undefined)
276
+ return false;
277
+ // The anchor's rows past the pages' last position are the cut frame's continuation (`…CHECKOU` | `T` |
278
+ // ` 40:…;`), read after it; an anchor past a gap, or at an unknown position, stands alone and must read
279
+ // clean.
280
+ const beyond = tailBeyond(pages, tail), cut = pages.rows.slice(k);
281
+ const continued = beyond === undefined ? !misplacedProof(tail.rows.join("\n"), checkout)
282
+ : beyond.length === 0 || !misplacedProof([...cut, ...beyond].join("\n"), checkout);
283
+ if (!continued || misplacedProof(pages.rows.slice(0, k).join("\n"), checkout))
284
+ return false;
285
+ if (beyond === undefined)
286
+ return true;
287
+ // An anchor sharing positions with the pages is a SECOND reading of those rows, read WHOLE before them:
288
+ // it is classified in place and whole — only the pages' own cut rows past it are dropped — so a foreign,
289
+ // malformed or unterminated mark the anchor read where a later page read harmless text or the cut frame
290
+ // is never forgotten, and a trailing prefix in the anchor's own reading never inherits the pages' cut.
291
+ const from = tail.at - pages.at, ownEnd = Math.max(0, from + tail.rows.length);
292
+ const anchored = [...pages.rows.slice(0, Math.max(0, from)), ...tail.rows, ...pages.rows.slice(ownEnd, Math.max(k, ownEnd))];
293
+ return !misplacedProof(anchored.join("\n"), checkout);
294
+ }
295
+ /** v2.6.8 T1: the row a trailing strict prefix of the own frame starts at, or undefined. A frame starts its own row
296
+ * and the terminal may wrap it anywhere, so rows are rejoined first and the cut frame is the LONGEST run
297
+ * of trailing rows that rejoins into a prefix; only the trailing frame-alphabet run can hold it. */
298
+ function cutFrameRow(rows, own) {
299
+ const joinedRows = rows.map(joinWrapped), joined = joinedRows.join("");
300
+ let run = joined.length;
301
+ while (run > 0 && /[A-Z0-9a-f_ :]/.test(joined[run - 1]))
302
+ run--;
303
+ for (let k = 0, at = 0; k < rows.length; at += joinedRows[k].length, k++) {
304
+ if (joinedRows[k] && at >= run && framePrefix(joined.slice(at), own))
305
+ return k;
306
+ }
307
+ return undefined;
308
+ }
309
+ /** A row the read returned but that is not text: kept in its position so it never joins its neighbours. */
310
+ const UNREADABLE_ROW = "�";
311
+ function linePosition(cursor) {
312
+ return typeof cursor === "string" && /^\d+$/.test(cursor) ? Number(cursor) : undefined;
313
+ }
314
+ /** v2.6.8 T1: the anchor's rows past the pages, by ROW IDENTITY — scrollback position, never text: none when
315
+ * the anchor lies inside the pages (the scrollback may have grown between the reads), the rest when it
316
+ * overlaps or abuts their end, undefined when a gap separates them or either position is unknown. */
317
+ function tailBeyond(pages, tail) {
318
+ if (pages.at === undefined || tail.at === undefined)
319
+ return undefined;
320
+ const end = pages.at + pages.rows.length;
321
+ if (tail.at > end || tail.at + tail.rows.length < pages.at)
322
+ return undefined;
323
+ return tail.rows.slice(Math.max(0, end - tail.at));
324
+ }
325
+ /** v2.6.8 T1: the anchor read in place among the paged rows: the pages' rows before its position, every one
326
+ * of its own rows, then the pages' rows after it. An anchor sharing or abutting no position with the
327
+ * pages, or at an unknown one, stands alone (fail-closed). The pages' own reading is classified too. */
328
+ function tailInContext(pages, tail) {
329
+ if (tailBeyond(pages, tail) === undefined)
330
+ return tail.rows.join("\n");
331
+ const from = tail.at - pages.at;
332
+ return [...pages.rows.slice(0, Math.max(0, from)), ...tail.rows, ...pages.rows.slice(Math.max(0, from + tail.rows.length))].join("\n");
333
+ }
334
+ /** v2.6.8 T1: the caller's live launch check, consulted by create at its actual submission — after the runtime
335
+ * and worktree lookups it awaits, the last moment before a terminal is launched. A throwing guard withdraws
336
+ * the launch (nothing is created) and its error is the launch failure. Async-scoped, so it reaches create
337
+ * through every driver wrapper that forwards run. */
338
+ const launchGuards = new AsyncLocalStorage();
339
+ export function withLaunchGuard(guard, run) {
340
+ return launchGuards.run(guard, run);
233
341
  }
234
342
  /** The exact bytes the create command prints as its first line. */
235
343
  export function checkoutProofLine(checkout) {
@@ -257,8 +365,8 @@ export function checkoutFrames(text) {
257
365
  * frame names anything else, and no frame is incomplete. Full-path equality after canonicalization —
258
366
  * never a prefix, a substring, or a whitespace-terminated fragment. */
259
367
  export function provesCheckout(text, checkout) {
260
- const { complete, incomplete } = checkoutFrames(text);
261
- return !incomplete && complete.includes(checkout) && complete.every((c) => c === checkout);
368
+ // v2.6.8 T1: a malformed mark the frame grammar cannot parse is placement evidence too (misplacedProof).
369
+ return checkoutFrames(text).complete.includes(checkout) && !misplacedProof(text, checkout);
262
370
  }
263
371
  /**
264
372
  * Everything a terminal on the tracked worktree runs before the payload: enter the checkout (a
@@ -725,7 +833,7 @@ export class OrcaDriver {
725
833
  throw new OrcaError(family, e.reason, raw, { code: e.code, runtimeId: e.runtimeId });
726
834
  }
727
835
  }
728
- throw new OrcaError(family, `orca CLI exited ${r.code}${r.timedOut ? " after timeout" : ""}`, raw);
836
+ throw new OrcaError(family, `orca CLI exited ${r.code}${r.timedOut ? " after timeout" : ""}`, raw, r.timedOut === true ? { timedOut: true } : {});
729
837
  }
730
838
  return parseEnvelope(family, r.stdout, raw);
731
839
  }
@@ -859,6 +967,7 @@ export class OrcaDriver {
859
967
  // into the checkout, because Orca cannot be asked to place a terminal in a path it does not
860
968
  // track (OBS-1004). Shell startup can swallow that prefix; prove it from scrollback below.
861
969
  const payload = `export ${FORK_CAP_ENV}=${shq(process.env[FORK_CAP_ENV] ?? resolvedForkCap())}; ${cmd}`;
970
+ launchGuards.getStore()?.(); // v2.6.8 T1: eligibility is re-read here, after every awaited lookup above
862
971
  const env = await this.call("create", [
863
972
  "terminal", "create", "--worktree", `path:${tracked}`, "--title", st.title, "--command", inCheckout(st.cwd, payload),
864
973
  ], this.cliCwd(st));
@@ -892,7 +1001,9 @@ export class OrcaDriver {
892
1001
  let misplaced, unread;
893
1002
  let proof = await this.checkoutProven(handle, st.cwd, this.cliCwd(st), env.runtimeId, deadline);
894
1003
  for (;;) {
895
- if (proof.proven)
1004
+ // v2.6.8 T1: placement evidence is sticky — a later poll whose scrollback no longer shows the
1005
+ // foreign, malformed or partial mark an earlier one read never turns it into proof.
1006
+ if (proof.proven && misplaced === undefined)
896
1007
  break;
897
1008
  if (proof.seen === "misplaced")
898
1009
  misplaced = proof;
@@ -906,15 +1017,16 @@ export class OrcaDriver {
906
1017
  break;
907
1018
  proof = await this.checkoutProven(handle, st.cwd, this.cliCwd(st), env.runtimeId, deadline);
908
1019
  }
909
- if (!proof.proven) {
1020
+ if (!proof.proven || misplaced !== undefined) {
910
1021
  try {
911
1022
  await this.closeTerminal({ ...st, handle, runtimeId: env.runtimeId });
912
1023
  }
913
1024
  catch { /* best effort under the answering runtime; the latch prevents another launch */ }
914
1025
  // The typed timeout only when EVERY poll read its whole scrollback inside the ceiling and found no
915
- // trace of the mark; one misplaced or unread poll anywhere in the window denies it.
1026
+ // trace of the mark — or the ceiling cut the last one mid-frame; one misplaced or unread poll
1027
+ // anywhere in the window denies it.
916
1028
  const evidence = misplaced ?? unread ?? proof;
917
- throw this.latched("create", st, `terminal ${handle} does not prove checkout ${st.cwd} within ${CHECKOUT_PROOF_TIMEOUT_MS} ms (${evidence.reason})`, env.raw, evidence.seen === "absent" ? "checkout-proof-timeout" : undefined);
1029
+ throw this.latched("create", st, `terminal ${handle} does not prove checkout ${st.cwd} within ${CHECKOUT_PROOF_TIMEOUT_MS} ms (${evidence.reason})`, env.raw, evidence.seen === "absent" || evidence.seen === "cut" ? "checkout-proof-timeout" : undefined);
918
1030
  }
919
1031
  st.handle = handle;
920
1032
  // The handle is bound to the runtime identity that ANSWERED its create.
@@ -1036,12 +1148,14 @@ export class OrcaDriver {
1036
1148
  * the terminal record must name `handle` and `_meta.runtimeId` must be `runtimeId` — the runtime
1037
1149
  * that supplied the ownership listing — else another terminal's or another runtime's bytes were
1038
1150
  * answered and nothing is proven. Proven means provesCheckout: exact canonical full-path equality
1039
- * of a complete frame, no other checkout named, no incomplete frame.
1151
+ * of a complete frame, no other checkout named, no incomplete frame or unparseable mark — in the pages
1152
+ * or the anchor's tail (v2.6.8 T1).
1040
1153
  * E1: with a `deadline` (create's window) there is ONE absolute deadline: a read is issued only while
1041
1154
  * budget is left and is handed exactly that budget, and a page that still lands past it is late —
1042
1155
  * nothing late proves the checkout. `seen` is what the scrollback showed, for the typed timeout:
1043
1156
  * `misplaced` — a frame naming another checkout, a cut frame or an unparseable mark in anything read
1044
- * (the anchor's tail and every page included, even when paging stopped or a later read threw);
1157
+ * (the anchor's tail and every page included, even when paging stopped, a later read threw or the page's
1158
+ * own cursors were malformed — v2.6.8 T1; the tail read after the pages it overlaps, tailInContext);
1045
1159
  * `absent` — ONLY when every page down to the start of the scrollback was read before the ceiling
1046
1160
  * and none carries a trace of the mark; `unread` — every other exit: PROOF_PAGES reached, a limited
1047
1161
  * read with no (or the same) next cursor or a truncated anchor without one, a read that threw, a read
@@ -1057,10 +1171,13 @@ export class OrcaDriver {
1057
1171
  // F (D-832): a page read whose own metadata does not say how complete it is (classifyProofPage):
1058
1172
  // its bytes are evidence, but the scrollback it came from is not known to be read whole.
1059
1173
  let late = false, unknown = false;
1060
- const page = async (cursor) => {
1174
+ // v2.6.8 T1: `keep` takes every row a bound read returned, at its position, the moment it is read —
1175
+ // BEFORE any row type, cursor or overlap validation can discard it: a bound page whose cursor metadata
1176
+ // is malformed, or that carries a non-string row, is still evidence of every row it shows.
1177
+ const page = async (keep, cursor) => {
1061
1178
  const budget = deadline === undefined ? undefined : deadline - this.time.now();
1062
1179
  if (budget !== undefined && budget <= 0)
1063
- return undefined; // no budget left: the read is never issued
1180
+ return undefined; // no budget left: the read is never issued (the anchor only)
1064
1181
  let env;
1065
1182
  try {
1066
1183
  env = await this.call("read", [
@@ -1068,7 +1185,10 @@ export class OrcaDriver {
1068
1185
  ], from, budget);
1069
1186
  }
1070
1187
  catch (error) {
1071
- if (deadline === undefined || this.time.now() < deadline)
1188
+ // v2.6.8 T1: only a read the ceiling actually INTERRUPTED — killed at the budget it was handed — is
1189
+ // the cut. A read that failed for any other reason (a nonzero exit, unparseable output, an invocation
1190
+ // exception) keeps its own failure at every arrival time, at or past the ceiling included.
1191
+ if (deadline === undefined || !(error instanceof OrcaError && error.timedOut === true))
1072
1192
  throw error;
1073
1193
  return undefined; // the read the ceiling cut off: unread
1074
1194
  }
@@ -1084,6 +1204,16 @@ export class OrcaDriver {
1084
1204
  // E1: only a well-formed page is read text. A tail that is missing, not an array or carries a
1085
1205
  // non-string row (tailText), or a cursor that is not a non-empty string is a malformed page: it
1086
1206
  // throws, so it is unread output — never filtered to an empty page that would read as an absent proof.
1207
+ // v2.6.8 T1: the tail's rows are kept first — a non-string row as UNREADABLE_ROW in its place — so
1208
+ // neither a malformed cursor nor a malformed row discards the foreign, malformed or partial mark the
1209
+ // rest show: that evidence stays sticky for the whole create window. A cursor page's rows start at
1210
+ // its cursor; the anchor's end at its nextCursor (an impossible or unknown position stays unknown).
1211
+ if (Array.isArray(term.tail)) {
1212
+ const rows = term.tail.map((row) => typeof row === "string" ? row : UNREADABLE_ROW);
1213
+ const at = cursor === undefined ? (linePosition(term.nextCursor) ?? -1) - rows.length : linePosition(cursor);
1214
+ keep({ rows, ...(at !== undefined && at >= 0 ? { at } : {}) });
1215
+ }
1216
+ this.tailText("read", term, env.raw);
1087
1217
  for (const key of ["oldestCursor", "nextCursor"]) {
1088
1218
  if (term[key] !== undefined && str(term[key]) === undefined)
1089
1219
  throw new OrcaError("read", `proof page ${key} is not a cursor`, env.raw);
@@ -1093,42 +1223,51 @@ export class OrcaDriver {
1093
1223
  // stream alone cannot tell them apart, so its empty tail is unread scrollback, never absence.
1094
1224
  if (isBlindStreamPage(term))
1095
1225
  throw new OrcaError("read", "proof page is a blind stream page (status exited, empty tail): its scrollback is unread", env.raw);
1096
- const text = this.tailText("read", term, env.raw);
1097
1226
  // F: paging flags missing, not boolean or contradicted by the cursors leave the page's bytes read
1098
1227
  // but its completeness unknown — it ends paging, and the poll can never read as absent.
1099
1228
  const kind = classifyProofPage(term);
1100
1229
  if (kind === "unread")
1101
1230
  unknown = true;
1102
- return { term, text, kind };
1231
+ return { term, kind };
1103
1232
  };
1104
1233
  // Every validated read is evidence, whatever stops the next one: the anchor's tail and the pages
1105
1234
  // read from the oldest cursor are inspected even when paging hits PROOF_PAGES or a read throws.
1106
- let tail = "", text = "";
1107
- const described = (why) => {
1108
- const misplaced = checkout !== undefined && [text, tail].some((t) => misplacedProof(t, checkout));
1109
- const shown = [text, tail].map(checkoutFrames);
1235
+ // v2.6.8 T1: ONE record of everything read — the anchor's rows and the pages' rows, each row once at
1236
+ // its position. `pages` keeps a position only while every page starts where the rows before it end.
1237
+ let tail = { rows: [] }, pages = { rows: [] }, pagedEnd;
1238
+ const text = () => pages.rows.join("\n");
1239
+ // `cut`: the ceiling stopped this read. A frame it left incomplete at the end of the paged text is
1240
+ // the cut, not the terminal's misprint — unread, typed as the timeout — unless anything else read
1241
+ // (a complete foreign frame, a malformed marker read whole) is placement evidence (ceilingCutFrame).
1242
+ const described = (why, cut = false) => {
1243
+ const after = tailInContext(pages, tail);
1244
+ const misplaced = checkout !== undefined && [text(), after].some((t) => misplacedProof(t, checkout));
1245
+ const shown = [text(), after].map(checkoutFrames);
1110
1246
  const named = [...new Set(shown.flatMap((f) => f.complete))];
1111
- const flaw = shown.some((f) => f.incomplete) ? " and carries an incomplete proof frame"
1112
- : misplaced && named.length === 0 ? " and carries a malformed proof marker" : "";
1247
+ const ceilingCut = cut && checkout !== undefined && ceilingCutFrame(pages, tail, checkout);
1248
+ const flaw = ceilingCut ? " and carries a proof frame the ceiling cut off before it was read whole"
1249
+ : shown.some((f) => f.incomplete) ? " and carries an incomplete proof frame"
1250
+ : misplaced && named.every((c) => c === checkout) ? " and carries a malformed proof marker" : "";
1113
1251
  return {
1114
1252
  proven: false,
1115
1253
  reason: `${why}its scrollback ${named.length ? `names ${named.join(", ")}` : "names no checkout"}${flaw}`,
1116
- ...(misplaced ? { seen: "misplaced" } : {}),
1254
+ ...(ceilingCut ? { seen: "cut" } : misplaced ? { seen: "misplaced" } : {}),
1117
1255
  };
1118
1256
  };
1119
1257
  try {
1120
- const first = await page();
1258
+ const first = await page((read) => { tail = read; });
1121
1259
  if (first === undefined)
1122
1260
  return { proven: false, reason: "the ceiling cut off the proof read before any scrollback was read", seen: "unread" };
1123
1261
  const anchor = first.term;
1124
- tail = first.text;
1125
1262
  let cursor = str(anchor.oldestCursor);
1126
1263
  // why paging ended with scrollback still unread. Every exit is one of: complete (an anchor that
1127
1264
  // is the whole scrollback, or a page whose metadata says it is the last), whole frames found, a
1128
1265
  // page whose completeness is unknown (`unknown`, classifyProofPage), or one of these.
1266
+ // `spent`: a whole page landed exactly AT the ceiling, so no budget is left for the next read — paging
1267
+ // ended with scrollback unread, but no read was cut.
1129
1268
  let stop;
1130
1269
  if (cursor === undefined) {
1131
- text = tail;
1270
+ pages = tail; // the anchor is the whole read: the same rows, read once
1132
1271
  // A cursorless anchor that says it holds less than the scrollback leaves the rest unreadable.
1133
1272
  if (first.kind === "partial")
1134
1273
  stop = "stalled";
@@ -1138,14 +1277,31 @@ export class OrcaDriver {
1138
1277
  stop = "exhausted";
1139
1278
  break;
1140
1279
  } // more scrollback than the bound: unread, not absent
1141
- const got = late ? undefined : await page(cursor);
1280
+ // v2.6.8 T1: lateness ends paging but never CUTS a read — a page returned whole, whether it landed
1281
+ // past the ceiling (`late`) or exactly at it (`spent`: the next read is never issued), keeps its rows
1282
+ // (an unterminated or malformed marker included) as placement evidence; only a read the ceiling
1283
+ // actually INTERRUPTED below is `cut` (described's typed-timeout exemption).
1284
+ if (late)
1285
+ break;
1286
+ if (deadline !== undefined && this.time.now() >= deadline) {
1287
+ stop = "spent";
1288
+ break;
1289
+ }
1290
+ const got = await page((read) => {
1291
+ // Pages join by POSITION, never by bytes: a page that does not start where the rows before it end — a
1292
+ // gap, an overlap or an unknown position — is set apart by UNREADABLE_ROW, so no frame bridges rows
1293
+ // it never read beside.
1294
+ const contiguous = n === 0 || (pagedEnd !== undefined && read.at === pagedEnd);
1295
+ pages = { rows: [...pages.rows, ...(contiguous ? [] : [UNREADABLE_ROW]), ...read.rows],
1296
+ ...(n === 0 ? { at: read.at } : contiguous && pages.at !== undefined ? { at: pages.at } : {}) };
1297
+ pagedEnd = read.at === undefined ? undefined : read.at + read.rows.length;
1298
+ }, cursor);
1142
1299
  if (got === undefined) {
1143
1300
  stop = "cut";
1144
1301
  break;
1145
- }
1302
+ } // issued with budget left, interrupted by the ceiling
1146
1303
  const term = got.term;
1147
- text += `${got.text}\n`;
1148
- const frames = checkoutFrames(text);
1304
+ const frames = checkoutFrames(text());
1149
1305
  if (frames.complete.length > 0 && !frames.incomplete)
1150
1306
  break; // whole frames, nothing dangling
1151
1307
  if (got.kind === "complete")
@@ -1170,20 +1326,25 @@ export class OrcaDriver {
1170
1326
  }
1171
1327
  cursor = next;
1172
1328
  }
1173
- const frames = checkoutFrames(text);
1174
1329
  // Reconcile has no task-checkout path after a daemon restart, but the proof itself remains
1175
1330
  // an ownership record: exactly one complete, unambiguous checkout frame can only have been
1176
1331
  // written by tickmarkr's create command. Slot recovery additionally requires its exact path.
1177
- // A stalled read proves only what it read, as before; a cut or exhausted one proves nothing.
1178
- const proven = stop !== "cut" && stop !== "exhausted" && (checkout === undefined
1179
- ? !frames.incomplete && frames.complete.length > 0 && new Set(frames.complete).size === 1
1180
- : provesCheckout(text, checkout));
1332
+ // A stalled read proves only what it read, as before; a cut, spent or exhausted one proves nothing.
1333
+ // v2.6.8 T1: proof is classified over EVERYTHING read — the pages and the anchor's tail, which
1334
+ // may hold rows paging stopped short of. A foreign, incomplete or malformed mark in either
1335
+ // (misplacedProof) contradicts the proof: refused, never accepted beside a matching frame.
1336
+ const own = checkout ?? checkoutFrames(text()).complete[0];
1337
+ const proven = (stop === undefined || stop === "stalled") && own !== undefined
1338
+ && provesCheckout(text(), own) && !misplacedProof(tailInContext(pages, tail), own);
1181
1339
  if (proven && !late)
1182
1340
  return { proven: true, reason: "" };
1183
1341
  const why = `${late ? "read past the ceiling, " : ""}${stop === "cut" ? "the ceiling cut off paging, "
1184
- : stop === "exhausted" ? `paging stopped at ${PROOF_PAGES} pages with scrollback unread, `
1185
- : stop === "stalled" ? "a limited read gave no next cursor with scrollback unread, " : ""}${unknown ? "a page carried missing, non-boolean or contradictory paging metadata, " : ""}`;
1186
- const read = described(why);
1342
+ : stop === "spent" ? "a page landed at the ceiling with scrollback unread, "
1343
+ : stop === "exhausted" ? `paging stopped at ${PROOF_PAGES} pages with scrollback unread, `
1344
+ : stop === "stalled" ? "a limited read gave no next cursor with scrollback unread, " : ""}${unknown ? "a page carried missing, non-boolean or contradictory paging metadata, " : ""}`;
1345
+ // Only a read the ceiling actually cut short leaves a frame unread; a page that landed late or at the
1346
+ // ceiling was still returned whole, so a prefix it ends in is a misprint read whole, never the typed timeout.
1347
+ const read = described(why, stop === "cut");
1187
1348
  // F: absent only when every page read said, explicitly and consistently, how complete it was.
1188
1349
  return { ...read, seen: read.seen ?? (stop || late || unknown ? "unread" : "absent") };
1189
1350
  }
@@ -1191,7 +1352,7 @@ export class OrcaDriver {
1191
1352
  // A read that failed midway keeps what was already read as evidence; what it could not read is
1192
1353
  // unread output — never an absent proof.
1193
1354
  const read = described(""), failed = error instanceof Error ? error.message : String(error);
1194
- return { ...read, reason: tail || text ? `${read.reason}; then a read failed — ${failed}` : failed, seen: read.seen ?? "unread" };
1355
+ return { ...read, reason: tail.rows.length + pages.rows.length > 0 ? `${read.reason}; then a read failed — ${failed}` : failed, seen: read.seen ?? "unread" };
1195
1356
  }
1196
1357
  }
1197
1358
  async recover(family, st, newRuntimeId, raw, opts) {
@@ -1,10 +1,10 @@
1
- import { shOk } from "../run/git.js";
1
+ import { shGitOk } from "../run/git.js";
2
2
  export async function evidenceGate(worktree, baseRef) {
3
- const commits = (await shOk(`git rev-list '${baseRef}..HEAD'`, worktree)).trim().split("\n").filter(Boolean);
3
+ const commits = (await shGitOk(`git rev-list '${baseRef}..HEAD'`, worktree)).trim().split("\n").filter(Boolean);
4
4
  if (!commits.length) {
5
5
  return { gate: "evidence", pass: false, details: "no commits — worker claimed work but committed nothing", commits: [] };
6
6
  }
7
- const stat = (await shOk(`git diff --stat '${baseRef}..HEAD'`, worktree)).trim();
7
+ const stat = (await shGitOk(`git diff --stat '${baseRef}..HEAD'`, worktree)).trim();
8
8
  if (!stat) {
9
9
  return { gate: "evidence", pass: false, details: "commits exist but the cumulative diff is empty", commits };
10
10
  }
@@ -148,4 +148,4 @@ export declare function renderPriorMaterials(priorMaterials: readonly Structured
148
148
  */
149
149
  export declare function reviewResponseExample(nonce: string): string;
150
150
  export declare function reviewResponseRequirement(nonce: string): string;
151
- export declare function reviewGate(task: Task, worktree: string, baseRef: string, author: Assignment, channels: BillingChannel[], adapters: WorkerAdapter[], cfg: TickmarkrConfig, via?: GateVia, excludeReviewers?: string[], artifactDir?: string, reviewHistory?: string[], demotedReviewers?: ReadonlySet<string>, carriedFindings?: readonly StructuredFinding[], priorReviewers?: readonly PriorReviewer[], carriedAuthors?: readonly string[], operatorContext?: string): Promise<GateResult>;
151
+ export declare function reviewGate(task: Task, worktree: string, baseRef: string, author: Assignment, channels: BillingChannel[], adapters: WorkerAdapter[], cfg: TickmarkrConfig, via?: GateVia, excludeReviewers?: string[], artifactDir?: string, reviewHistory?: string[], demotedReviewers?: ReadonlySet<string>, carriedFindings?: readonly StructuredFinding[], priorReviewers?: readonly PriorReviewer[], carriedAuthors?: readonly string[], _operatorContext?: string): Promise<GateResult>;
@@ -5,7 +5,7 @@ import { criticalPathHits, DEFAULT_DIFF_CAP, DEFAULT_REVIEW_CRITICAL_PATHS, decl
5
5
  import { filesGlob } from "../graph/files-glob.js";
6
6
  import { renderAcceptanceItem, TIERS } from "../graph/schema.js";
7
7
  import { getAdapter } from "../adapters/registry.js";
8
- import { shOk } from "../run/git.js";
8
+ import { shGitOk } from "../run/git.js";
9
9
  import { carryReviewFindings, isDeferredFinding, observedReviewFingerprints, reviewFingerprintMatches, structuredFindings, UNIDENTIFIED } from "../run/journal.js";
10
10
  import { redactSecrets } from "../run/redact.js";
11
11
  import { rankPreferredChannels, reviewPreferenceTieBreak } from "../route/role-pick.js";
@@ -81,7 +81,7 @@ const DIFF_CAP_REMEDY = "split the task, or raise gates.diffCap";
81
81
  * the leaf class must be visible to the promotion test, and rename detection would hide the old side.
82
82
  */
83
83
  export async function changedPaths(worktree, baseRef) {
84
- const out = await shOk(`git diff --name-only --no-renames -z '${baseRef}..HEAD'`, worktree);
84
+ const out = await shGitOk(`git diff --name-only --no-renames -z '${baseRef}..HEAD'`, worktree);
85
85
  return [...new Set(out.split("\0").map((p) => p.trim()).filter(Boolean))].sort();
86
86
  }
87
87
  /**
@@ -95,7 +95,7 @@ const VERSION_FIELD_LINE_RE = /^[+-]\s*"version":\s*"[^"]*",?\s*$/;
95
95
  export async function mirrorsVersionOnly(worktree, baseRef, path) {
96
96
  let diff;
97
97
  try {
98
- diff = await shOk(`git diff --full-index -U0 '${baseRef}..HEAD' -- ${shq(path)}`, worktree);
98
+ diff = await shGitOk(`git diff --full-index -U0 '${baseRef}..HEAD' -- ${shq(path)}`, worktree);
99
99
  }
100
100
  catch {
101
101
  return false;
@@ -109,8 +109,8 @@ export async function fetchTaskDiff(worktree, baseRef, files = []) {
109
109
  const matched = files.length ? (await changedPaths(worktree, baseRef)).filter(filesGlob([...files])) : [];
110
110
  const pathspec = matched.length ? ` -- ${matched.map(shq).join(" ")}` : "";
111
111
  const [rawFull, rawForCap] = files.length && matched.length === 0 ? ["", ""] : await Promise.all([
112
- shOk(`git diff --full-index '${baseRef}..HEAD'${pathspec}`, worktree),
113
- shOk(`git diff --full-index -U0 '${baseRef}..HEAD'${pathspec}`, worktree),
112
+ shGitOk(`git diff --full-index '${baseRef}..HEAD'${pathspec}`, worktree),
113
+ shGitOk(`git diff --full-index -U0 '${baseRef}..HEAD'${pathspec}`, worktree),
114
114
  ]);
115
115
  const fullMeasurement = measureArtifactDiff(rawFull);
116
116
  const capMeasurement = measureArtifactDiff(rawForCap);
@@ -324,6 +324,52 @@ export function renderDeclaredWriteScope(files) {
324
324
  The task DECLARED these write-scope patterns:
325
325
  ${files.map((path) => `- ${path}`).join("\n")}`;
326
326
  }
327
+ // v2.6.8 T7: syntax only, never a filesystem glob. Explicit alternatives are finite at any count.
328
+ // ponytail: no expansion ceiling; the operator authors files[], so its product of alternatives is theirs to size.
329
+ const TEST_FILE_RE = /(?:^|\/)[^/]*\.(?:test|spec)\.[cm]?[jt]sx?$/;
330
+ /** Every finite, explicit `{a,b}` alternative of one entry, deduplicated; null when any part is not finite. */
331
+ function expandFiniteBraces(pattern) {
332
+ const open = pattern.indexOf("{");
333
+ if (open < 0)
334
+ return pattern.includes("}") ? null : [pattern];
335
+ if (pattern.slice(0, open).includes("}"))
336
+ return null;
337
+ const cuts = [open];
338
+ let depth = 0;
339
+ for (let i = open; i < pattern.length; i++) {
340
+ const c = pattern[i];
341
+ if (c === "{")
342
+ depth++;
343
+ else if (c === "," && depth === 1)
344
+ cuts.push(i);
345
+ else if (c === "}" && --depth === 0) {
346
+ if (cuts.length < 2)
347
+ return null; // `{}`, `{a}`, `{1..3}`: no finite alternation
348
+ cuts.push(i);
349
+ const out = new Set();
350
+ for (let k = 0; k + 1 < cuts.length; k++) {
351
+ const alt = pattern.slice(cuts[k] + 1, cuts[k + 1]);
352
+ const sub = alt ? expandFiniteBraces(pattern.slice(0, open) + alt + pattern.slice(i + 1)) : null;
353
+ if (!sub)
354
+ return null;
355
+ for (const path of sub)
356
+ out.add(path);
357
+ }
358
+ return [...out];
359
+ }
360
+ }
361
+ return null; // unclosed brace
362
+ }
363
+ /**
364
+ * The test files one files[] entry names explicitly. A wildcard, escape, malformed or empty brace, or
365
+ * a path leaving the repository anywhere in the expansion grants nothing for the whole entry.
366
+ */
367
+ function explicitSuitePaths(entry) {
368
+ const paths = expandFiniteBraces(entry);
369
+ const explicit = paths?.every((path) => !/[*?[\]{}()!\\]/.test(path)
370
+ && path.split("/").every((segment) => segment !== "" && segment !== "." && segment !== ".."));
371
+ return explicit ? paths.filter((path) => TEST_FILE_RE.test(path)) : [];
372
+ }
327
373
  /**
328
374
  * OBS-1033: the vendors of the seats that authored commits inside the accumulated diff. A seat is
329
375
  * never handed its own earlier work to approve. Unresolved channels are refused by reviewGate.
@@ -433,7 +479,10 @@ artifactDir, reviewHistory, demotedReviewers, carriedFindings = [],
433
479
  // never the run-wide rotation history nor excludeReviewers; the seat holds the highest of their tiers.
434
480
  priorReviewers = [],
435
481
  // Closed list of actual subject authors; empty preserves legacy callers' single-author contract.
436
- carriedAuthors = [], operatorContext) {
482
+ carriedAuthors = [],
483
+ // v2.6.8 T7: kept for compatible callers and read as empty. An approve reason is the repair worker's
484
+ // standing instruction; a reviewer handed "the previous finding is resolved" graded the reason, not the diff.
485
+ _operatorContext) {
437
486
  // R3 (OBS-186): participation is keyed on PATHS. The compiler's assignment comes from the DECLARED
438
487
  // files[]; the operator's floor may RAISE it to full and can never lower it. `complexityThreshold` is
439
488
  // retired — the branch that returned a green skip on a complexity comparison is gone, and with it the
@@ -529,8 +578,9 @@ carriedAuthors = [], operatorContext) {
529
578
  return capFail;
530
579
  const nonce = generateVerdictNonce();
531
580
  const repoRoot = daemonRepoRoot(worktree, artifactDir);
532
- // OBS-880 add.1: guidance only. Do not expand scope globs into permission to run suites.
533
- const ownTestFiles = [...new Set(task.files.filter((file) => !/[*?[\]{}()!]/.test(file) && /(?:^|\/)[^/]*\.(?:test|spec)\.[cm]?[jt]sx?$/.test(file)))];
581
+ // OBS-880 add.1: guidance only. Do not expand scope globs into permission to run suites; v2.6.8 T7:
582
+ // finite explicit brace alternatives are explicit files and each one is named.
583
+ const ownTestFiles = [...new Set(task.files.flatMap(explicitSuitePaths))];
534
584
  const suiteBudget = ownTestFiles.length
535
585
  ? `You may run at most the task's own test files explicitly named in files[]; these are the only suites you may run: ${ownTestFiles.map((file) => `\`${file}\``).join(", ")}.`
536
586
  : "No suite may be run: files[] names no explicit test file owned by this task.";
@@ -562,10 +612,6 @@ ${suiteBudget} Never run the whole suite (including an unfiltered npm test or vi
562
612
 
563
613
  ${priorMaterials.length ? `${renderPriorMaterials(priorMaterials)}
564
614
 
565
- ` : ""}${operatorContext?.trim() ? `## Operator context
566
- Context only: this never substitutes for an acceptance criterion or closes a prior material.
567
- ${operatorContext.trim()}
568
-
569
615
  ` : ""}${task.outOfScope?.length ? `## Out of scope
570
616
  The task declares these items out of scope. A finding inside these declared bounds is not material and must not block approval.
571
617
  ${task.outOfScope.map((item) => `- ${item}`).join("\n")}
@@ -683,7 +729,10 @@ ${responseRequirement}
683
729
  // a genuinely wrapped verdict behind it is reconstructed here, exactly as llm.ts would have.
684
730
  const echoFree = withoutExampleEcho(raw, nonce);
685
731
  const seat = via ? dewrapPaneVerdict(echoFree, nonce) : echoFree;
686
- const v = extractVerdictJson(seat, nonce);
732
+ const parsed = extractVerdictJson(seat, nonce);
733
+ // v2.6.8 T7: with zero carried ids nothing was asked to close, so volunteered closure ids are ignored —
734
+ // a material red stays red, never no-verdict. With carried ids the closure protocol binds unchanged.
735
+ const v = parsed && !priorMaterials.length ? { ...parsed, resolved: undefined, reraised: undefined } : parsed;
687
736
  const findings = v && Array.isArray(v.findings) ? v.findings : null;
688
737
  const priorIds = priorMaterials;
689
738
  const closureInvalid = isReviewClosureInvalid(v, priorIds);
@@ -73,6 +73,9 @@ export interface GateContext {
73
73
  excludeReviewers?: string[];
74
74
  demotedReviewers?: Set<string>;
75
75
  reviewNoVerdicts?: Map<string, string[]>;
76
+ /** v2.6.8 T1: review seats whose one same-seat launch retry this candidate already spent (the daemon
77
+ * replays them from the journal), so a resume or recheck never re-grants it by forgetting. */
78
+ launchRetried?: readonly string[];
76
79
  recheck?: boolean;
77
80
  /** Explicit worker funding requires fresh red measurements, never a gate waiver. OBS-1106: so does
78
81
  * a retry that landed nothing on a timeout-class red — its one fresh re-observation. C: and one whose