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.
- package/README.md +10 -2
- package/dist/adapters/catalog-remote.d.ts +23 -0
- package/dist/adapters/catalog-remote.js +38 -0
- package/dist/adapters/claude-code.d.ts +1 -1
- package/dist/adapters/claude-code.js +4 -1
- package/dist/adapters/codex-commit-check.d.ts +25 -0
- package/dist/adapters/codex-commit-check.js +108 -0
- package/dist/adapters/codex.d.ts +2 -1
- package/dist/adapters/codex.js +26 -5
- package/dist/adapters/model-discovery-check.d.ts +33 -0
- package/dist/adapters/model-discovery-check.js +123 -0
- package/dist/adapters/model-lints.d.ts +2 -1
- package/dist/adapters/model-lints.js +52 -1
- package/dist/adapters/model-windows.d.ts +2 -1
- package/dist/adapters/model-windows.js +18 -7
- package/dist/adapters/prompt.js +3 -2
- package/dist/adapters/types.d.ts +13 -0
- package/dist/cli/commands/approve.js +37 -6
- package/dist/cli/commands/doctor.d.ts +4 -0
- package/dist/cli/commands/doctor.js +20 -3
- package/dist/cli/commands/fleet.d.ts +10 -1
- package/dist/cli/commands/fleet.js +279 -95
- package/dist/cli/commands/init.js +10 -4
- package/dist/cli/commands/plan.js +65 -4
- package/dist/cli/commands/report.d.ts +39 -2
- package/dist/cli/commands/report.js +227 -10
- package/dist/cli/commands/resume.js +2 -4
- package/dist/cli/commands/run.d.ts +11 -0
- package/dist/cli/commands/run.js +33 -5
- package/dist/cli/commands/status.js +30 -3
- package/dist/cli/commands/verify.d.ts +1 -1
- package/dist/cli/commands/verify.js +356 -123
- package/dist/compile/native.js +130 -0
- package/dist/config/config.d.ts +25 -7
- package/dist/config/config.js +92 -11
- package/dist/config/fleet-overlay.d.ts +2 -1
- package/dist/config/fleet-overlay.js +77 -4
- package/dist/drivers/orca.d.ts +19 -1
- package/dist/drivers/orca.js +161 -31
- package/dist/gates/cache.d.ts +4 -0
- package/dist/gates/cache.js +10 -2
- package/dist/gates/llm.d.ts +4 -1
- package/dist/gates/llm.js +6 -1
- package/dist/gates/review.js +8 -3
- package/dist/gates/run-gates.d.ts +3 -2
- package/dist/gates/run-gates.js +91 -50
- package/dist/route/router.d.ts +11 -0
- package/dist/route/router.js +29 -8
- package/dist/run/daemon.js +158 -22
- package/dist/run/git.js +9 -0
- package/dist/run/journal.d.ts +101 -1
- package/dist/run/journal.js +352 -2
- package/dist/run/launchservices-check.d.ts +56 -0
- package/dist/run/launchservices-check.js +117 -0
- package/dist/run/operator-state.d.ts +22 -2
- package/dist/run/operator-state.js +20 -3
- package/dist/run/title-environment.d.ts +45 -0
- package/dist/run/title-environment.js +127 -0
- package/dist/run/title-preload.cjs +38 -0
- package/dist/run/title-preload.d.cts +1 -0
- package/dist/tui/cockpit/board.js +12 -6
- package/dist/tui/cockpit/keys.d.ts +8 -0
- package/dist/tui/cockpit/keys.js +4 -1
- package/dist/tui/cockpit/layout.d.ts +2 -2
- package/dist/tui/cockpit/layout.js +2 -2
- package/dist/tui/cockpit/live-runtime.js +46 -20
- package/dist/tui/cockpit/live-store.d.ts +30 -1
- package/dist/tui/cockpit/live-store.js +134 -18
- package/dist/tui/cockpit/log-view.d.ts +28 -0
- package/dist/tui/cockpit/log-view.js +60 -0
- package/dist/tui/cockpit/run-view.js +4 -0
- package/dist/tui/cockpit/shell.d.ts +2 -1
- package/dist/tui/cockpit/shell.js +6 -4
- package/dist/tui/ink/fleet-app.d.ts +12 -6
- package/dist/tui/ink/fleet-app.js +17 -11
- package/package.json +1 -1
- package/skills/tickmarkr-auto/SKILL.md +33 -3
- package/skills/tickmarkr-loop/SKILL.md +55 -5
- package/skills/tickmarkr-overseer/SKILL.md +83 -8
package/dist/route/router.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { channelKey, channelsFromConfig } from "../adapters/types.js";
|
|
2
2
|
import { TIER_RANK } from "../config/config.js";
|
|
3
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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) => !
|
|
430
|
+
return channels.filter((c) => !excluded(c)
|
|
410
431
|
&& (!excludedProvider || routingModelProvider(c.model, c.vendor) !== excludedProvider)
|
|
411
432
|
&& tierPredicate(c.tier));
|
|
412
433
|
}
|
package/dist/run/daemon.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
3302
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3344
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
|
3532
|
-
// row held behind it
|
|
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
|
-
|
|
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
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/dist/run/journal.d.ts
CHANGED
|
@@ -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 {};
|