tickmarkr 2.6.3 → 2.6.5

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 (79) hide show
  1. package/README.md +10 -2
  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 +25 -0
  7. package/dist/adapters/codex-commit-check.js +108 -0
  8. package/dist/adapters/codex.d.ts +2 -1
  9. package/dist/adapters/codex.js +26 -5
  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 +2 -1
  13. package/dist/adapters/model-lints.js +52 -1
  14. package/dist/adapters/model-windows.d.ts +2 -1
  15. package/dist/adapters/model-windows.js +18 -7
  16. package/dist/adapters/prompt.js +3 -2
  17. package/dist/adapters/types.d.ts +13 -0
  18. package/dist/cli/commands/approve.js +37 -6
  19. package/dist/cli/commands/doctor.d.ts +4 -0
  20. package/dist/cli/commands/doctor.js +20 -3
  21. package/dist/cli/commands/fleet.d.ts +10 -1
  22. package/dist/cli/commands/fleet.js +279 -95
  23. package/dist/cli/commands/init.js +10 -4
  24. package/dist/cli/commands/plan.js +65 -4
  25. package/dist/cli/commands/report.d.ts +39 -2
  26. package/dist/cli/commands/report.js +227 -10
  27. package/dist/cli/commands/resume.js +2 -4
  28. package/dist/cli/commands/run.d.ts +11 -0
  29. package/dist/cli/commands/run.js +33 -5
  30. package/dist/cli/commands/status.js +30 -3
  31. package/dist/cli/commands/verify.d.ts +1 -1
  32. package/dist/cli/commands/verify.js +356 -123
  33. package/dist/compile/native.js +130 -0
  34. package/dist/config/config.d.ts +25 -7
  35. package/dist/config/config.js +92 -11
  36. package/dist/config/fleet-overlay.d.ts +2 -1
  37. package/dist/config/fleet-overlay.js +77 -4
  38. package/dist/drivers/orca.d.ts +19 -1
  39. package/dist/drivers/orca.js +161 -31
  40. package/dist/gates/cache.d.ts +4 -0
  41. package/dist/gates/cache.js +10 -2
  42. package/dist/gates/llm.d.ts +4 -1
  43. package/dist/gates/llm.js +6 -1
  44. package/dist/gates/review.js +8 -3
  45. package/dist/gates/run-gates.d.ts +3 -2
  46. package/dist/gates/run-gates.js +91 -50
  47. package/dist/route/router.d.ts +11 -0
  48. package/dist/route/router.js +29 -8
  49. package/dist/run/daemon.js +158 -22
  50. package/dist/run/git.js +9 -0
  51. package/dist/run/journal.d.ts +101 -1
  52. package/dist/run/journal.js +352 -2
  53. package/dist/run/launchservices-check.d.ts +56 -0
  54. package/dist/run/launchservices-check.js +117 -0
  55. package/dist/run/operator-state.d.ts +22 -2
  56. package/dist/run/operator-state.js +20 -3
  57. package/dist/run/title-environment.d.ts +45 -0
  58. package/dist/run/title-environment.js +127 -0
  59. package/dist/run/title-preload.cjs +38 -0
  60. package/dist/run/title-preload.d.cts +1 -0
  61. package/dist/tui/cockpit/board.js +12 -6
  62. package/dist/tui/cockpit/keys.d.ts +8 -0
  63. package/dist/tui/cockpit/keys.js +4 -1
  64. package/dist/tui/cockpit/layout.d.ts +2 -2
  65. package/dist/tui/cockpit/layout.js +2 -2
  66. package/dist/tui/cockpit/live-runtime.js +46 -20
  67. package/dist/tui/cockpit/live-store.d.ts +30 -1
  68. package/dist/tui/cockpit/live-store.js +134 -18
  69. package/dist/tui/cockpit/log-view.d.ts +28 -0
  70. package/dist/tui/cockpit/log-view.js +60 -0
  71. package/dist/tui/cockpit/run-view.js +4 -0
  72. package/dist/tui/cockpit/shell.d.ts +2 -1
  73. package/dist/tui/cockpit/shell.js +6 -4
  74. package/dist/tui/ink/fleet-app.d.ts +12 -6
  75. package/dist/tui/ink/fleet-app.js +17 -11
  76. package/package.json +1 -1
  77. package/skills/tickmarkr-auto/SKILL.md +33 -3
  78. package/skills/tickmarkr-loop/SKILL.md +55 -5
  79. package/skills/tickmarkr-overseer/SKILL.md +83 -8
@@ -1,6 +1,6 @@
1
1
  import { channelKey, channelsFromConfig } from "../adapters/types.js";
2
2
  import { TIER_RANK } from "../config/config.js";
3
- import { channelRouteIdentity, disallowedBy, modelRouteIdentity, routingModelProvider } from "./preference.js";
3
+ import { disallowedBy, routingModelProvider } from "./preference.js";
4
4
  import { cellOf, EXPLORE_CAP, explorationBonus, learnedScore, MIN_SAMPLES } from "./profile.js";
5
5
  export const NO_EXPLORE_ENV = "TICKMARKR_NO_EXPLORE";
6
6
  // OBS-89 (v1.60): the TICKMARKR_QUALITY variable is RETIRED — nothing in src reads it anymore and
@@ -124,11 +124,33 @@ function spreadFrontierTies(sorted, task, prefer, tied) {
124
124
  }
125
125
  return out;
126
126
  }
