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
@@ -1,6 +1,6 @@
1
1
  // Fleet-overlay mutation, serialization, and diff rendering for the `tickmarkr fleet` write path.
2
- import { isMap, isScalar, isSeq, parseDocument, stringify, visit } from "yaml";
3
- import { DENY_SCOPES, universeCovers, universeEntryMatches } from "./config.js";
2
+ import { isAlias, isCollection, isMap, isNode, isScalar, isSeq, parseDocument, stringify, visit } from "yaml";
3
+ import { ConfigError, DENY_SCOPES, universeCovers, universeEntryMatches } from "./config.js";
4
4
  // OBS-1099 add.1: the writer ranges over the schema-derived scopes. The flat (all-seats) scopes
5
5
  // take part in the membership/allow-form write; every nested scope (routing.deny.workers.*) is a
6
6
  // literal deny list written on its own. ponytail: "flat" = directly under routing.deny. Read
@@ -64,9 +64,12 @@ function residualDeny(universe, entries) {
64
64
  // list keeps its node. A staged entry the allow form cannot express as a membership key (outside the
65
65
  // probe universe, or a bare-model/identity spelling) is written verbatim too; canonical keys the
66
66
  // session added ride the allow form alone.
67
+ // T10 (D-973): read as the loader reads these bytes (readYaml parses with yaml's defaults, as toJS does
68
+ // here) — aliases at the leaf, its items and every ancestor resolve, and `<<` merges only in a document
69
+ // whose YAML version merges — so an aliased list's entries are authored entries too.
67
70
  function authoredEntries(doc, path) {
68
- const node = doc.getIn(path, true);
69
- return isSeq(node) ? node.items.flatMap((item) => (isScalar(item) ? [String(item.value)] : [])) : [];
71
+ const value = path.reduce((node, key) => (node !== null && typeof node === "object" ? node[key] : undefined), doc.toJS());
72
+ return Array.isArray(value) ? value.flatMap((item) => (item === null || typeof item !== "object" ? [String(item)] : [])) : [];
70
73
  }
71
74
  function flatDenyAfterWrite(doc, path, after, universe) {
72
75
  const authored = authoredEntries(doc, path);
@@ -112,8 +115,10 @@ function unpackFleetProvenance(provenance) {
112
115
  }
113
116
  function setScalarPreservingComment(doc, path, value, authoredComment) {
114
117
  const existing = doc.getIn(path, true);
115
- if (isScalar(existing))
118
+ if (isScalar(existing)) {
119
+ releaseAnchors(doc, path);
116
120
  existing.value = value;
121
+ }
117
122
  else
118
123
  setAt(doc, path, doc.createNode(value));
119
124
  if (authoredComment !== undefined) {
@@ -146,6 +151,10 @@ function setStringSequencePreservingComments(doc, path, values) {
146
151
  setAt(doc, path, doc.createNode(values));
147
152
  return;
148
153
  }
154
+ // T10 owed review F2: an unchanged list is not edited, so its anchor and every alias keep their bytes
155
+ if (existing.items.length === values.length && existing.items.every((item, at) => isScalar(item) && String(item.value) === values[at]))
156
+ return;
157
+ releaseAnchors(doc, path); // an anchored list is edited in place: its other aliases keep the original
149
158
  const available = [...existing.items];
150
159
  existing.items = values.map((value) => {
151
160
  const at = available.findIndex((item) => isScalar(item) && String(item.value) === value);
@@ -161,31 +170,150 @@ function setStringSequencePreservingComments(doc, path, values) {
161
170
  // Total forms: a delete treats the masked subtree as absent (the operator's tombstone already
162
171
  // denies it, and survives untouched); a set clears the scalar and rebuilds what fleet owns —
163
172
  // the routing.allow precedent below.
173
+ // T8 repair: an alias is no tombstone — `spec: *empty` resolves to a mapping. Each alias on the path
174
+ // is replaced by an un-anchored copy of its target, so what it resolves to decides, and a write under
175
+ // it never edits the anchored node its other aliases share.
176
+ function materializeAliases(doc, path) {
177
+ for (let depth = 1; depth < path.length; depth++) {
178
+ const node = doc.getIn(path.slice(0, depth), true);
179
+ if (!isAlias(node))
180
+ continue;
181
+ const target = node.resolve(doc);
182
+ if (target === undefined)
183
+ return;
184
+ doc.setIn(path.slice(0, depth), detachedCopy(doc, node, target, new Set()));
185
+ }
186
+ }
187
+ // T10 (D-973): a write mutates every ancestor on its path and edits or replaces the leaf with all it
188
+ // holds; any of those nodes may be an anchor other aliases share. Each such alias first becomes a
189
+ // detached copy of what it resolved to, so the write never changes another consumer's value nor
190
+ // leaves its alias dangling.
191
+ function releaseAnchors(doc, path) {
192
+ const held = new Set();
193
+ for (let depth = 1; depth <= path.length; depth++) {
194
+ const node = doc.getIn(path.slice(0, depth), true);
195
+ if (!isNode(node))
196
+ break;
197
+ for (const inner of depth < path.length ? [node] : nodesOf(node)) {
198
+ if (!isAlias(inner) && inner.anchor)
199
+ held.add(inner);
200
+ }
201
+ }
202
+ if (!held.size)
203
+ return;
204
+ visit(doc, {
205
+ Alias: (_key, alias) => {
206
+ const target = alias.resolve(doc);
207
+ return target !== undefined && held.has(target) ? detachedCopy(doc, alias, target, new Set()) : undefined;
208
+ },
209
+ });
210
+ }
211
+ const nodesOf = (root) => {
212
+ const out = [];
213
+ visit(root, (_key, node) => {
214
+ if (isNode(node))
215
+ out.push(node);
216
+ });
217
+ return out;
218
+ };
219
+ // D-821: a copy carries NO anchor (a nested `&empty` would rebind `plan: *empty` and every later
220
+ // alias to the edited copy), and each inner alias must still name what it named in the original —
221
+ // one whose anchor is redefined before `at` is replaced by a detached copy of its original target.
222
+ function detachedCopy(doc, at, target, expanding) {
223
+ if (expanding.has(target))
224
+ throw new Error(`fleet cannot detach the recursive YAML alias *${at.source}`);
225
+ const visible = new Map();
226
+ visit(doc, (_key, node) => {
227
+ if (node === at)
228
+ return visit.BREAK;
229
+ if (isNode(node) && !isAlias(node) && node.anchor)
230
+ visible.set(node.anchor, node);
231
+ });
232
+ const copy = target.clone();
233
+ const originals = nodesOf(target);
234
+ const replace = new Map();
235
+ nodesOf(copy).forEach((node, index) => {
236
+ if (!isAlias(node)) {
237
+ node.anchor = undefined;
238
+ return;
239
+ }
240
+ const resolved = originals[index].resolve(doc);
241
+ if (resolved !== undefined && visible.get(node.source) !== resolved) {
242
+ replace.set(node, detachedCopy(doc, at, resolved, new Set([...expanding, target])));
243
+ }
244
+ });
245
+ if (!replace.size)
246
+ return copy;
247
+ visit(copy, (_key, node) => (isNode(node) ? replace.get(node) : undefined));
248
+ return copy;
249
+ }
250
+ function underScalar(doc, path) {
251
+ for (let depth = 1; depth < path.length; depth++) {
252
+ const node = doc.getIn(path.slice(0, depth), true);
253
+ if (node === undefined)
254
+ return false;
255
+ if (!isMap(node))
256
+ return true;
257
+ }
258
+ return false;
259
+ }
164
260
  function deleteAt(doc, path) {
261
+ materializeAliases(doc, path); // an aliased mapping is no tombstone: Auto under `spec: *pinned` deletes the pin
165
262
  for (let depth = 1; depth < path.length; depth++) {
166
263
  if (!isMap(doc.getIn(path.slice(0, depth), true)))
167
264
  return;
168
265
  }
169
266
  // deleteIn throws on an empty document — only delete keys that exist.
170
- if (doc.getIn(path) !== undefined)
171
- doc.deleteIn(path);
267
+ if (doc.getIn(path) === undefined)
268
+ return;
269
+ releaseAnchors(doc, path);
270
+ doc.deleteIn(path);
172
271
  }
173
272
  function setAt(doc, path, value) {
273
+ materializeAliases(doc, path); // edit a copy of an aliased mapping, never drop what it resolved to
274
+ releaseAnchors(doc, path);
174
275
  for (let depth = 1; depth < path.length; depth++) {
175
276
  const node = doc.getIn(path.slice(0, depth), true);
176
277
  if (node === undefined)
177
278
  break;
178
279
  if (!isMap(node)) {
179
- doc.deleteIn(path.slice(0, depth));
280
+ const prefix = path.slice(0, depth);
281
+ doc.deleteIn(prefix);
282
+ // T10: a null mask over membership lists stays a mask for every deny leaf — and the routing.allow
283
+ // block — this write does not set, so materializing one leaf never resurrects a lower layer's
284
+ // other deny lists or its allow form (judge/review/consult reach)
285
+ if (isScalar(node) && node.value === null) {
286
+ const under = (inner, outer) => outer.every((key, at) => inner[at] === key);
287
+ for (const masked of [...DENY_SCOPES.map((scope) => scope.path), ["routing", "allow"]]) {
288
+ if (under(masked, prefix) && !under(masked, path) && !under(path, masked))
289
+ doc.setIn(masked, doc.createNode(null));
290
+ }
291
+ }
180
292
  break;
181
293
  }
182
294
  }
183
295
  doc.setIn(path, value);
184
296
  }
297
+ // T10 (D-987): Fleet writes through no YAML merge key — a membership write whose edited path (the edited
298
+ // leaf or any map above it) takes keys through `<<` is refused, fail-closed, so nothing is published.
299
+ function refuseMerge(doc, priorBytes, path) {
300
+ let node = doc.contents;
301
+ for (let depth = 0; depth <= path.length && isMap(node); depth++) {
302
+ const merge = node.items.find((item) => isScalar(item.key) && typeof item.key.value === "symbol");
303
+ if (merge) {
304
+ const range = isNode(merge.value) ? merge.value.range : undefined;
305
+ const shape = range ? priorBytes.slice(range[0], range[1]).trim().replace(/\s+/g, " ") : "…";
306
+ throw new ConfigError(`${path.join(".")} takes keys through the YAML merge key \`<<: ${shape}\` in ${path.slice(0, depth).join(".") || "the document root"} — Fleet does not write through merge keys; inline the merged keys there by hand, then save again`);
307
+ }
308
+ node = depth < path.length ? node.get(path[depth], true) : undefined;
309
+ if (isAlias(node))
310
+ node = node.resolve(doc);
311
+ }
312
+ }
185
313
  function deleteEmptyMap(doc, path) {
186
314
  const node = doc.getIn(path, true);
187
315
  if (isMap(node) && node.items.length === 0)
188
- doc.deleteIn(path);
316
+ deleteAt(doc, path);
189
317
  }
190
318
  /** Apply only fields fleet authored to the parsed YAML document. Untouched nodes retain their
191
319
  * keys, ordering, scalar style, and comments; fresh provenance is written directly on its tier node. */
@@ -201,24 +329,49 @@ export function renderFleetOverlayWrite(priorBytes, write) {
201
329
  const flatChanged = new Map(FLAT_DENY_SCOPES.map((scope) => [scope.key, changed(...listsOf(write, scope))]));
202
330
  if ([...flatChanged.values()].some(Boolean) || changed(initial.allowOut, edited.allowOut)) {
203
331
  if (write.universe) {
332
+ refuseMerge(doc, priorBytes, ["routing", "allow"]);
204
333
  // Membership write: the allow form IS the fleet; deny adapters/models scopes are tombstoned
205
334
  // so a lower layer can never re-exclude behind the operator's back (workers untouched).
206
335
  const form = allowFormFromExclusions(write.universe, edited);
336
+ const universe = write.universe;
337
+ const known = (entry) => universe.some((row) => entry === row.adapter || row.models.some((m) => universeEntryMatches(row, m, entry)));
338
+ const retainedAll = [];
339
+ for (const leaf of ["adapters", "models"]) {
340
+ const retained = [...(write.preservedAllow?.[leaf] ?? []), ...authoredEntries(doc, ["routing", "allow", leaf])]
341
+ .filter((entry) => !known(entry));
342
+ if (!retained.length)
343
+ continue;
344
+ retainedAll.push(...retained);
345
+ form[leaf] = sortedUnique([...form[leaf], ...retained]);
346
+ form.excluded = true;
347
+ }
348
+ // T10 review: a retained allow entry admits a channel the allow form cannot see, so a staged flat
349
+ // deny covering it stays in deny verbatim — folded into the allow form alone, the retained entry
350
+ // would re-admit what the operator just excluded (rail out · all over an unprobed fake:U).
351
+ // Either allow leaf admits a bare model (routing matches both leaves alike), so coverage never
352
+ // depends on which leaf retained the entry.
353
+ // ponytail: a bare retained entry's adapter is unknown, so any discovered adapter id covers it;
354
+ // an identity alias of an unprobed channel is unknowable and is matched by spelling only.
355
+ const coversRetained = (entry) => retainedAll.some((kept) => {
356
+ const colon = kept.indexOf(":");
357
+ if (colon !== -1) {
358
+ const model = kept.slice(colon + 1);
359
+ return universeEntryMatches({ adapter: kept.slice(0, colon), models: [model] }, model, entry);
360
+ }
361
+ return entry === kept || universe.some((row) => row.adapter === entry);
362
+ });
207
363
  if (form.excluded) {
208
364
  const allowNode = doc.getIn(["routing", "allow"], true);
209
365
  if (allowNode !== undefined && !isMap(allowNode))
210
- doc.deleteIn(["routing", "allow"]);
211
- if (form.adapters.length) {
212
- setStringSequencePreservingComments(doc, ["routing", "allow", "adapters"], form.adapters);
213
- }
214
- else {
215
- deleteAt(doc, ["routing", "allow", "adapters"]);
216
- }
217
- if (form.models.length) {
218
- setStringSequencePreservingComments(doc, ["routing", "allow", "models"], form.models);
219
- }
220
- else {
221
- deleteAt(doc, ["routing", "allow", "models"]);
366
+ deleteAt(doc, ["routing", "allow"]);
367
+ for (const leaf of ["adapters", "models"]) {
368
+ const path = ["routing", "allow", leaf];
369
+ if (form[leaf].length)
370
+ setStringSequencePreservingComments(doc, path, form[leaf]);
371
+ else if (write.lowerAllow?.[leaf] != null)
372
+ setStringSequencePreservingComments(doc, path, null);
373
+ else
374
+ deleteAt(doc, path);
222
375
  }
223
376
  // Whole fleet out: allow stays present but empty — fail-closed, nothing admitted.
224
377
  if (doc.getIn(["routing", "allow"]) === undefined) {
@@ -226,8 +379,12 @@ export function renderFleetOverlayWrite(priorBytes, write) {
226
379
  }
227
380
  }
228
381
  else {
229
- // Whole fleet in: no restriction to express — the allow block goes away entirely.
230
- deleteAt(doc, ["routing", "allow"]);
382
+ // Whole fleet in: no restriction to express — the allow block goes away entirely, or is
383
+ // masked when a lower layer's allow would otherwise come back (T10 A/B resurrection).
384
+ if (write.lowerAllow)
385
+ setAt(doc, ["routing", "allow"], doc.createNode(null));
386
+ else
387
+ deleteAt(doc, ["routing", "allow"]);
231
388
  }
232
389
  // Authored and non-canonical entries stay in deny (LEG2-T3 finding 4, round 2 finding 2).
233
390
  // OBS-1046: only an EDITED scope is rewritten, and only when its bytes must change — an
@@ -248,7 +405,8 @@ export function renderFleetOverlayWrite(priorBytes, write) {
248
405
  const stale = authored.some((entry) => admitted.includes(entry));
249
406
  if (!touched && !stale)
250
407
  continue;
251
- const remaining = flatDenyAfterWrite(doc, path, after, write.universe);
408
+ refuseMerge(doc, priorBytes, path);
409
+ const remaining = [...new Set([...flatDenyAfterWrite(doc, path, after, write.universe), ...after.filter(coversRetained)])];
252
410
  if (remaining.length) {
253
411
  if (remaining.join("\n") !== authored.join("\n"))
254
412
  setStringSequencePreservingComments(doc, path, remaining);
@@ -262,6 +420,7 @@ export function renderFleetOverlayWrite(priorBytes, write) {
262
420
  for (const scope of FLAT_DENY_SCOPES) {
263
421
  if (!flatChanged.get(scope.key))
264
422
  continue;
423
+ refuseMerge(doc, priorBytes, scope.path);
265
424
  const after = edited[scope.key] ?? [];
266
425
  setStringSequencePreservingComments(doc, scope.path, after.length ? sortedUnique(after) : null);
267
426
  }
@@ -276,16 +435,25 @@ export function renderFleetOverlayWrite(priorBytes, write) {
276
435
  const [before, after] = listsOf(write, scope);
277
436
  if (!changed(before, after))
278
437
  continue;
438
+ refuseMerge(doc, priorBytes, scope.path);
279
439
  setStringSequencePreservingComments(doc, scope.path, after.length ? sortedUnique(after) : null);
280
440
  }
281
441
  for (const shape of new Set([...Object.keys(initial.map), ...Object.keys(edited.map)])) {
282
442
  const before = initial.map[shape];
283
443
  const after = edited.map[shape];
284
- if (JSON.stringify(before?.pin) !== JSON.stringify(after?.pin)) {
444
+ const pinPath = ["routing", "map", shape, "pin"];
445
+ const declares = after?.pin !== undefined || after?.pool !== undefined || after?.prefer !== undefined;
446
+ if (!declares && write.lowerMap?.[shape]?.pin !== undefined && JSON.stringify(before) !== JSON.stringify(after)) {
447
+ // pin: null tombstone — masks the lower-layer pin; a scalar tombstone above it already masks the entry
448
+ materializeAliases(doc, pinPath);
449
+ if (!underScalar(doc, pinPath))
450
+ setScalarPreservingComment(doc, pinPath, null);
451
+ }
452
+ else if (JSON.stringify(before?.pin) !== JSON.stringify(after?.pin)) {
285
453
  if (after?.pin === undefined)
286
- deleteAt(doc, ["routing", "map", shape, "pin"]);
454
+ deleteAt(doc, pinPath);
287
455
  else
288
- setAt(doc, ["routing", "map", shape, "pin"], doc.createNode(after.pin));
456
+ setAt(doc, pinPath, doc.createNode(after.pin));
289
457
  }
290
458
  if (JSON.stringify(before?.pool) !== JSON.stringify(after?.pool)) {
291
459
  const path = ["routing", "map", shape, "pool"];
@@ -457,8 +625,9 @@ export function renderFleetOverlayWrite(priorBytes, write) {
457
625
  // byte-identical to yaml's own stringifyComment. OBS-1046: a scalar parsed from the prior bytes
458
626
  // keeps the exact whitespace it had before its hash sign (yaml itself emits one space, so the
459
627
  // sentinel carries the rest); only a comment fleet authored gets the two-space style.
628
+ // T10 (D-936): a flow collection's trailing note (`[codex] # c`) is inline too, so it keeps its gap.
460
629
  visit(doc, (_key, node) => {
461
- if (isScalar(node) && typeof node.comment === "string" && !node.comment.includes("\n")) {
630
+ if ((isScalar(node) || (isCollection(node) && node.flow)) && typeof node.comment === "string" && !node.comment.includes("\n")) {
462
631
  const tail = node.range ? priorBytes.slice(node.range[1], node.range[2]) : "";
463
632
  const gap = /^([ \t]+)#/.exec(tail)?.[1] ?? " ";
464
633
  node.comment = `${INLINE_COMMENT_SENTINEL}${gap.slice(1)}#${node.comment}`;
@@ -474,6 +643,39 @@ export function renderFleetOverlayWrite(priorBytes, write) {
474
643
  flowCollectionPadding: false,
475
644
  });
476
645
  }
646
+ /** T10: the 1-based line of the key that holds `path` in these overlay bytes — the path's own key, the
647
+ * key whose `null` masks it from above, or a `<<` merge key on it (D-987: the write there refuses) — else
648
+ * undefined (the overlay neither declares nor masks it). */
649
+ export function overlayHoldingLine(bytes, path) {
650
+ const doc = parseDocument(bytes);
651
+ if (doc.errors.length)
652
+ return undefined;
653
+ const lineOf = (offset) => bytes.slice(0, offset).split("\n").length;
654
+ let node = doc.contents;
655
+ let line;
656
+ for (const key of path) {
657
+ if (isAlias(node))
658
+ node = node.resolve(doc);
659
+ if (line !== undefined && isScalar(node) && node.value === null)
660
+ return line; // a mask at or above
661
+ if (!isMap(node))
662
+ return undefined;
663
+ const merge = node.items.find((item) => isScalar(item.key) && typeof item.key.value === "symbol");
664
+ const pair = merge ?? node.items.find((item) => isScalar(item.key) && String(item.key.value) === key);
665
+ if (!pair || !isScalar(pair.key) || !pair.key.range)
666
+ return undefined;
667
+ line = lineOf(pair.key.range[0]);
668
+ if (merge)
669
+ return line;
670
+ node = pair.value;
671
+ }
672
+ return line;
673
+ }
674
+ /** T10: does `path` hold a mapping in these overlay bytes — neither absent nor a `null` mask? */
675
+ export function overlayHoldsMap(bytes, path) {
676
+ const doc = parseDocument(bytes);
677
+ return !doc.errors.length && isMap(doc.getIn(path, true));
678
+ }
477
679
  /** Build the repo overlay fragment fleet would write for edits since session start. */
478
680
  export function fleetRepoOverlayFromDelta(initial, edited, existingRepo = {}, firstTouches = {}, universe) {
479
681
  if (fleetEditableEquals(initial, edited))
@@ -5,7 +5,7 @@ import { type ExecutorDriver, type FocusTarget, type FocusResult, type NotifyOpt
5
5
  /** The response families the ONE shared envelope parser serves. There is no second JSON seam. */
6
6
  export declare const ORCA_RESPONSE_FAMILIES: readonly ["status", "create", "list", "read", "send", "wait", "show", "close", "worktree-current", "worktree-set", "hooks-status", "split"];
7
7
  export type OrcaFamily = (typeof ORCA_RESPONSE_FAMILIES)[number];
8
- export declare const ORCA_FIXTURE_VERSION = "1.4.195";
8
+ export declare const ORCA_FIXTURE_VERSION = "1.4.218";
9
9
  export declare const ORCA_CLI_COMMAND_ENV = "ORCA_CLI_COMMAND";
10
10
  export declare const STALE_HANDLE_CODE = "terminal_handle_stale";
11
11
  export declare const TERMINAL_GONE_CODE = "terminal_gone";
@@ -31,16 +31,18 @@ export declare class OrcaError extends Error {
31
31
  readonly raw: string;
32
32
  readonly code?: string;
33
33
  readonly runtimeId?: string;
34
+ readonly launchCause?: SeatLaunchCause;
34
35
  constructor(family: string, reason: string, raw: string, opts?: {
35
36
  code?: string;
36
37
  runtimeId?: string;
38
+ launchCause?: SeatLaunchCause;
37
39
  });
38
40
  }
39
41
  /** The slot cannot be addressed: dead/unknown terminal record, or a handle that cannot be recovered
40
42
  * to exactly one owned terminal in the slot's own worktree. Never a silent false or empty string. */
41
43
  export declare class OrcaUnavailableError extends OrcaError {
42
44
  readonly terminalStatus?: string | undefined;
43
- constructor(family: string, reason: string, raw: string, terminalStatus?: string | undefined);
45
+ constructor(family: string, reason: string, raw: string, terminalStatus?: string | undefined, launchCause?: SeatLaunchCause);
44
46
  }
45
47
  export interface OrcaEnvelope {
46
48
  result: Record<string, unknown>;
@@ -83,6 +85,10 @@ export declare function terminalWorktree(term: Record<string, unknown>): string
83
85
  export declare function canonicalWorktreePath(path: string): string;
84
86
  /** The proof line a worker terminal prints first: create, recovery and focus read it back. */
85
87
  export declare const CHECKOUT_MARK = "TICKMARKR_CHECKOUT";
88
+ /** E1: the one typed launch cause a driver attaches to its launch failure. `checkout-proof-timeout`
89
+ * means the terminal printed NO proof frame by the ceiling — a slow host, not a misplaced seat. A
90
+ * foreign or malformed frame is placement evidence and carries no cause (never a timeout exemption). */
91
+ export type SeatLaunchCause = "checkout-proof-timeout";
86
92
  /** The exact bytes the create command prints as its first line. */
87
93
  export declare function checkoutProofLine(checkout: string): string;
88
94
  /** Every complete frame in a scrollback, decoded and canonicalized; whether an incomplete one was seen. */
@@ -170,6 +176,17 @@ export interface OrcaDriverOpts {
170
176
  probeStalenessMs?: number;
171
177
  launchingHandle?: string;
172
178
  }
179
+ /**
180
+ * F (D-832): what one proof page's OWN metadata says about the scrollback it was read from — the closed
181
+ * seam checkoutProven decides paging and absence by. `complete`: explicit `limited:false` and
182
+ * `truncated:false` with cursors that agree — nothing lies past this page. `partial`: explicit booleans,
183
+ * one of them true, cursors that agree — more scrollback exists. `unread`: everything else — a flag
184
+ * missing or not a boolean, a cursor that is not a string, `limited:false` with a next cursor short of
185
+ * the latest one (the contradictory "last" page), a blind stream page
186
+ * (isBlindStreamPage), or a source that is not the stream (`screen-unavailable`). Missing metadata is
187
+ * never inferred complete: its page's bytes still count as read evidence, but no absence rests on it.
188
+ */
189
+ export declare function classifyProofPage(term: Record<string, unknown>): "complete" | "partial" | "unread";
173
190
  export declare class OrcaDriver implements ExecutorDriver {
174
191
  id: string;
175
192
  interactive: boolean;
@@ -250,6 +267,21 @@ export declare class OrcaDriver implements ExecutorDriver {
250
267
  * that supplied the ownership listing — else another terminal's or another runtime's bytes were
251
268
  * answered and nothing is proven. Proven means provesCheckout: exact canonical full-path equality
252
269
  * of a complete frame, no other checkout named, no incomplete frame.
270
+ * E1: with a `deadline` (create's window) there is ONE absolute deadline: a read is issued only while
271
+ * budget is left and is handed exactly that budget, and a page that still lands past it is late —
272
+ * nothing late proves the checkout. `seen` is what the scrollback showed, for the typed timeout:
273
+ * `misplaced` — a frame naming another checkout, a cut frame or an unparseable mark in anything read
274
+ * (the anchor's tail and every page included, even when paging stopped or a later read threw);
275
+ * `absent` — ONLY when every page down to the start of the scrollback was read before the ceiling
276
+ * and none carries a trace of the mark; `unread` — every other exit: PROOF_PAGES reached, a limited
277
+ * read with no (or the same) next cursor or a truncated anchor without one, a read that threw, a read
278
+ * the ceiling cut off or never let start (the anchor included), or a page that landed late (even one
279
+ * carrying the right proof), a malformed page (its tail or cursors), a blind stream page
280
+ * (isBlindStreamPage), or — F (D-832) — any page read whose paging metadata classifyProofPage cannot
281
+ * call complete or partial (flags missing, not boolean or contradicted; even when they were omitted,
282
+ * that page's bytes stay evidence and its matching proof still proves). Unread output is unknown —
283
+ * never an empty page, never absence.
284
+ * Without a deadline (reconcile, recovery) only `proven` is consumed.
253
285
  */
254
286
  private checkoutProven;
255
287
  private recover;