@tokenfactory/acc-runner 0.47.12 → 0.47.13

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.
@@ -45,7 +45,7 @@ import { acquireTaskLock as defaultAcquireTaskLock, clearLocalTaskLock as defaul
45
45
  // RUNNER-HOOK-DIR-AND-LOCK-JANITOR-1: remove this task's commit-guard hooks dir
46
46
  // on every exit path (the janitor is the hourly backstop for leaks a crash left).
47
47
  import { removeTaskHookDir as defaultRemoveTaskHookDir } from "./runtime/janitor.js";
48
- import { prepareTaskWorktree as defaultPrepareTaskWorktree, WorktreeBranchTakenError, } from "./runtime/worktree.js";
48
+ import { prepareTaskWorktree as defaultPrepareTaskWorktree, WorktreeBranchTakenError, findDanglingWarmCloneLinks, seedInstallSafeNodeModules, } from "./runtime/worktree.js";
49
49
  // v0.21 T-66-1: serialize the per-task worktree-provisioning critical section
50
50
  // against the shared clone so concurrent tasks can't collide on git's
51
51
  // repo-global locks (the T-65-2 race).
@@ -58,7 +58,7 @@ import { clonePathForRepo, ensureRepoClone, CloneFailedError, } from "./runtime/
58
58
  import { parseConflictMeta, resolveConflict, MAX_CONFLICT_ATTEMPTS, } from "./runtime/conflict-resolver.js";
59
59
  import { MAX_REWORK_CYCLES, parseReworkMeta, renderReworkPrompt, reconcileReworkBranch as defaultReconcileReworkBranch, fetchPrHeadRef as defaultFetchPrHeadRef, } from "./runtime/rework.js";
60
60
  import { assertResolutionIntegrity, isConflictResolutionTask, summariseFailures, } from "./runtime/resolution-integrity.js";
61
- import { depsMissingGateResult, depsStaleGateResult, gateFailureBlockReason, gateRunnerCrashResult, runPreprGate as defaultRunPreprGate, DEPS_MISSING_CLASSIFICATION, DEPS_STALE_CLASSIFICATION, GATE_TOOL_CRASHED_CLASSIFICATION, PREPR_GATE_FAILED_CLASSIFICATION, PREPR_GATES, } from "./prepr-gate.js";
61
+ import { depsMissingGateResult, depsStaleGateResult, gateFailureBlockReason, gateRunnerCrashResult, runPreprGate as defaultRunPreprGate, classifyDanglingParentDeps, DEPS_MISSING_CLASSIFICATION, DEPS_STALE_CLASSIFICATION, GATE_TOOL_CRASHED_CLASSIFICATION, PREPR_GATE_FAILED_CLASSIFICATION, PREPR_GATES, } from "./prepr-gate.js";
62
62
  import { revalidateMigrationSeq as defaultRevalidateMigrationSeq, } from "./migration-seq-guard.js";
63
63
  import { readAppliedMigrationSeqs } from "./applied-seq-roster.js";
64
64
  import { commitGuardSkipRequested, gitConfigHooksEnv, installCommitGuardSync, } from "./commit-guard.js";
@@ -364,10 +364,13 @@ export async function ensureWorktreeNodeModules(workdir, repoPath, opts = {}) {
364
364
  try {
365
365
  if (!(await worktreePathExists(src)))
366
366
  continue; // parent has no deps here
367
- if (await worktreePathExists(dst))
367
+ // A resumed worktree seeded by an older runner holds ONE link to `src`: replace it.
368
+ const prior = await fs.promises.lstat(dst).catch(() => null);
369
+ if (prior && !prior.isSymbolicLink())
368
370
  continue; // already present (resume/prior seed)
369
- await fs.promises.mkdir(path.dirname(dst), { recursive: true });
370
- await fs.promises.symlink(src, dst, "dir");
371
+ if (prior)
372
+ await fs.promises.unlink(dst);
373
+ await seedInstallSafeNodeModules(src, dst);
371
374
  linked.push(rel === "." ? "node_modules" : `${rel}/node_modules`);
372
375
  }
373
376
  catch (err) {
@@ -378,7 +381,7 @@ export async function ensureWorktreeNodeModules(workdir, repoPath, opts = {}) {
378
381
  linked,
379
382
  installed: false,
380
383
  detail: linked.length
381
- ? `seeded ${linked.length} node_modules symlink(s) from warm clone`
384
+ ? `seeded ${linked.length} install-safe node_modules dir(s) from warm clone`
382
385
  : "worktree already provisioned; nothing to seed",
383
386
  depsMissing: false,
384
387
  };
@@ -429,7 +432,7 @@ export async function verifyWorktreeDepsFreshness(workdir, repoPath, opts = {})
429
432
  `node_modules were symlinked from`;
430
433
  const install = opts.install ?? defaultWorktreeInstall;
431
434
  try {
432
- await install(workdir);
435
+ await install(workdir); // writes the worktree only: the seed is a real dir (LANE-WARM-CLONE-…-1)
433
436
  return {
434
437
  fresh: true, lockfileHash, parentLockfileHash, installed: true, depsMissing: false,
435
438
  detail: `${drift} — reinstalled in worktree`,
@@ -1981,6 +1984,15 @@ export function runTask(taskId, deps) {
1981
1984
  * with block_reason `gate_tool_crashed:<gate>`, which is still fail-CLOSED
1982
1985
  * because no PR is opened off a check that never ran.
1983
1986
  */
1987
+ // LANE-WARM-CLONE-NODE-MODULES-REWRITE-1 (AC3): a module-resolution gate
1988
+ // failure is a LANE fault (not a verdict on the diff) when the warm parent
1989
+ // clone's node_modules links are dangling. Null → keep the code verdict.
1990
+ const reclassifyDanglingParentDeps = async (fail) => {
1991
+ if (fail.gate === "deps" || fail.gate === "deps-stale")
1992
+ return null;
1993
+ const dangling = await findDanglingWarmCloneLinks(repoPath).catch(() => []);
1994
+ return classifyDanglingParentDeps(fail, repoPath, dangling, deps.session.runnerId);
1995
+ };
1984
1996
  const handleGateToolCrash = async (result, crash, rework) => {
1985
1997
  // Advisory probe: a GitHub hiccup must never be what decides this, so an
1986
1998
  // unreadable probe degrades to "no PR" — and that route is `blocked`,
@@ -3426,12 +3438,18 @@ export function runTask(taskId, deps) {
3426
3438
  // WORKTREE-STALE-DEPS-1: `deps-stale` is the sibling environment
3427
3439
  // fault (deps WERE seeded, the merge above left them stale) — kept
3428
3440
  // out of the generic `rework_gate_failed` bucket for the same reason.
3429
- const blockReason = gateFailureBlockReason(fail.gate);
3430
- const errorClass = fail.gate === "deps"
3431
- ? DEPS_MISSING_CLASSIFICATION
3432
- : fail.gate === "deps-stale"
3433
- ? DEPS_STALE_CLASSIFICATION
3434
- : "rework_gate_failed";
3441
+ // LANE-WARM-CLONE-NODE-MODULES-REWRITE-1 (AC3): a dangling warm clone
3442
+ // is a lane fault here too — out of the `rework_gate_failed` bucket.
3443
+ const dangling = await reclassifyDanglingParentDeps(fail);
3444
+ const blockReason = dangling ? dangling.blockReason : gateFailureBlockReason(fail.gate);
3445
+ const errorClass = dangling
3446
+ ? dangling.errorClass
3447
+ : fail.gate === "deps"
3448
+ ? DEPS_MISSING_CLASSIFICATION
3449
+ : fail.gate === "deps-stale"
3450
+ ? DEPS_STALE_CLASSIFICATION
3451
+ : "rework_gate_failed";
3452
+ const stderrTail = dangling ? dangling.notice : fail.outputTail;
3435
3453
  await appendEvent(deps.supabase, taskId, "rework_halt", {
3436
3454
  phase: "prepr_gate",
3437
3455
  pr_number: reworkMeta.pr_number,
@@ -3446,7 +3464,7 @@ export function runTask(taskId, deps) {
3446
3464
  // it TIMED OUT, so a timeout halt is distinguishable from a genuine
3447
3465
  // assertion halt without hand-querying stderr_tail.
3448
3466
  ...gateFailureRecordFields(fail),
3449
- stderr_tail: fail.outputTail,
3467
+ stderr_tail: stderrTail,
3450
3468
  runner_id: deps.session.runnerId,
3451
3469
  });
3452
3470
  await emitGateTimeoutBreadcrumb(fail);
@@ -3459,7 +3477,7 @@ export function runTask(taskId, deps) {
3459
3477
  error_class: errorClass,
3460
3478
  block_reason: blockReason,
3461
3479
  failed_gate: fail.gate,
3462
- stderr_tail: fail.outputTail,
3480
+ stderr_tail: stderrTail,
3463
3481
  });
3464
3482
  await transitionBlocked(blockReason, {
3465
3483
  phase: "prepr_gate",
@@ -3603,18 +3621,27 @@ export function runTask(taskId, deps) {
3603
3621
  // a rework merge just left them stale — kept as its own class end-to-end
3604
3622
  // so triage never conflates "no environment" with "stale environment".
3605
3623
  const depsStale = fail.gate === "deps-stale";
3606
- const envFault = depsMissing || depsStale;
3607
- const blockReason = gateFailureBlockReason(fail.gate);
3608
- const errorClass = depsMissing
3609
- ? DEPS_MISSING_CLASSIFICATION
3610
- : depsStale
3611
- ? DEPS_STALE_CLASSIFICATION
3612
- : PREPR_GATE_FAILED_CLASSIFICATION;
3613
- const summary = depsMissing
3614
- ? `pre-PR gate could not run: worktree has no node_modules (${blockReason}) — PR not opened`
3615
- : depsStale
3616
- ? `pre-PR gate could not run: worktree deps are stale vs the merged lockfile (${blockReason}) — PR not opened`
3617
- : `pre-PR ${fail.gate} gate failed (${blockReason}, exit ${fail.exitCode ?? "signal"}) — PR not opened`;
3624
+ // LANE-WARM-CLONE-NODE-MODULES-REWRITE-1 (AC3): a module-resolution
3625
+ // failure over a DANGLING warm clone is a lane fault, parked (not a code
3626
+ // reject) so the sweep requeues it losslessly.
3627
+ const dangling = await reclassifyDanglingParentDeps(fail);
3628
+ const envFault = depsMissing || depsStale || dangling !== null;
3629
+ const blockReason = dangling ? dangling.blockReason : gateFailureBlockReason(fail.gate);
3630
+ const errorClass = dangling
3631
+ ? dangling.errorClass
3632
+ : depsMissing
3633
+ ? DEPS_MISSING_CLASSIFICATION
3634
+ : depsStale
3635
+ ? DEPS_STALE_CLASSIFICATION
3636
+ : PREPR_GATE_FAILED_CLASSIFICATION;
3637
+ const stderrTail = dangling ? dangling.notice : fail.outputTail;
3638
+ const summary = dangling
3639
+ ? `pre-PR ${fail.gate} gate could not run: the warm clone's node_modules dangle (${blockReason}) — lane fault, PR not opened`
3640
+ : depsMissing
3641
+ ? `pre-PR gate could not run: worktree has no node_modules (${blockReason}) — PR not opened`
3642
+ : depsStale
3643
+ ? `pre-PR gate could not run: worktree deps are stale vs the merged lockfile (${blockReason}) — PR not opened`
3644
+ : `pre-PR ${fail.gate} gate failed (${blockReason}, exit ${fail.exitCode ?? "signal"}) — PR not opened`;
3618
3645
  await appendEvent(deps.supabase, taskId, "error", {
3619
3646
  phase: "prepr_gate",
3620
3647
  error_class: errorClass,
@@ -3626,7 +3653,7 @@ export function runTask(taskId, deps) {
3626
3653
  // PREPR-GATE-TIMEOUT-VISIBILITY-1: same structured timeout/assertion
3627
3654
  // fingerprint as the rework_halt path above.
3628
3655
  ...gateFailureRecordFields(fail),
3629
- stderr_tail: fail.outputTail,
3656
+ stderr_tail: stderrTail,
3630
3657
  runner_id: deps.session.runnerId,
3631
3658
  });
3632
3659
  // RUNNER-TEST-HANG-SLOT-LEAK-1: a wall-clock test-gate timeout gets its
@@ -3641,7 +3668,7 @@ export function runTask(taskId, deps) {
3641
3668
  error_class: errorClass,
3642
3669
  block_reason: blockReason,
3643
3670
  failed_gate: fail.gate,
3644
- stderr_tail: fail.outputTail,
3671
+ stderr_tail: stderrTail,
3645
3672
  });
3646
3673
  if (envFault) {
3647
3674
  // Park, never fail — same reasoning as a crashed gate tool