127
+ /**
128
+ * F (D-718): the one exclusion meaning route(), nextChannel() and climbChannel() share — explicit
129
+ * exclusions, demotions, tried and escalation history, and climb skips — exported so resume can test a
130
+ * restored assignment the same way. An excluded raw channel key always excludes itself (the fallback).
131
+ * The exclusion extends to another offered channel only when BOTH offered channels carry a probed
132
+ * identity and the two are equal. A missing identity is neutral: it never matches and never blocks.
133
+ * Residual, by ruling: an unprobed alias of an excluded model stays eligible by its raw key — this is
134
+ * not complete alias exclusion; reopen on an executed re-dispatch of a tried model through an alias
135
+ * with no probed identity. The daemon's nextChannel-null exhaustion fallback is not this policy's.
136
+ */
137
+ export function channelExclusion(offered, excluded) {
138
+ const keys = new Set(excluded);
139
+ const identityOf = new Map(offered.flatMap((c) => (c.identity ? [[channelKey(c), c.identity]] : [])));
140
+ const identities = new Set([...keys].flatMap((k) => identityOf.get(k) ?? []));
141
+ return (seat) => {
142
+ const key = channelKey(seat);
143
+ const identity = identityOf.get(key);
144
+ return keys.has(key) || (identity !== undefined && identities.has(identity));
145
+ };
146
+ }
127
147
  function withoutExcluded(channels, exclude) {
128
148
  if (!exclude?.size)
129
149
  return channels;
130
150
  // OBS-57: in-run demotion after consecutive no-trailer windows — route around poisoned channels for the rest of the run.
131
- return channels.filter((c) => !exclude.has(channelKey(c)));
151
+ // D-718: a demoted channel's probed-identical alias is routed around too; an unprobed one is not.
152
+ const excluded = channelExclusion(channels, exclude);
153
+ return channels.filter((c) => !excluded(c));
132
154
  }
133
155
  // Pools spread providers before tier, independently within each marginal-cost run. Stable
134
156
  // cost sorting preserves declaration order when forming groups; gateways share the served
@@ -394,19 +416,18 @@ export function route(task, cfg, channels, profile, preferCtx, exclude, exploreC
394
416
  return { assignment: toAssignment(eligible[0]), ladder: ladderFor(task, entry), lints, provenance: `${degraded}${bound}, marginal-cost auto (${chosenBy})`, ...(deviation ? { deviation } : {}) };
395
417
  }
