tickmarkr 2.6.4 → 2.6.6

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.
Files changed (73) hide show
  1. package/README.md +7 -3
  2. package/dist/adapters/catalog-remote.d.ts +23 -0
  3. package/dist/adapters/catalog-remote.js +38 -0
  4. package/dist/adapters/claude-code.d.ts +1 -1
  5. package/dist/adapters/claude-code.js +4 -1
  6. package/dist/adapters/codex-commit-check.d.ts +36 -0
  7. package/dist/adapters/codex-commit-check.js +205 -0
  8. package/dist/adapters/codex.d.ts +1 -0
  9. package/dist/adapters/codex.js +12 -6
  10. package/dist/adapters/model-discovery-check.d.ts +33 -0
  11. package/dist/adapters/model-discovery-check.js +123 -0
  12. package/dist/adapters/model-lints.d.ts +1 -1
  13. package/dist/adapters/model-lints.js +4 -0
  14. package/dist/adapters/prompt.js +3 -2
  15. package/dist/adapters/types.d.ts +10 -0
  16. package/dist/cli/commands/approve.js +10 -4
  17. package/dist/cli/commands/doctor.d.ts +2 -0
  18. package/dist/cli/commands/doctor.js +17 -3
  19. package/dist/cli/commands/fleet.d.ts +10 -1
  20. package/dist/cli/commands/fleet.js +363 -106
  21. package/dist/cli/commands/init.js +10 -3
  22. package/dist/cli/commands/plan.js +76 -4
  23. package/dist/cli/commands/verify.d.ts +1 -1
  24. package/dist/cli/commands/verify.js +65 -15
  25. package/dist/config/fleet-overlay.d.ts +16 -1
  26. package/dist/config/fleet-overlay.js +230 -28
  27. package/dist/drivers/orca.d.ts +34 -2
  28. package/dist/drivers/orca.js +206 -32
  29. package/dist/gates/cache.d.ts +4 -0
  30. package/dist/gates/cache.js +10 -2
  31. package/dist/gates/llm.d.ts +4 -1
  32. package/dist/gates/llm.js +6 -1
  33. package/dist/gates/review.js +8 -3
  34. package/dist/gates/run-gates.d.ts +5 -3
  35. package/dist/gates/run-gates.js +112 -56
  36. package/dist/gates/test-manifest.d.ts +3 -0
  37. package/dist/gates/test-manifest.js +1 -1
  38. package/dist/gates/timeout-shaped.d.ts +3 -0
  39. package/dist/gates/timeout-shaped.js +10 -0
  40. package/dist/run/daemon.js +326 -130
  41. package/dist/run/git.js +9 -0
  42. package/dist/run/journal.d.ts +6 -2
  43. package/dist/run/journal.js +22 -7
  44. package/dist/run/launchservices-check.d.ts +56 -0
  45. package/dist/run/launchservices-check.js +117 -0
  46. package/dist/run/operator-state.d.ts +22 -2
  47. package/dist/run/operator-state.js +20 -3
  48. package/dist/run/title-environment.d.ts +45 -0
  49. package/dist/run/title-environment.js +127 -0
  50. package/dist/run/title-preload.cjs +38 -0
  51. package/dist/run/title-preload.d.cts +1 -0
  52. package/dist/tui/cockpit/board.js +12 -6
  53. package/dist/tui/cockpit/keys.d.ts +8 -0
  54. package/dist/tui/cockpit/keys.js +4 -1
  55. package/dist/tui/cockpit/layout.d.ts +2 -2
  56. package/dist/tui/cockpit/layout.js +2 -2
  57. package/dist/tui/cockpit/live-runtime.js +46 -20
  58. package/dist/tui/cockpit/live-store.d.ts +24 -1
  59. package/dist/tui/cockpit/live-store.js +99 -8
  60. package/dist/tui/cockpit/log-view.d.ts +28 -0
  61. package/dist/tui/cockpit/log-view.js +60 -0
  62. package/dist/tui/cockpit/run-view.js +4 -0
  63. package/dist/tui/cockpit/shell.d.ts +2 -1
  64. package/dist/tui/cockpit/shell.js +6 -4
  65. package/dist/tui/ink/fleet-app.d.ts +37 -10
  66. package/dist/tui/ink/fleet-app.js +65 -21
  67. package/package.json +1 -1
  68. package/skills/tickmarkr-auto/SKILL.md +32 -2
  69. package/skills/tickmarkr-loop/SKILL.md +46 -2
  70. package/skills/tickmarkr-overseer/SKILL.md +83 -8
  71. package/skills/tickmarkr-overseer/scripts/classify-vitest-log.sh +58 -16
  72. package/skills/tickmarkr-overseer/scripts/grade-ci.sh +19 -6
  73. package/skills/tickmarkr-overseer/scripts/watch-parks.sh +121 -64