396
418
  function candidatePool(current, task, cfg, channels, tried, tierPredicate, exclude) {
397
- channels = withoutExcluded(channels, exclude).filter((c) => inTaskPool(task, cfg, channelKey(c)));
419
+ // D-718: tried/escalated/demoted keys share channelExclusion's meaning, resolved against every offered
420
+ // channel; the provider-outage rule below still reads the raw-key remainder exactly as before.
421
+ const excluded = channelExclusion(channels, [...(exclude ?? []), ...tried]);
422
+ channels = channels.filter((c) => !exclude?.has(channelKey(c)) && inTaskPool(task, cfg, channelKey(c)));
398
423
  const triedKeys = new Set(tried);
399
- const triedIdentities = new Set(tried.map((key) => {
400
- const channel = channels.find((c) => channelKey(c) === key);
401
- return channel ? modelRouteIdentity(channel.model, channel.vendor) : channelRouteIdentity(key);
402
- }));
403
424
  // excludeAdapter is expanded by the daemon into every channel key of the failed adapter. Once that
404
425
  // complete set is present, the outage follows the current served provider across gateway aliases.
405
426
  const currentAdapterExcluded = channels.some((c) => c.adapter === current.adapter)
406
427
  && channels.filter((c) => c.adapter === current.adapter).every((c) => triedKeys.has(channelKey(c)));
407
428
  const currentChannel = channels.find((c) => c.adapter === current.adapter && c.model === current.model);
408
429
  const excludedProvider = currentAdapterExcluded ? routingModelProvider(current.model, currentChannel?.vendor) : undefined;
409
- return channels.filter((c) => !triedIdentities.has(modelRouteIdentity(c.model, c.vendor))
430
+ return channels.filter((c) => !excluded(c)
410
431
  && (!excludedProvider || routingModelProvider(c.model, c.vendor) !== excludedProvider)
411
432
  && tierPredicate(c.tier));
412
433
  }
@@ -36,10 +36,10 @@ import { changeRepresented, cleanupRunWorktrees, deriveForkCap, FORK_CAP_ENV, gi
36
36
  import { runInteractiveSeed } from "./interactive-seed.js";
37
37
  import { classifyRepairDisposition, resolveScopeHints } from "./repair-disposition.js";
38
38
  import { applyScopeAmendments, activeRetryBan, interruptedAttempt, RESUME_HARVEST_SOURCE, resumeHarvestAuthor, approvalAction, APPROVAL_REFUSED, bindingToken, effectiveEvents, foldDecisions, physicalLine, staleApprovals, classifyTaskFailure, classifyWorkerResultCause, deferredReviewFindings, engagementComparable, formatPriorFindingEvidence, GATE_FINGERPRINT_CAP, GATE_SATISFIED_RELEASE, identicalGateFailures, isDeferredFinding, journaledFailureBrief, Journal, loadRoutingProfile, newRunId, normalizeGateFailure, outstandingConsultGuidance, readableExcerpt, receiptOrigin, outstandingReviewFindings, pendingApprovalActions, pendingRechecks, pendingRepairFindings, phaseForGate, priorJudgments, readPriorRunEvidence, recordedTaskFailureKind, reraisedReviewChains, RECHECK_RELEASE, renderStructuredReviewFinding, repairReachSinceApproval, repairsSinceApproval, reviewRoundsSinceApproval, runHasEnded, standingRulings, structuredFindings, upheldFeedbackByTask } from "./journal.js";
39
- import { gateReviewerFloor, isDiffCapPark, isGarbageReview, pickReviewer } from "../gates/review.js";
39
+ import { carriedAuthorVendors, gateReviewerFloor, isDiffCapPark, isGarbageReview, pickReviewer } from "../gates/review.js";
40
40
  import { acquireApprovalSerialization, acquireRunLock, isPidLive, releaseRunLock } from "./lock.js";
41
41
  import { ensureIntegration, integrationBranch, integrationHead, mergeTask, reusedTipEvidence, verifyIntegrationTip } from "./merge.js";
42
- import { climbChannel, inTaskPool, marginalCostRank, nextChannel, poolExhaustion, route, taskPool } from "../route/router.js";
42
+ import { channelExclusion, climbChannel, inTaskPool, marginalCostRank, nextChannel, poolExhaustion, route, taskPool } from "../route/router.js";
43
43
  import { desiredPanes } from "./reconcile.js";
44
44
  import { readTierLiveness, readWatchBoard, supervisionPresencePath } from "./supervision.js";
45
45
  import { harvestCpuFlatWindowMs, NUDGEABLE_ADAPTERS, PANE_READ_ROWS, StallProgressTracker, stallSnapshotBannerRows, WorkerTreeCpuAccountant, reapOwnedProcessGroup, readOwnedProcessGroup, } from "./stall.js";
@@ -377,6 +377,49 @@ function classifyInfraResult(g) {
377
377
  const TIMEOUT_SHAPED_RE = /\b(?:Test|Hook) timed out in (?:\d+|#)\s*ms\b|\bexpected (?:\d+|#)(?:\.(?:\d+|#))? to be (?:less than|below)(?: or equal to)? (?:\d+|#)/i;
378
378
  const RUNNER_INFRA_DIAGNOSTIC_RE = /\bnever-started (?:[1-9]\d*|#)(?![\d#])|\[vitest-worker\]: Timeout calling\b/;
379
379
  const timeoutShaped = (details) => TIMEOUT_SHAPED_RE.test(readableExcerpt(details));
380
+ /** C (E1): the one no-verdict that is the host's slowness, not the seat's — the same predicate run-gates
381
+ * applies to its run-scoped tally, read here for the live task strike and the resume reconstruction. */
382
+ const proofTimeoutNote = (data) => data.cause === "seat-launch-failed" && data.launchCause === "checkout-proof-timeout";
383
+ /** C: the gate-fresh-forced reason that bounds the out-of-scope re-execution, one per journaled subject. */
384
+ const OUTSIDE_SCOPE_RED = "no-commit-out-of-scope-red";
385
+ const NAMED_FAILURE_FILE_RE = /^\s*(\S+\.(?:test|spec)\.[cm]?[jt]sx?)\s+>/gm;
386
+ /** D-854: the runner certificate the vitest manifest path appends to a red (test-manifest.ts). */
387
+ const RUNNER_CERTIFICATE_RE = /runner-level diagnostic: never-started ([^;\s]+); reporter errors ([^;\s]+)/g;
388
+ /** C: a journaled test red whose failing-file attribution is nonempty, trustworthy and complete (repair
389
+ * selection's own rule: a behavioral red on a real subject from a full suite or a screen that names
390
+ * every failure), POSITIVELY certified complete by its runner, uncontradicted by the files its own text
391
+ * names, and wholly outside a declared files[]. Empty files[] is unrestricted and never qualifies;
392
+ * every other shape keeps its replay. */
393
+ function outsideScopeRed(t, taskId, data, commit) {
394
+ if (!t.files.length || data.gate !== "test" || data.pass !== false || data.skipped === true || data.commit !== commit
395
+ // a failed producer — an infra row, a dirty-tree refusal, a recovery the runner could not finish —
396
+ // never attributes anything, whatever files it lists
397
+ || data.infra === true || data.dirtyWorktree === true || data.recoveryBlocked !== undefined)
398
+ return false;
399
+ const attribution = repairSelectionDecision([{ event: "gate-result", taskId, data }], taskId, true);
400
+ if (attribution.reason !== "known-failing-files" || attribution.requiredFiles.length === 0)
401
+ return false;
402
+ const text = readableExcerpt(String(data.details ?? ""));
403
+ // D-854: validate, never filter. A collection failure has no module record, so failingFiles omits it
404
+ // and it lives only in the certificate's counts; any never-started file or reporter error, an unknown
405
+ // count, or no certificate at all leaves the attribution unproven.
406
+ // ponytail: a nonzero count never qualifies, even when each diagnostic names an outside file — the
407
+ // details interleave diagnostics with the stdout tail; map them only if outside-scope collection reds need it.
408
+ const certificates = [...text.matchAll(RUNNER_CERTIFICATE_RE)];
409
+ if (!certificates.length || certificates.some((m) => m[1] !== "0" || m[2] !== "0"))
410
+ return false;
411
+ const named = [...text.matchAll(ATTRIBUTED_FILE_RE), ...text.matchAll(NAMED_FAILURE_FILE_RE)].map((m) => m[1].replace(/^\.\//, ""));
412
+ if (named.some((file) => !attribution.requiredFiles.includes(file)))
413
+ return false; // contradictory
414
+ const inScope = filesGlob(t.files);
415
+ return attribution.requiredFiles.every((file) => !inScope(file));
416
+ }
417
+ /** C: every test red a round journaled on `commit` is an out-of-scope red (none means none). */
418
+ const allOutsideScopeReds = (t, rows, commit) => {
419
+ const reds = rows.filter((e) => e.event === "gate-result" && e.data.gate === "test" && e.data.pass === false
420
+ && e.data.skipped !== true && e.data.commit === commit);
421
+ return reds.length > 0 && reds.every((e) => outsideScopeRed(t, e.taskId ?? t.id, e.data, commit));
422
+ };
380
423
  const ATTRIBUTED_FILE_RE = /^\s*(?:FAIL|×|✗)\s+(?:\|[^|\n]*\|\s+)?(\S+\.(?:test|spec)\.[cm]?[jt]sx?)\b/gm;
381
424
  /** The failing files a red attributes to itself: the runner's own list, else its FAIL headlines. */
382
425
  const attributedFailingFiles = (g) => [...new Set(Array.isArray(g.meta?.failingFiles) && g.meta.failingFiles.length
@@ -2203,10 +2246,13 @@ export async function runDaemon(repoRoot, opts = {}) {
2203
2246
  // is what bounds a seat that flakes once per task (or once per session) across the run. Seeded from the
2204
2247
  // journal's review-no-verdict rows — the same notes noteReviewEvent appends — so a resumed daemon keeps
2205
2248
  // the count; run-gates appends to it in place, exactly as it does demotedReviewers.
2249
+ // C (E1): a checkout-proof timeout never struck the live tally (run-gates), so a resume never seeds one.
2206
2250
  const reviewNoVerdicts = new Map();
2207
2251
  for (const event of journal.read()) {
2208
2252
  if (event.event !== "review-no-verdict" || event.data.noVerdict !== true || typeof event.data.reviewer !== "string")
2209
2253
  continue;
2254
+ if (proofTimeoutNote(event.data))
2255
+ continue;
2210
2256
  reviewNoVerdicts.set(event.data.reviewer, [...(reviewNoVerdicts.get(event.data.reviewer) ?? []), String(event.data.cause)]);
2211
2257
  }
2212
2258
  const reviewHistory = journal.read()
@@ -2930,6 +2976,18 @@ export async function runDaemon(repoRoot, opts = {}) {
2930
2976
  return red !== undefined
2931
2977
  && identicalGateFailures(journal.read(), t.id, "test", normalizeGateFailure(String(red.data.details))) < GATE_FINGERPRINT_CAP;
2932
2978
  };
2979
+ // C: an unchanged cached test red attributed wholly outside files[] is re-executed ONCE per journaled
2980
+ // subject — the worker cannot repair those files, so a copy of the red would only buy another round.
2981
+ const owesOutsideScopeRefresh = (t, priorRows, commit) => allOutsideScopeReds(t, priorRows, commit)
2982
+ && !journal.read().some((e) => e.taskId === t.id && e.event === "gate-fresh-forced"
2983
+ && e.data.reason === OUTSIDE_SCOPE_RED && e.data.commit === commit);
2984
+ // C: the forced re-execution's own round failed again only outside files[]: independent evidence the
2985
+ // worker cannot act on. It keeps its red row and parks gate-fail before any cap, consult or repair move.
2986
+ const freshOutsideScopeRed = (t, commit) => {
2987
+ const own = journal.read().filter((e) => e.taskId === t.id);
2988
+ return allOutsideScopeReds(t, own.slice(own.map((e) => e.event === "phase-start" && e.data.phase === "gates").lastIndexOf(true) + 1), commit);
2989
+ };
2990
+ const outsideScopePark = (t) => gateFailApprovalReason(t.id, "test: a fresh re-execution of the unchanged subject failed again only in files outside files[] — no repair can reach them");
2933
2991
  // OBS-1106 residual: before a timeout or wall-budget red accompanied by runner infra diagnostics is
2934
2992
  // charged, its attributed failing files are re-run ONCE, on the same checkout, under the suite
2935
2993
  // lease. Only a reproduced assertion is charged. A pass, an infra rerun, a rerun naming none of the
@@ -3215,10 +3273,12 @@ export async function runDaemon(repoRoot, opts = {}) {
3215
3273
  // and a lastAssignment at zero attempts is a first dispatch whose capacity requeue was taken back:
3216
3274
  // the seat is still in force, so restore it instead of failing over its own tried[] entry early.
3217
3275
  // OBS-1187: a restored seat must still be a member of the task's pool (the operator may have closed it).
3276
+ // F/W (D-718): and not excluded under the router's one meaning — its raw key, or a probed identity
3277
+ // it shares with an excluded channel; an unprobed seat is excluded by its raw key alone.
3218
3278
  if (!hintsChanged && rs?.lastAssignment
3219
3279
  && channels.some((c) => channelKey(c) === channelKey(rs.lastAssignment))
3220
3280
  && inTaskPool(t, cfg, channelKey(rs.lastAssignment))
3221
- && !demotedChannels.has(channelKey(rs.lastAssignment))) {
3281
+ && !channelExclusion(channels, demotedChannels)(rs.lastAssignment)) {
3222
3282
  assignment = rs.lastAssignment; // restore the consult-chosen assignment (bypasses route()'s static re-pick)
3223
3283
  }
3224
3284
  else if (!hintsChanged && rs && rs.tried.length && !t.routingHints?.pin) {
@@ -3295,11 +3355,15 @@ export async function runDaemon(repoRoot, opts = {}) {
3295
3355
  }
3296
3356
  // Keep one live list: recovery retries must see exclusions added by onGate during the round.
3297
3357
  const badReviewers = [...replayedReviewerExclusions];
3358
+ // C (E1): a checkout-proof timeout is the host being slow, not the seat failing — it is excluded for
3359
+ // the CURRENT round only (so an always-timeout pool still ends the round), never struck task-wide.
3360
+ let roundTimeouts = [];
3298
3361
  const noteReviewEvent = (e) => {
3299
3362
  journal.append(e.name, t.id, e.payload);
3300
3363
  if (e.name === "review-no-verdict" && typeof e.payload.reviewer === "string") {
3301
- if (!badReviewers.includes(e.payload.reviewer))
3302
- badReviewers.push(e.payload.reviewer);
3364
+ const strikes = proofTimeoutNote(e.payload) ? roundTimeouts : badReviewers;
3365
+ if (!strikes.includes(e.payload.reviewer))
3366
+ strikes.push(e.payload.reviewer);
3303
3367
  }
3304
3368
  // OBS-1196: a seat whose same-subject re-emission delivered a valid verdict was a delivery flake,
3305
3369
  // not a bad reviewer — it stays seatable for this task's later rounds.
@@ -3335,17 +3399,31 @@ export async function runDaemon(repoRoot, opts = {}) {
3335
3399
  gateRound: journal.read().filter((row) => row.taskId === task.id
3336
3400
  && row.event === "phase-start" && row.data.phase === "gates").length,
3337
3401
  };
3338
- const round = await runGates(task, ctx);
3402
+ roundTimeouts = [];
3403
+ // C: every gate-round boundary — a returned or a THROWN round — publishes the one row held behind
3404
+ // a still-pending sibling and forgets the pending set, before any park or task-failed row lands.
3405
+ const gateRound = async (subject) => {
3406
+ try {
3407
+ return await runGates(subject, { ...ctx, excludeReviewers: [...badReviewers, ...roundTimeouts] });
3408
+ }
3409
+ finally {
3410
+ settleParallelRound();
3411
+ }
3412
+ };
3413
+ const round = await gateRound(task);
3339
3414
  let review = round.results.find((g) => g.gate === "review");
3415
+ // The recovery pick carries every preserved author, exactly as reviewGate's own pick does.
3416
+ const authors = ctx.carriedAuthors?.length ? [...ctx.carriedAuthors] : [channelKey(ctx.author)];
3340
3417
  while (review?.meta?.noVerdict === true) {
3341
3418
  // Recovery must honor the gate's floor, including the seats that just failed to
3342
- // return a verdict; a below-floor alternative cannot replace the infra result.
3343
- const { floor } = gateReviewerFloor(task, ctx.cfg, ctx.author, ctx.channels, [...(ctx.priorReviewers ?? []), ...badReviewers]);
3344
- const next = pickReviewer(ctx.author, ctx.channels, badReviewers, cfg.review.prefer ?? [], floor, reviewHistory, undefined, demotedReviewers);
3419
+ // return a verdict; a below-floor alternative cannot replace the infra result. This round's
3420
+ // proof timeouts count too — runGates folds every excluded seat into its own floor.
3421
+ const { floor } = gateReviewerFloor(task, ctx.cfg, ctx.author, ctx.channels, [...(ctx.priorReviewers ?? []), ...badReviewers, ...roundTimeouts]);
3422
+ const next = pickReviewer(ctx.author, ctx.channels, [...badReviewers, ...roundTimeouts], cfg.review.prefer ?? [], floor, reviewHistory, undefined, demotedReviewers, carriedAuthorVendors(ctx.channels, authors), authors);
3345
3423
  if (!next)
3346
3424
  break;
3347
3425
  journal.append("review-infra-retry", t.id, { reviewer: channelKey(next), cause: review.meta.cause });
3348
- const retried = await runGates({ ...task, gates: ["review"] }, ctx);
3426
+ const retried = await gateRound({ ...task, gates: ["review"] });
3349
3427
  const replacement = retried.results.find((g) => g.gate === "review");
3350
3428
  // A dirty tree or another pre-dispatch refusal must still fail closed.
3351
3429
  if (!replacement) {
@@ -3372,13 +3450,16 @@ export async function runDaemon(repoRoot, opts = {}) {
3372
3450
  gate: "review", flaked: rr.flaked, retried: rr.retried,
3373
3451
  ...(g.meta?.unparseable === true ? { secondUnparseable: true } : {}),
3374
3452
  });
3375
- badReviewers.push(rr.flaked);
3453
+ // C (E1): a seat re-routed after a checkout-proof timeout is excluded for this round only.
3454
+ if (!roundTimeouts.includes(rr.flaked))
3455
+ badReviewers.push(rr.flaked);
3376
3456
  }
3377
3457
  };
3378
3458
  // v1.85 T3 (ruling R4): every BLOCKING review/judge result lands its findings in the journal
3379
3459
  // structured — class + canonical path + stable symbol — so a retry, a consult or an auto-uphold
3380
3460
  // decision reads identity instead of re-parsing prose, and line-number churn is not a new finding.
3381
3461
  // One helper, both onGate sites (satisfied-gate resume + main attempt loop).
3462
+ // `journaledGates` narrows replayMeasurement to the gates an interrupted round already journaled (C).
3382
3463
  let gateSubject;
3383
3464
  const journalGateResult = (g) => {
3384
3465
  const blocking = g.meta?.infra !== true && gateFailed(g) && (g.gate === "review" || g.gate === "acceptance");
@@ -3414,7 +3495,7 @@ export async function runDaemon(repoRoot, opts = {}) {
3414
3495
  // for the worktree it ran in; absent only for a row that never went through the battery.
3415
3496
  ...(g.meta?.verification !== undefined ? { verification: g.meta.verification } : {}),
3416
3497
  ...(gateSubject ? { commit: gateSubject.commit, attempt: gateSubject.attempt } : {}),
3417
- ...(gateSubject?.replayMeasurement ? { replayMeasurement: true } : {}),
3498
+ ...(gateSubject?.replayMeasurement && (gateSubject.journaledGates?.includes(g.gate) ?? true) ? { replayMeasurement: true } : {}),
3418
3499
  ...(gateSubject?.replayedFromAttempt !== undefined ? { replayedFromAttempt: gateSubject.replayedFromAttempt } : {}),
3419
3500
  ...(g.meta?.skipped === true || noVerdictReview ? { skipped: true } : {}),
3420
3501
  // T9: an infra-only exit is journaled AS one. The operator reading a red `test` row has to
@@ -3528,10 +3609,17 @@ export async function runDaemon(repoRoot, opts = {}) {
3528
3609
  // ever held, and only while an earlier sibling is still in flight — a review that finishes first
3529
3610
  // waits for acceptance, never the reverse. That keeps T4's durability where it pays (the first
3530
3611
  // verdict to land is still published immediately) and bounds the exposure to one row for the
3531
- // remainder of one already-running gate. A gate that THROWS kills the round before merge, so a
3532
- // row held behind it is lost with the round it belonged to — not a verdict that could have merged.
3612
+ // remainder of one already-running gate. A gate that THROWS still ends its round: C publishes the
3613
+ // row held behind it at that boundary (settleParallelRound), so it is never lost or carried over.
3533
3614
  const parallelPending = new Set();
3534
3615
  let heldParallel;
3616
+ const settleParallelRound = () => {
3617
+ parallelPending.clear();
3618
+ const held = heldParallel;
3619
+ heldParallel = undefined;
3620
+ if (liveEngagement())
3621
+ held?.();
3622
+ };
3535
3623
  const notePhaseStart = (e) => {
3536
3624
  const active = activeGatePhases.get(t.id) ?? new Set();
3537
3625
  active.add(e.gate);
@@ -4016,7 +4104,10 @@ export async function runDaemon(repoRoot, opts = {}) {
4016
4104
  const followsApproved = GATE_NAMES.indexOf(gate) > satisfiedIndex;
4017
4105
  const otherRed = prior?.pass === false;
4018
4106
  const needsFullSuite = gate === "test" && prior?.fullSuite !== true;
4019
- return followsApproved || otherRed || needsFullSuite;
4107
+ // W: a red evidence/scope screen ends the round before the battery, so a gate that PRECEDES
4108
+ // the waived one may never have run: no prior-round row is no verdict — run it as declared.
4109
+ const unobserved = prior === undefined;
4110
+ return followsApproved || otherRed || needsFullSuite || unobserved;
4020
4111
  });
4021
4112
  }
4022
4113
  else {
@@ -4091,6 +4182,7 @@ export async function runDaemon(repoRoot, opts = {}) {
4091
4182
  journal.append("gate-provisioned", t.id, provisionedRow);
4092
4183
  const resumedTask = { ...t, gates: remainingGates };
4093
4184
  const operatorContext = approvalReviewContext(journal.read(), t.id);
4185
+ let outsideScopeForced = false;
4094
4186
  gateLoop: while (true) {
4095
4187
  fatalStop.signal.throwIfAborted();
4096
4188
  executionSignal()?.throwIfAborted();
@@ -4119,13 +4211,42 @@ export async function runDaemon(repoRoot, opts = {}) {
4119
4211
  const own = journal.read().filter((e) => e.taskId === t.id);
4120
4212
  return own.slice(own.map((e) => e.event === "phase-start" && e.data.phase === "gates").lastIndexOf(true) + 1);
4121
4213
  })();
4122
- const freshTimeout = !recheck && !satisfiedGate && ((harvestAuthor !== undefined && owesTimeoutRefresh(t, lastRound, gateSubject.commit))
4123
- || (lastRound.some((e) => e.event === "gate-fresh-forced" && e.data.commit === gateSubject.commit)
4124
- && !lastRound.some((e) => e.event === "gate-result" && e.data.gate === "test")));
4214
+ // C: a passing selected-test screen is never a forced round's conclusive test result (the
4215
+ // journal's own replay rule): its full suite is still owed, fresh, under the round's bypass.
4216
+ const measured = lastRound.filter((e) => e.event === "gate-result" && !(e.data.gate === "test"
4217
+ && Array.isArray(e.data.selectedTests) && e.data.fullSuite !== true && e.data.pass !== false));
4218
+ const forcedUnmeasured = (outside) => lastRound.some((e) => e.event === "gate-fresh-forced"
4219
+ && e.data.commit === gateSubject.commit && (e.data.reason === OUTSIDE_SCOPE_RED) === outside)
4220
+ && !measured.some((e) => e.data.gate === "test");
4221
+ // C: a crash after the forced re-execution journaled its red but before its park still owes that
4222
+ // park — restored from the journaled subject and result, never re-measured, replayed or repaired.
4223
+ // Infra adjudication stays authoritative, exactly as it is ahead of the live park.
4224
+ if (!recheck && !satisfiedGate && lastRound.some((e) => e.event === "gate-fresh-forced"
4225
+ && e.data.reason === OUTSIDE_SCOPE_RED && e.data.commit === gateSubject.commit)
4226
+ && allOutsideScopeReds(t, lastRound, gateSubject.commit)) {
4227
+ const restored = lastRound.filter((e) => e.event === "gate-result" && e.data.gate === "test" && e.data.pass === false)
4228
+ .map((e) => ({ gate: "test", pass: false, details: String(e.data.details ?? ""), meta: e.data }));
4229
+ if (await adjudicateInfraShapedRed(t, restored, wt, gateSubject.commit, rs?.attempts ?? 0, true, (reason) => park(t, reason, "infra", gateAuthor, rs?.attempts ?? 0, startMs, gateFails, consults, tokens, metered, retryMode)))
4230
+ return;
4231
+ await park(t, outsideScopePark(t), "gate-fail", gateAuthor, rs?.attempts ?? 0, startMs, gateFails, consults, tokens, metered, retryMode);
4232
+ return;
4233
+ }
4234
+ // C: the same out-of-scope re-execution the live loop owes, and a crash inside it still owes it. It
4235
+ // takes precedence over a timeout refresh: a qualifying red keeps its own bypass and park.
4236
+ outsideScopeForced = !recheck && !satisfiedGate && ((harvestAuthor !== undefined && owesOutsideScopeRefresh(t, lastRound, gateSubject.commit)) || forcedUnmeasured(true));
4237
+ const freshTimeout = !recheck && !satisfiedGate && !outsideScopeForced && ((harvestAuthor !== undefined && owesTimeoutRefresh(t, lastRound, gateSubject.commit)) || forcedUnmeasured(false));
4238
+ // C: a measurement this forced round EXECUTES fresh — its forced test above all — is independent
4239
+ // evidence, never a replay measurement; only a gate the interrupted round already journaled is.
4240
+ if (outsideScopeForced) {
4241
+ gateSubject = { ...gateSubject, journaledGates: measured.map((e) => String(e.data.gate)) };
4242
+ }
4125
4243
  journal.phaseStart(t.id, "gates");
4126
4244
  if (freshTimeout) {
4127
4245
  journal.append("gate-fresh-forced", t.id, { gate: "test", attempt: gateSubject.attempt, commit: gateSubject.commit, reason: "no-commit-timeout-red", resumed: true });
4128
4246
  }
4247
+ if (outsideScopeForced) {
4248
+ journal.append("gate-fresh-forced", t.id, { gate: "test", attempt: gateSubject.attempt, commit: gateSubject.commit, reason: OUTSIDE_SCOPE_RED, resumed: true });
4249
+ }
4129
4250
  const { results } = await withCommandContext(t.id, async () => runReviewRecovery(resumedTask, {
4130
4251
  carriedAuthors: await subjectAuthors(journal.read(), t.id, wt, taskBase),
4131
4252
  producer: producerNow(),
@@ -4150,7 +4271,7 @@ export async function runDaemon(repoRoot, opts = {}) {
4150
4271
  // flaking seat is re-asked on every task.
4151
4272
  reviewNoVerdicts,
4152
4273
  recheck, // OBS-1055: a recheck discards cached reds — the battery re-measures what the operator questioned
4153
- cachedRedBypass: freshTimeout ? "timeout-fresh" : undefined,
4274
+ cachedRedBypass: freshTimeout ? "timeout-fresh" : outsideScopeForced ? "out-of-scope-fresh" : undefined,
4154
4275
  onGate: async (e) => {
4155
4276
  if (!liveEngagement())
4156
4277
  return;
@@ -4215,6 +4336,10 @@ export async function runDaemon(repoRoot, opts = {}) {
4215
4336
  // assignment back with it.
4216
4337
  if (await adjudicateInfraShapedRed(t, results, wt, gateSubject.commit, rs?.attempts ?? 0, false, (reason) => park(t, reason, "infra", gateAuthor, rs?.attempts ?? 0, startMs, gateFails, consults, tokens, metered, retryMode)))
4217
4338
  return;
4339
+ if (outsideScopeForced && freshOutsideScopeRed(t, gateSubject.commit)) {
4340
+ await park(t, outsideScopePark(t), "gate-fail", gateAuthor, rs?.attempts ?? 0, startMs, gateFails, consults, tokens, metered, retryMode);
4341
+ return;
4342
+ }
4218
4343
  const classified = journal.classifiedDispatch(t.id);
4219
4344
  if (await dispositionScopeRed(t, results, classified?.assignment ?? gateAuthor, classified ? (rs?.attempts ?? 0) : Math.max(0, (rs?.attempts ?? 0) - 1), startMs, gateFails, consults, tokens, metered, retryMode, await treeOrDiffPaths(taskBase, wt)))
4220
4345
  return;
@@ -6241,6 +6366,7 @@ export async function runDaemon(repoRoot, opts = {}) {
6241
6366
  };
6242
6367
  let results = [];
6243
6368
  let commits = [];
6369
+ let outsideScopeForced = false;
6244
6370
  gateLoop: while (true) {
6245
6371
  fatalStop.signal.throwIfAborted();
6246
6372
  executionSignal()?.throwIfAborted();
@@ -6256,12 +6382,18 @@ export async function runDaemon(repoRoot, opts = {}) {
6256
6382
  && e.data.attempt === attempt - 1);
6257
6383
  const previousRows = retryMode === "repair" ? lastRoundRows : [];
6258
6384
  // OBS-1106 residual: see owesTimeoutRefresh — the replay below and the verdict cache are both refused.
6259
- const freshTimeout = !explicitlyFundedAttempt && owesTimeoutRefresh(t, lastRoundRows, gateSubject.commit);
6385
+ // C: an unchanged red wholly outside files[] is re-executed once instead of copied (journal or cache);
6386
+ // it outranks the timeout refresh, so a timeout-shaped qualifying red keeps its own bypass and park.
6387
+ outsideScopeForced = !explicitlyFundedAttempt && owesOutsideScopeRefresh(t, lastRoundRows, gateSubject.commit);
6388
+ const freshTimeout = !explicitlyFundedAttempt && !outsideScopeForced && owesTimeoutRefresh(t, lastRoundRows, gateSubject.commit);
6260
6389
  journal.phaseStart(t.id, "gates");
6261
6390
  if (freshTimeout) {
6262
6391
  journal.append("gate-fresh-forced", t.id, { gate: "test", attempt, priorAttempt: attempt - 1, commit: gateSubject.commit, reason: "no-commit-timeout-red" });
6263
6392
  }
6264
- const replay = !explicitlyFundedAttempt && !freshTimeout && previousRows.length > 0
6393
+ if (outsideScopeForced) {
6394
+ journal.append("gate-fresh-forced", t.id, { gate: "test", attempt, priorAttempt: attempt - 1, commit: gateSubject.commit, reason: OUTSIDE_SCOPE_RED });
6395
+ }
6396
+ const replay = !explicitlyFundedAttempt && !freshTimeout && !outsideScopeForced && previousRows.length > 0
6265
6397
  && previousRows.every((e) => e.data.commit === gateSubject.commit)
6266
6398
  && previousRows.some((e) => e.data.pass === false && e.data.skipped !== true);
6267
6399
  if (replay) {
@@ -6294,7 +6426,7 @@ export async function runDaemon(repoRoot, opts = {}) {
6294
6426
  commands, baseline, channels: pools.review, judgeChannels: pools.judge, health, adapters, cfg, artifactDir: journal.dir,
6295
6427
  collateral: collateral.get(t.id) ?? [],
6296
6428
  selectTests: !testGateFailed,
6297
- cachedRedBypass: explicitlyFundedAttempt ? "operator-rerun" : freshTimeout ? "timeout-fresh" : undefined,
6429
+ cachedRedBypass: explicitlyFundedAttempt ? "operator-rerun" : freshTimeout ? "timeout-fresh" : outsideScopeForced ? "out-of-scope-fresh" : undefined,
6298
6430
  via: cfg.visibility.llm === "pane"
6299
6431
  ? {
6300
6432
  driver: trackedDriver,
@@ -6403,6 +6535,10 @@ export async function runDaemon(repoRoot, opts = {}) {
6403
6535
  }
6404
6536
  if (await adjudicateInfraShapedRed(t, results, wt, gateSubject.commit, attempt, gateSubject.replayedFromAttempt !== undefined, (reason) => park(t, reason, "infra", assignment, attempt + 1, startMs, gateFails, consults, tokens, metered, retryMode)))
6405
6537
  return;
6538
+ if (outsideScopeForced && freshOutsideScopeRed(t, gateSubject.commit)) {
6539
+ await park(t, outsideScopePark(t), "gate-fail", assignment, attempt + 1, startMs, gateFails, consults, tokens, metered, retryMode);
6540
+ return;
6541
+ }
6406
6542
  // OBS-547: who pays for this red is decided by the run's collateral prediction (see
6407
6543
  // dispositionScopeRed) — an authoring defect parks unchargeable before any accounting below.
6408
6544
  if (await dispositionScopeRed(t, results, assignment, attempt, startMs, gateFails, consults, tokens, metered, retryMode, await treeOrDiffPaths(taskBase, wt)))
package/dist/run/git.js CHANGED
@@ -9,6 +9,7 @@ import { StringDecoder } from "node:string_decoder";
9
9
  import { shq } from "../adapters/types.js";
10
10
  import { tickmarkrDir } from "../graph/graph.js";
11
11
  import { ROUTING_ENV_SEAMS } from "../route/router.js";
12
+ import { titleEnvironment } from "./title-environment.js";
12
13
  export { ROUTING_ENV_SEAMS };
13
14
  // OBS-110: gate/baseline/tip-verify children are vitest suites; without a fork cap, concurrent
14
15
  // full-suite gate runs fork a worker-per-core pool per worktree and saturate the operator box.
@@ -324,6 +325,14 @@ function executeShell(cmd, cwd, timeoutMs, login, options, observation) {
324
325
  // OBS-854: descendants can leave the checkout (nested fixture suites do), so cwd alone cannot
325
326
  // attribute them. Every daemon shell exports the daemon pid as their durable parentage marker.
326
327
  env[SUITE_PARENT_ENV] = String(process.pid);
328
+ // A2: on Darwin a Node child's title writes stay in JavaScript (title-environment.ts); runnerInputsHash
329
+ // hashes this same effective NODE_OPTIONS, so a verdict is only reused under the identical mode. Native
330
+ // mode is applied too: it drops a preload a nested shell inherited from its parent (unset when that was all).
331
+ const { nodeOptions } = titleEnvironment(env);
332
+ if (nodeOptions === undefined)
333
+ delete env.NODE_OPTIONS;
334
+ else
335
+ env.NODE_OPTIONS = nodeOptions;
327
336
  // T7: the capacity every result of this shell carries, read HERE — off the environment the child
328
337
  // is about to receive, after the precedence above has settled. An operator export is already in
329
338
  // `env`, so what gets recorded is the operator's number, which is the case a release was re-taken
@@ -435,8 +435,8 @@ export declare const TelemetryRowSchema: z.ZodObject<{
435
435
  quotaFailover: z.ZodOptional<z.ZodLiteral<true>>;
436
436
  overrun: z.ZodOptional<z.ZodLiteral<true>>;
437
437
  retryMode: z.ZodOptional<z.ZodEnum<{
438
- repair: "repair";
439
438
  resume: "resume";
439
+ repair: "repair";
440
440
  fresh: "fresh";
441
441
  }>>;
442
442
  signalQuality: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<0>, z.ZodLiteral<0.25>, z.ZodLiteral<0.5>, z.ZodLiteral<0.75>, z.ZodLiteral<1>]>>;
@@ -593,4 +593,104 @@ export declare class Journal {
593
593
  readTelemetry(): TelemetryRow[];
594
594
  readJudgeTelemetry(): TelemetryRow[];
595
595
  }
596
+ export declare const OWED_CHECK_VERSION: 1;
597
+ export declare const OWED_DISCHARGE_EVENT: "owed-check-discharged";
598
+ export interface OwedCheck {
599
+ version: typeof OWED_CHECK_VERSION;
600
+ id: string;
601
+ runId: string;
602
+ taskId: string;
603
+ gate: GateName;
604
+ /** The immutable range the waived gate measured, and its canonical subject (daemon gateCommitSubject). */
605
+ base: string;
606
+ head: string;
607
+ subject: string;
608
+ /** Verbatim (whitespace-sensitive) patch identity of the whole range and of each commit, in order — the integration mapping key. */
609
+ patch: string;
610
+ patches: string[];
611
+ criteria: string;
612
+ /** The task's file scope at waive time: a discharge gates exactly this allowlist, never a wider one. */
613
+ files: string[];
614
+ authors: string[];
615
+ /** The channels this repository's adapters declared under its config at waive time (key → vendor):
616
+ * the only seats a review discharge's reviewer and author channels may resolve to. */
617
+ declared: ChannelFact[];
618
+ cause: string;
619
+ evidence: string;
620
+ disposition: "accepted-risk";
621
+ known: boolean;
622
+ unknownReason?: string;
623
+ }
624
+ export interface OwedFold {
625
+ known: boolean;
626
+ debt: number | "unknown";
627
+ outstanding: OwedCheck[];
628
+ acceptedRisk: OwedCheck[];
629
+ discharged: string[];
630
+ unknown: Array<{
631
+ taskId?: string;
632
+ gate?: string;
633
+ reason: string;
634
+ }>;
635
+ }
636
+ export declare const owedCriteria: (acceptance: unknown) => string;
637
+ /** The daemon's canonical gate subject for base..head (daemon.ts gateCommitSubject), byte for byte. */
638
+ export declare const owedSubject: (cwd: string, base: string, head: string) => string;
639
+ /** Ordered verbatim patch ids of the commits in base..head; an empty commit authored no patch. */
640
+ export declare const rangePatches: (cwd: string, base: string, head: string) => string[];
641
+ /** Verbatim patch identity of the whole change base..head introduces. */
642
+ export declare const rangePatch: (cwd: string, base: string, head: string) => string;
643
+ /**
644
+ * `head` is the task's own recorded integration merge of the obligation's range — exactly. Its first
645
+ * parent is `mergeBase`; its second parent replays the waived history source tree for source tree from
646
+ * a base with the waived base's tree (a carry may mint new commit objects, never new content — and
647
+ * patch ids alone ignore where a hunk lands); its tree is git's own clean merge of the two (no
648
+ * resolution, no amend); and it introduced the same ordered verbatim patch series and total patch.
649
+ * Squashed, reordered, relocated, re-indented, reverted or conflict-resolved history fails one of
650
+ * these and is refused as ambiguous; containment never counts.
651
+ * Both verify (before discharging) and the fold (re-reading every discharge) decide through here.
652
+ */
653
+ export declare function integrationMapped(cwd: string, events: readonly JournalEvent[], o: OwedCheck, mergeBase: unknown, head: unknown, memo?: OwedProofMemo): boolean;
654
+ /** I2: a caller-scoped set of completed integration re-proofs (see integrationMapped); absent, every fold re-proves. */
655
+ export type OwedProofMemo = Set<string>;
656
+ /**
657
+ * Lifetime patch authors of base..head, read from the run: the daemon's subjectAuthors fold
658
+ * (daemon.ts, not exported and not this task's to edit) restated synchronously so the waive row is
659
+ * written without yielding. Recreation rows name SOURCE hashes, so ownership joins by stable patch
660
+ * identity across changed commit objects; a gate-only restore (resume-restore's seat, a recreation
661
+ * after work started) never becomes an author. Unreadable history names an unknown author.
662
+ */
663
+ export declare function owedAuthors(events: readonly JournalEvent[], taskId: string, cwd: string, base: string, head: string): string[];
664
+ /**
665
+ * The obligation a waive of `gate` leaves on the task branch's current range. Synchronous on purpose:
666
+ * approve writes it into the waive row under the approval serialization without yielding, so a live
667
+ * daemon's boundary sees the same single append it always did. Anything not bound exactly — the
668
+ * waived row measured another subject, unreadable refs, unresolved authors, unreadable criteria — is
669
+ * recorded, never guessed, as known: false.
670
+ */
671
+ export declare function captureOwedCheck(input: {
672
+ cwd: string;
673
+ runId: string;
674
+ taskId: string;
675
+ gate: GateName;
676
+ cause: string;
677
+ acceptance: unknown;
678
+ files: unknown;
679
+ declared: readonly ChannelFact[] | undefined;
680
+ taskRef: string;
681
+ integrationRef: string;
682
+ events: readonly JournalEvent[];
683
+ }): OwedCheck;
684
+ export type ChannelFact = {
685
+ key: string;
686
+ vendor: string;
687
+ };
688
+ /**
689
+ * The CURRENT owed-check fold: accepted-risk history, outstanding debt, and whether it is known.
690
+ * `cwd` is any checkout of the run's repository (integration discharges are re-proved from git).
691
+ * It never throws: a fold that cannot be computed is unknown debt, not a crash and never zero.
692
+ * `memo` (I2, optional): a store-scoped table reusing the immutable git re-proof of an integration mapping
693
+ * across folds; every artifact, hash, criteria and reviewer condition is still re-read on every fold.
694
+ */
695
+ export declare function foldOwedChecks(events: readonly JournalEvent[], cwd: string, memo?: OwedProofMemo): OwedFold;
596
696
  export {};