@@ -44,7 +44,7 @@ export const ORCA_RESPONSE_FAMILIES = [
44
44
  "status", "create", "list", "read", "send", "wait", "show", "close",
45
45
  "worktree-current", "worktree-set", "hooks-status", "split",
46
46
  ];
47
- export const ORCA_FIXTURE_VERSION = "1.4.195";
47
+ export const ORCA_FIXTURE_VERSION = "1.4.218";
48
48
  export const ORCA_CLI_COMMAND_ENV = "ORCA_CLI_COMMAND";
49
49
  export const STALE_HANDLE_CODE = "terminal_handle_stale";
50
50
  export const TERMINAL_GONE_CODE = "terminal_gone";
@@ -76,6 +76,7 @@ export class OrcaError extends Error {
76
76
  raw;
77
77
  code;
78
78
  runtimeId;
79
+ launchCause;
79
80
  constructor(family, reason, raw, opts = {}) {
80
81
  super(`orca ${family} failed — ${reason}; raw response:\n${raw}`);
81
82
  this.family = family;
@@ -84,14 +85,15 @@ export class OrcaError extends Error {
84
85
  this.name = "OrcaError";
85
86
  this.code = opts.code;
86
87
  this.runtimeId = opts.runtimeId;
88
+ this.launchCause = opts.launchCause;
87
89
  }
88
90
  }
89
91
  /** The slot cannot be addressed: dead/unknown terminal record, or a handle that cannot be recovered
90
92
  * to exactly one owned terminal in the slot's own worktree. Never a silent false or empty string. */
91
93
  export class OrcaUnavailableError extends OrcaError {
92
94
  terminalStatus;
93
- constructor(family, reason, raw, terminalStatus) {
94
- super(family, reason, raw, { code: "terminal_unavailable" });
95
+ constructor(family, reason, raw, terminalStatus, launchCause) {
96
+ super(family, reason, raw, { code: "terminal_unavailable", launchCause });
95
97
  this.terminalStatus = terminalStatus;
96
98
  this.name = "OrcaUnavailableError";
97
99
  }
@@ -209,7 +211,26 @@ export const CHECKOUT_MARK = "TICKMARKR_CHECKOUT";
209
211
  // never decode to this one; `A` versus `A B`, `…--T1` versus `…--T10` are different frames.
210
212
  const CHECKOUT_FRAME_RE = /TICKMARKR_CHECKOUT (\d+):([0-9a-f]*)(;?)/g;
211
213
  const PROOF_PAGES = 16; // pages read from the oldest cursor before the proof is declared absent
212
- const CHECKOUT_PROOF_TIMEOUT_MS = 2_000; // allow the wrapper shell to emit its startup proof
214
+ // E1: the ceiling for the wrapper shell's startup proof. A loaded host can print a review seat's proof
215
+ // long after the old 2 s window closed (the window a healthy seat was retired on); a proof first
216
+ // arriving at 10 s is accepted, and 20 s still bounds a terminal that never proves anything.
217
+ const CHECKOUT_PROOF_TIMEOUT_MS = 20_000;
218
+ // E1: the printed mark's first bytes past the printf split. The typed command never carries them (the
219
+ // mark is split at its underscore; exit trailers type `TICKMARKR_''EXIT`) and no other tickmarkr name
220
+ // starts `TICKMARKR_C`, so any occurrence is proof bytes — whole, cut or with a header the frame
221
+ // grammar cannot parse — and never an absent proof.
222
+ const MARK_TRACE = CHECKOUT_MARK.slice(0, CHECKOUT_MARK.indexOf("_") + 2);
223
+ /** E1: placement evidence in text actually read — a complete frame naming another checkout, a cut
224
+ * frame, or a mark the frame grammar cannot parse (more mark traces than frames). It denies the
225
+ * timeout exemption: only a terminal that printed no mark at all was merely slow.
226
+ * ponytail: a wrapped frame split across the last page read (the ceiling or PROOF_PAGES stopped
227
+ * paging) reads as cut — conservative (no exemption); it needs the proof line on that boundary. */
228
+ function misplacedProof(text, checkout) {
229
+ const { complete, incomplete } = checkoutFrames(text);
230
+ const joined = joinWrapped(text);
231
+ return incomplete || complete.some((c) => c !== checkout)
232
+ || joined.split(MARK_TRACE).length - 1 > [...joined.matchAll(CHECKOUT_FRAME_RE)].length;
233
+ }
213
234
  /** The exact bytes the create command prints as its first line. */
214
235
  export function checkoutProofLine(checkout) {
215
236
  const bytes = Buffer.from(checkout, "utf8");
@@ -587,6 +608,31 @@ export function isBlindStreamPage(term) {
587
608
  && Array.isArray(term.tail) && term.tail.length === 0
588
609
  && (term.returnedLineCount === undefined || term.returnedLineCount === 0);
589
610
  }
611
+ /**
612
+ * F (D-832): what one proof page's OWN metadata says about the scrollback it was read from — the closed
613
+ * seam checkoutProven decides paging and absence by. `complete`: explicit `limited:false` and
614
+ * `truncated:false` with cursors that agree — nothing lies past this page. `partial`: explicit booleans,
615
+ * one of them true, cursors that agree — more scrollback exists. `unread`: everything else — a flag
616
+ * missing or not a boolean, a cursor that is not a string, `limited:false` with a next cursor short of
617
+ * the latest one (the contradictory "last" page), a blind stream page
618
+ * (isBlindStreamPage), or a source that is not the stream (`screen-unavailable`). Missing metadata is
619
+ * never inferred complete: its page's bytes still count as read evidence, but no absence rests on it.
620
+ */
621
+ export function classifyProofPage(term) {
622
+ const { limited, truncated } = term;
623
+ if (typeof limited !== "boolean" || typeof truncated !== "boolean")
624
+ return "unread";
625
+ if (isBlindStreamPage(term) || (term.source !== undefined && term.source !== "stream"))
626
+ return "unread";
627
+ const next = str(term.nextCursor), latest = str(term.latestCursor);
628
+ if ((term.nextCursor !== undefined && next === undefined) || (term.latestCursor !== undefined && latest === undefined))
629
+ return "unread";
630
+ // a page that says it is the last while its next cursor stops short of the latest one contradicts itself.
631
+ // (A limited page whose next cursor IS the latest is not: the read from that cursor settles it.)
632
+ if (!limited && next !== undefined && latest !== undefined && next !== latest)
633
+ return "unread";
634
+ return limited || truncated ? "partial" : "complete";
635
+ }
590
636
  export class OrcaDriver {
591
637
  id = "orca";
592
638
  interactive = true; // a visible terminal the operator can watch and answer
@@ -752,9 +798,9 @@ export class OrcaDriver {
752
798
  throw new OrcaError("show", `unknown slot ${slot.id}`, "");
753
799
  return s;
754
800
  }
755
- latched(family, st, reason, raw) {
801
+ latched(family, st, reason, raw, launchCause) {
756
802
  st.unavailable = reason;
757
- return new OrcaUnavailableError(family, reason, raw);
803
+ return new OrcaUnavailableError(family, reason, raw, undefined, launchCause);
758
804
  }
759
805
  assertAvailable(family, st) {
760
806
  if (st.unavailable)
@@ -836,20 +882,39 @@ export class OrcaDriver {
836
882
  catch { /* already gone, or no longer provably ours — never a blind retry */ }
837
883
  throw this.latched("create", st, `create receipt bound to ${worktree ?? "no worktree"}, not the tracked ${tracked} enclosing ${st.cwd}`, env.raw);
838
884
  }
839
- // A receipt only proves placement on the enclosing tracked checkout. Wait briefly for the
840
- // startup command's proof before run() can resolve and the daemon can count a worker launch.
885
+ // A receipt only proves placement on the enclosing tracked checkout. Wait, up to the ceiling, for
886
+ // the startup command's proof before run() can resolve and the daemon can count a worker launch.
887
+ // Polls and reads are bounded by what is left of it, and proof read past it proves nothing.
888
+ // One absolute deadline, rechecked after every sleep: no read starts once it has passed.
841
889
  const deadline = this.time.now() + CHECKOUT_PROOF_TIMEOUT_MS;
842
- let proof = await this.checkoutProven(handle, st.cwd, this.cliCwd(st), env.runtimeId);
843
- while (!proof.proven && this.time.now() < deadline) {
844
- await this.time.sleep(POLL_MS);
845
- proof = await this.checkoutProven(handle, st.cwd, this.cliCwd(st), env.runtimeId);
890
+ // Evidence is kept across polls and never downgraded: once a poll read a misplaced mark or left
891
+ // output unread, no later poll — absent, cut or otherwise — turns the window back into absence.
892
+ let misplaced, unread;
893
+ let proof = await this.checkoutProven(handle, st.cwd, this.cliCwd(st), env.runtimeId, deadline);
894
+ for (;;) {
895
+ if (proof.proven)
896
+ break;
897
+ if (proof.seen === "misplaced")
898
+ misplaced = proof;
899
+ else if (proof.seen === "unread")
900
+ unread = proof;
901
+ const left = deadline - this.time.now();
902
+ if (left <= 0)
903
+ break;
904
+ await this.time.sleep(Math.min(POLL_MS, left));
905
+ if (this.time.now() >= deadline)
906
+ break;
907
+ proof = await this.checkoutProven(handle, st.cwd, this.cliCwd(st), env.runtimeId, deadline);
846
908
  }
847
909
  if (!proof.proven) {
848
910
  try {
849
911
  await this.closeTerminal({ ...st, handle, runtimeId: env.runtimeId });
850
912
  }
851
913
  catch { /* best effort under the answering runtime; the latch prevents another launch */ }
852
- throw this.latched("create", st, `terminal ${handle} does not prove checkout ${st.cwd} (${proof.reason})`, env.raw);
914
+ // 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.
916
+ 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);
853
918
  }
854
919
  st.handle = handle;
855
920
  // The handle is bound to the runtime identity that ANSWERED its create.
@@ -972,12 +1037,43 @@ export class OrcaDriver {
972
1037
  * that supplied the ownership listing — else another terminal's or another runtime's bytes were
973
1038
  * answered and nothing is proven. Proven means provesCheckout: exact canonical full-path equality
974
1039
  * of a complete frame, no other checkout named, no incomplete frame.
1040
+ * E1: with a `deadline` (create's window) there is ONE absolute deadline: a read is issued only while
1041
+ * budget is left and is handed exactly that budget, and a page that still lands past it is late —
1042
+ * nothing late proves the checkout. `seen` is what the scrollback showed, for the typed timeout:
1043
+ * `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);
1045
+ * `absent` — ONLY when every page down to the start of the scrollback was read before the ceiling
1046
+ * and none carries a trace of the mark; `unread` — every other exit: PROOF_PAGES reached, a limited
1047
+ * read with no (or the same) next cursor or a truncated anchor without one, a read that threw, a read
1048
+ * the ceiling cut off or never let start (the anchor included), or a page that landed late (even one
1049
+ * carrying the right proof), a malformed page (its tail or cursors), a blind stream page
1050
+ * (isBlindStreamPage), or — F (D-832) — any page read whose paging metadata classifyProofPage cannot
1051
+ * call complete or partial (flags missing, not boolean or contradicted; even when they were omitted,
1052
+ * that page's bytes stay evidence and its matching proof still proves). Unread output is unknown —
1053
+ * never an empty page, never absence.
1054
+ * Without a deadline (reconcile, recovery) only `proven` is consumed.
975
1055
  */
976
- async checkoutProven(handle, checkout, from, runtimeId) {
1056
+ async checkoutProven(handle, checkout, from, runtimeId, deadline) {
1057
+ // F (D-832): a page read whose own metadata does not say how complete it is (classifyProofPage):
1058
+ // its bytes are evidence, but the scrollback it came from is not known to be read whole.
1059
+ let late = false, unknown = false;
977
1060
  const page = async (cursor) => {
978
- const env = await this.call("read", [
979
- "terminal", "read", "--terminal", handle, ...(cursor === undefined ? [] : ["--cursor", cursor]), "--limit", String(this.pageLines),
980
- ], from);
1061
+ const budget = deadline === undefined ? undefined : deadline - this.time.now();
1062
+ if (budget !== undefined && budget <= 0)
1063
+ return undefined; // no budget left: the read is never issued
1064
+ let env;
1065
+ try {
1066
+ env = await this.call("read", [
1067
+ "terminal", "read", "--terminal", handle, ...(cursor === undefined ? [] : ["--cursor", cursor]), "--limit", String(this.pageLines),
1068
+ ], from, budget);
1069
+ }
1070
+ catch (error) {
1071
+ if (deadline === undefined || this.time.now() < deadline)
1072
+ throw error;
1073
+ return undefined; // the read the ceiling cut off: unread
1074
+ }
1075
+ if (deadline !== undefined && this.time.now() > deadline)
1076
+ late = true;
981
1077
  if (env.runtimeId !== runtimeId) {
982
1078
  throw new OrcaError("read", `proof page answered by runtime ${env.runtimeId}, not the listing's ${runtimeId}`, env.raw);
983
1079
  }
@@ -985,39 +1081,117 @@ export class OrcaDriver {
985
1081
  if (str(term.handle) !== handle) {
986
1082
  throw new OrcaError("read", `proof page names terminal ${str(term.handle) ?? "none"}, not the candidate ${handle}`, env.raw);
987
1083
  }
988
- return term;
1084
+ // E1: only a well-formed page is read text. A tail that is missing, not an array or carries a
1085
+ // non-string row (tailText), or a cursor that is not a non-empty string is a malformed page: it
1086
+ // throws, so it is unread output — never filtered to an empty page that would read as an absent proof.
1087
+ for (const key of ["oldestCursor", "nextCursor"]) {
1088
+ if (term[key] !== undefined && str(term[key]) === undefined)
1089
+ throw new OrcaError("read", `proof page ${key} is not a cursor`, env.raw);
1090
+ }
1091
+ // E1: the OBS-1011 add.1 blind stream (exited, empty tail, zero lines returned) is the shape a
1092
+ // live terminal's stream answers while its screen still paints — and a dead terminal's too. The
1093
+ // stream alone cannot tell them apart, so its empty tail is unread scrollback, never absence.
1094
+ if (isBlindStreamPage(term))
1095
+ 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
+ // F: paging flags missing, not boolean or contradicted by the cursors leave the page's bytes read
1098
+ // but its completeness unknown — it ends paging, and the poll can never read as absent.
1099
+ const kind = classifyProofPage(term);
1100
+ if (kind === "unread")
1101
+ unknown = true;
1102
+ return { term, text, kind };
1103
+ };
1104
+ // Every validated read is evidence, whatever stops the next one: the anchor's tail and the pages
1105
+ // 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);
1110
+ 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" : "";
1113
+ return {
1114
+ proven: false,
1115
+ reason: `${why}its scrollback ${named.length ? `names ${named.join(", ")}` : "names no checkout"}${flaw}`,
1116
+ ...(misplaced ? { seen: "misplaced" } : {}),
1117
+ };
989
1118
  };
990
- const lines = (term) => Array.isArray(term.tail) ? term.tail.filter((l) => typeof l === "string") : [];
991
1119
  try {
992
- const anchor = await page();
1120
+ const first = await page();
1121
+ if (first === undefined)
1122
+ return { proven: false, reason: "the ceiling cut off the proof read before any scrollback was read", seen: "unread" };
1123
+ const anchor = first.term;
1124
+ tail = first.text;
993
1125
  let cursor = str(anchor.oldestCursor);
994
- let text = cursor === undefined ? lines(anchor).join("\n") : "";
995
- for (let n = 0; cursor !== undefined && n < PROOF_PAGES; n++) {
996
- const term = await page(cursor);
997
- text += `${lines(term).join("\n")}\n`;
1126
+ // why paging ended with scrollback still unread. Every exit is one of: complete (an anchor that
1127
+ // is the whole scrollback, or a page whose metadata says it is the last), whole frames found, a
1128
+ // page whose completeness is unknown (`unknown`, classifyProofPage), or one of these.
1129
+ let stop;
1130
+ if (cursor === undefined) {
1131
+ text = tail;
1132
+ // A cursorless anchor that says it holds less than the scrollback leaves the rest unreadable.
1133
+ if (first.kind === "partial")
1134
+ stop = "stalled";
1135
+ }
1136
+ for (let n = 0; cursor !== undefined; n++) {
1137
+ if (n === PROOF_PAGES) {
1138
+ stop = "exhausted";
1139
+ break;
1140
+ } // more scrollback than the bound: unread, not absent
1141
+ const got = late ? undefined : await page(cursor);
1142
+ if (got === undefined) {
1143
+ stop = "cut";
1144
+ break;
1145
+ }
1146
+ const term = got.term;
1147
+ text += `${got.text}\n`;
998
1148
  const frames = checkoutFrames(text);
999
1149
  if (frames.complete.length > 0 && !frames.incomplete)
1000
1150
  break; // whole frames, nothing dangling
1151
+ if (got.kind === "complete")
1152
+ break; // the last page: the scrollback was read to its end
1001
1153
  const next = str(term.nextCursor);
1002
- if (term.limited !== true || next === undefined || next === cursor)
1154
+ const advancing = next !== undefined && next !== cursor;
1155
+ if (got.kind === "unread") {
1156
+ // F: no absence rests on this page (`unknown`), but a cursor that still advances toward more
1157
+ // scrollback — the page says limited, or its next cursor is short of the latest one — is
1158
+ // followed for positive proof, inside PROOF_PAGES and the ceiling.
1159
+ if (advancing && (term.limited === true || next !== str(term.latestCursor))) {
1160
+ cursor = next;
1161
+ continue;
1162
+ }
1163
+ break;
1164
+ }
1165
+ // A partial page that is not limited, or a limited one with no next cursor or the same one, says
1166
+ // more scrollback exists that no read can reach: unread, never absent.
1167
+ if (term.limited !== true || !advancing) {
1168
+ stop = "stalled";
1003
1169
  break;
1170
+ }
1004
1171
  cursor = next;
1005
1172
  }
1006
1173
  const frames = checkoutFrames(text);
1007
1174
  // Reconcile has no task-checkout path after a daemon restart, but the proof itself remains
1008
1175
  // an ownership record: exactly one complete, unambiguous checkout frame can only have been
1009
1176
  // written by tickmarkr's create command. Slot recovery additionally requires its exact path.
1010
- if (checkout === undefined
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
1011
1179
  ? !frames.incomplete && frames.complete.length > 0 && new Set(frames.complete).size === 1
1012
- : provesCheckout(text, checkout))
1180
+ : provesCheckout(text, checkout));
1181
+ if (proven && !late)
1013
1182
  return { proven: true, reason: "" };
1014
- return {
1015
- proven: false,
1016
- reason: `its scrollback ${frames.complete.length ? `names ${[...new Set(frames.complete)].join(", ")}` : "names no checkout"}${frames.incomplete ? " and carries an incomplete proof frame" : ""}`,
1017
- };
1183
+ 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);
1187
+ // F: absent only when every page read said, explicitly and consistently, how complete it was.
1188
+ return { ...read, seen: read.seen ?? (stop || late || unknown ? "unread" : "absent") };
1018
1189
  }
1019
1190
  catch (error) {
1020
- return { proven: false, reason: error instanceof Error ? error.message : String(error) };
1191
+ // A read that failed midway keeps what was already read as evidence; what it could not read is
1192
+ // unread output — never an absent proof.
1193
+ 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" };
1021
1195
  }
1022
1196
  }
1023
1197
  async recover(family, st, newRuntimeId, raw, opts) {
@@ -13,6 +13,10 @@ export declare function getWorktreeTree(worktree: string): Promise<string>;
13
13
  * ponytail: a named list, not the whole env (pids and terminal vars would defeat every reuse); extend
14
14
  * it when another variable is shown to change what a runner executes. */
15
15
  export declare const RUNNER_ENV_KEYS: readonly ["PATH", "NODE_OPTIONS", "NODE_PATH", "NODE_ENV", "TZ"];
16
+ /** A2: NODE_OPTIONS is hashed as the child RECEIVES it (shell appends the Darwin title preload), plus the
17
+ * preload's content identity, so a replaced preload, the opt-out or a refused preflight never reuses a
18
+ * verdict or baseline measured under another mode. Native mode hashes the pre-A2 inputs less any inherited
19
+ * managed preload, which shell removes from that child too. */
16
20
  export declare function runnerInputsHash(env?: NodeJS.ProcessEnv): string;
17
21
  export interface GateEnvironmentInput {
18
22
  nodeRuntime?: string;
@@ -5,6 +5,7 @@ import { dirname, join, relative, resolve } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
  import { checkoutIncarnation, describeCapacity, inventoryDependencyLinks, resolvedCapacity, shGit, verificationProtocol } from "../run/git.js";
7
7
  import { shq } from "../adapters/types.js";
8
+ import { titleEnvironment } from "../run/title-environment.js";
8
9
  export const DEFAULT_VERDICT_CACHE_BOUND = 128;
9
10
  let testCacheBound;
10
11
  export function setVerdictCacheBoundForTests(bound) {
@@ -81,9 +82,16 @@ export async function getWorktreeTree(worktree) {
81
82
  * ponytail: a named list, not the whole env (pids and terminal vars would defeat every reuse); extend
82
83
  * it when another variable is shown to change what a runner executes. */
83
84
  export const RUNNER_ENV_KEYS = ["PATH", "NODE_OPTIONS", "NODE_PATH", "NODE_ENV", "TZ"];
85
+ /** A2: NODE_OPTIONS is hashed as the child RECEIVES it (shell appends the Darwin title preload), plus the
86
+ * preload's content identity, so a replaced preload, the opt-out or a refused preflight never reuses a
87
+ * verdict or baseline measured under another mode. Native mode hashes the pre-A2 inputs less any inherited
88
+ * managed preload, which shell removes from that child too. */
84
89
  export function runnerInputsHash(env = process.env) {
85
- return createHash("sha256").update(canonicalJson(Object.fromEntries(RUNNER_ENV_KEYS.map((k) => [k, env[k] ?? null]))))
86
- .digest("hex").slice(0, 16);
90
+ const title = titleEnvironment(env);
91
+ const inputs = Object.fromEntries(RUNNER_ENV_KEYS.map((k) => [k, (k === "NODE_OPTIONS" ? title.nodeOptions : env[k]) ?? null]));
92
+ if (title.mode === "preload")
93
+ inputs.titlePreload = title.identity;
94
+ return createHash("sha256").update(canonicalJson(inputs)).digest("hex").slice(0, 16);
87
95
  }
88
96
  export function environmentFingerprint(env) {
89
97
  const nodeRuntime = env.nodeRuntime ?? process.version;
@@ -1,6 +1,7 @@
1
1
  import { type WorkerAdapter } from "../adapters/types.js";
2
2
  import type { Effort } from "../graph/schema.js";
3
3
  import { type ExecutorDriver, type Slot } from "../drivers/types.js";
4
+ import type { SeatLaunchCause } from "../drivers/orca.js";
4
5
  export declare const GATE_PANE_SEP = " \u00B7 ";
5
6
  export declare const COMPLETION_FAKING_CHECKLIST = "## Completion-faking checklist\nHunt for these concrete completion-faking shortcuts before ruling on any criterion:\n- hardcoded-result: output or fixture hardcoded to satisfy the stated criterion instead of real logic\n- test-weakening: tests skipped, deleted, or assertions loosened until failing behavior looks green\n- vacuous-assertion: a test that cannot fail (asserts a constant, asserts its own setup, no assertion)\n- fixture-overfit: implementation narrowed to the exact test inputs rather than the described behavior\n- echo-not-implement: criterion text echoed in names, comments, or strings without the behavior itself\n- stub-left-behind: TODO, throw, or no-op stub where the real implementation should be\n- error-swallowing: catch or fallback that hides failures instead of handling them\n- self-mocking: the code under test mocked or faked so the test exercises the mock\n- check-bypass: lint, type, or CI checks disabled, relaxed, or excluded to get green\n- rename-as-work: code moved or renamed and presented as the requested change\n- scope-padding: unrelated edits padding the diff while the criterion's behavior is untouched\nWhen a criterion fails, the verdict MUST name which shortcut above it matches, or state that none does.";
6
7
  /** Fable F3: per-call nonce echoed in verdict JSON and gate exit markers. */
@@ -70,9 +71,11 @@ export interface LlmRunResult {
70
71
  export declare const PROMPT_GLYPHS: readonly ["➜", "❯", "$", "%", ">>", ">"];
71
72
  export declare function reviewSeatOutput(raw: string, nonce: string, adapterBannerRows?: readonly string[]): string;
72
73
  /** OBS-1168(b): a judge/review seat whose pane could not be created or launched. Typed so the gates can
73
- * contain it as seat recovery (another seat) or an infra park — never as a worker failure. */
74
+ * contain it as seat recovery (another seat) or an infra park — never as a worker failure. E1: the
75
+ * driver's typed launch cause survives the wrap as a field; the message alone is prose. */
74
76
  export declare class SeatLaunchError extends Error {
75
77
  readonly seat: string;
78
+ readonly launchCause?: SeatLaunchCause;
76
79
  constructor(seat: string, cause: unknown);
77
80
  }
78
81
  export declare const REVIEW_FIRST_LIVENESS_MS = 30000;
package/dist/gates/llm.js CHANGED
@@ -303,13 +303,18 @@ export function reviewSeatOutput(raw, nonce, adapterBannerRows = []) {
303
303
  return trailer ? seat.slice(0, trailer.index) : seat;
304
304
  }
305
305
  /** OBS-1168(b): a judge/review seat whose pane could not be created or launched. Typed so the gates can
306
- * contain it as seat recovery (another seat) or an infra park — never as a worker failure. */
306
+ * contain it as seat recovery (another seat) or an infra park — never as a worker failure. E1: the
307
+ * driver's typed launch cause survives the wrap as a field; the message alone is prose. */
307
308
  export class SeatLaunchError extends Error {
308
309
  seat;
310
+ launchCause;
309
311
  constructor(seat, cause) {
310
312
  super(`seat ${seat} failed to launch: ${cause instanceof Error ? cause.message : String(cause)}`);
311
313
  this.seat = seat;
312
314
  this.name = "SeatLaunchError";
315
+ const launchCause = cause?.launchCause;
316
+ if (launchCause === "checkout-proof-timeout")
317
+ this.launchCause = launchCause;
313
318
  }
314
319
  }
315
320
  export const REVIEW_FIRST_LIVENESS_MS = 30_000;
@@ -367,7 +367,9 @@ function daemonRepoRoot(worktree, artifactDir) {
367
367
  export function renderPriorMaterials(priorMaterials) {
368
368
  const fingerprints = priorMaterials.map((finding, i) => {
369
369
  const observed = observedReviewFingerprints(finding);
370
- const heading = observed.length > 1 ? `Finding ${i + 1} (choose one observed spelling):\n` : "";
370
+ // E2 (T4 D-766): every finding is headed. A one-spelling finding left unheaded read as one more
371
+ // spelling of the multi-spelling finding above it, so a faithful seat closed F1 alone.
372
+ const heading = observed.length > 1 ? `Finding ${i + 1} (choose one observed spelling):\n` : `Finding ${i + 1}:\n`;
371
373
  return heading + observed.map((id) => `Fingerprint: ${id}`).join("\n");
372
374
  }).join("\n");
373
375
  return `## Prior materials this attempt must close
@@ -642,11 +644,14 @@ ${responseRequirement}
642
644
  throw error;
643
645
  // OBS-1168(b): the seat never launched, so there is no verdict and nothing about the WORK. A typed
644
646
  // no-verdict re-routes to another seat in run-gates; an exhausted pool is an infra park.
647
+ // E1: the cause stays seat-launch-failed; a typed launchCause rides beside it, never replaces it.
648
+ const launch = error.launchCause ? `; launchCause: ${error.launchCause}` : "";
645
649
  return { gate: "review", pass: false,
646
- details: `review dispatch failed — ${error.message} (reviewer ${reviewer.adapter}:${reviewer.model}; vendor: ${reviewer.vendor}; provider: ${provider}; cause: seat-launch-failed) — failing closed`,
650
+ details: `review dispatch failed — ${error.message} (reviewer ${reviewer.adapter}:${reviewer.model}; vendor: ${reviewer.vendor}; provider: ${provider}; cause: seat-launch-failed${launch}) — failing closed`,
647
651
  meta: { ...policyMeta, ...rotationMeta, ...floorMeta, reviewer: channelKey(reviewer), reviewerTier: reviewer.tier,
648
652
  vendor: reviewer.vendor, provider, noVerdict: true, classification: "infra", infra: true,
649
- cause: "seat-launch-failed", ...(savedBrief ? { briefPath: savedBrief } : {}) } };
653
+ cause: "seat-launch-failed", ...(error.launchCause ? { launchCause: error.launchCause } : {}),
654
+ ...(savedBrief ? { briefPath: savedBrief } : {}) } };
650
655
  }
651
656
  const raw = llm.output;
652
657
  let saved;
@@ -75,8 +75,9 @@ export interface GateContext {
75
75
  reviewNoVerdicts?: Map<string, string[]>;
76
76
  recheck?: boolean;
77
77
  /** Explicit worker funding requires fresh red measurements, never a gate waiver. OBS-1106: so does
78
- * a retry that landed nothing on a timeout-class red — its one fresh re-observation. */
79
- cachedRedBypass?: "operator-rerun" | "timeout-fresh";
78
+ * a retry that landed nothing on a timeout-class red — its one fresh re-observation. C: and one whose
79
+ * unchanged red is attributed wholly outside the task's files[] — its one fresh re-execution. */
80
+ cachedRedBypass?: "operator-rerun" | "timeout-fresh" | "out-of-scope-fresh";
80
81
  carriedAuthors?: readonly string[];
81
82
  /** The attempt whose worker last wrote the gated checkout; a dirty-tree refusal stamps it on the
82
83
  * preserve commit and its row. Absent (standalone verify, gate-only restores) preserves as "unknown". */
@@ -108,7 +109,8 @@ export declare function screenCostRatio(baseline: Baseline, selected: readonly s
108
109
  /** A retry base no runner invocation parses. evaluateManifestedTest builds its stranded single-fork
109
110
  * retry from the base it is handed, and one it cannot parse throws before any spawn — so this base
110
111
  * disables that inner recovery: a worker-RPC-stranded re-observation comes back infra (the caller
111
- * parks it as ambiguous) instead of launching a second execution. */
112
+ * parks it as ambiguous) instead of launching a second execution. runVitestManifestGate reads it as
113
+ * the mark of a diagnostic subset, which never buys the outer timeout-shaped remeasurement either. */
112
114
  export declare const REOBSERVATION_RETRY_BASE = "tickmarkr-reobservation-refuses-stranded-retry";
113
115
  /** OBS-1106 residual: ONE isolated re-observation of a timeout-shaped red's attributed failing files on
114
116
  * the same checkout, narrowed exactly as a screen is. Never cached and never a verdict: the caller