tickmarkr 2.7.2 → 2.7.3
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/dist/run/daemon.d.ts +2 -0
- package/dist/run/daemon.js +6 -4
- package/dist/run/stall.js +20 -18
- package/package.json +1 -1
package/dist/run/daemon.d.ts
CHANGED
|
@@ -263,6 +263,8 @@ export declare function verifyIntegrationTipCached(intWt: string, commands: Reco
|
|
|
263
263
|
signal?: AbortSignal;
|
|
264
264
|
evidence?: GateEvidenceOptions;
|
|
265
265
|
}): Promise<boolean>;
|
|
266
|
+
type WorkerProcessTree = "empty" | "running" | "unmeasurable";
|
|
267
|
+
export declare function observeWorkerProcessTree(marker: string, cwd: string): Promise<WorkerProcessTree>;
|
|
266
268
|
type SuitePidProbe = (pid: number) => number | undefined;
|
|
267
269
|
/** Count full-suite roots in one process-table snapshot. The probes are arguments so the ownership
|
|
268
270
|
* rules remain testable on hosts that forbid process inspection; production supplies cwd and the
|
package/dist/run/daemon.js
CHANGED
|
@@ -34,7 +34,7 @@ import { executionSignal, remainingExecutionMs, withExecutionBudget, withoutExec
|
|
|
34
34
|
import { repairSelectionDecision, repairSelectionEnabled } from "./repair-selection.js";
|
|
35
35
|
import { failureDisposition, reserveInfrastructureRetry } from "./recovery.js";
|
|
36
36
|
import { runEnvironment } from "./environment.js";
|
|
37
|
-
import { addDetachedWorktree, changeRepresented, cleanupRunWorktrees, deriveForkCap, FORK_CAP_ENV, gitHead, linkNodeModules, npmDependencyInstallCommand, npmDependencyManifestChanged, PRESERVE_COMMIT_SUBJECT, PRESERVE_PRODUCER_TRAILER, preserveWorktree, producerFields, removeWorktree, resolvedCapacity, runWithForkBudget, runWithVerificationBudget, sameCapacity, sameVerification, sh, shGit, SUITE_PARENT_ENV, verificationProtocol, WORKTREE_LAYOUT_CONTRACT, WORKTREES_DIR, worktreePath } from "./git.js";
|
|
37
|
+
import { addDetachedWorktree, changeRepresented, cleanupRunWorktrees, deriveForkCap, FORK_CAP_ENV, gitHead, linkNodeModules, npmDependencyInstallCommand, npmDependencyManifestChanged, PRESERVE_COMMIT_SUBJECT, PRESERVE_PRODUCER_TRAILER, preserveWorktree, producerFields, removeWorktree, resolvedCapacity, runWithForkBudget, runWithVerificationBudget, sameCapacity, sameVerification, sh, shell, shGit, SUITE_PARENT_ENV, verificationProtocol, WORKTREE_LAYOUT_CONTRACT, WORKTREES_DIR, worktreePath } from "./git.js";
|
|
38
38
|
import { MASK } from "./redact.js";
|
|
39
39
|
import { runInteractiveSeed } from "./interactive-seed.js";
|
|
40
40
|
import { classifyRepairDisposition, resolveScopeHints } from "./repair-disposition.js";
|
|
@@ -1237,8 +1237,9 @@ async function gateCommitSubject(base, head, wt) {
|
|
|
1237
1237
|
// OBS-737 needs. A readable process table with no marker root is measured empty; a failed or empty
|
|
1238
1238
|
// table is unmeasurable. Descendants are closed over PPID because not every child retains the
|
|
1239
1239
|
// dispatch-script marker in its own argv.
|
|
1240
|
-
async function observeWorkerProcessTree(marker, cwd) {
|
|
1241
|
-
|
|
1240
|
+
export async function observeWorkerProcessTree(marker, cwd) {
|
|
1241
|
+
// queue row 113: `ps` is not git, so the payload entry — the own-git entry's pin probes are two SYNC git spawns per call
|
|
1242
|
+
const snapshot = await shell("ps -Awwo pid=,ppid=,command=", cwd, 15_000);
|
|
1242
1243
|
if (snapshot.code !== 0)
|
|
1243
1244
|
return "unmeasurable";
|
|
1244
1245
|
const rows = [];
|
|
@@ -1327,7 +1328,8 @@ export const resetLiveSuiteCountForTests = () => { liveSuiteCountForTests = unde
|
|
|
1327
1328
|
export async function liveSuiteCount(repoRoot) {
|
|
1328
1329
|
if (liveSuiteCountForTests)
|
|
1329
1330
|
return liveSuiteCountForTests(repoRoot);
|
|
1330
|
-
|
|
1331
|
+
// queue row 113: `ps` is not git, so the payload entry — the own-git entry's pin probes are two SYNC git spawns per call
|
|
1332
|
+
const snapshot = await shell("ps -Aww -o pid=,ppid=,state=,command=", repoRoot, 15_000);
|
|
1331
1333
|
if (snapshot.code !== 0)
|
|
1332
1334
|
return 0;
|
|
1333
1335
|
// row 103: one batched async probe per snapshot — a synchronous lsof/ps per pid held this loop 44–46 s
|
package/dist/run/stall.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
3
|
import { isAbsolute, relative, sep } from "node:path";
|
|
4
|
-
import { activeShellIdentities, processIdentity, resolveActiveShellIdentities,
|
|
4
|
+
import { activeShellIdentities, processIdentity, resolveActiveShellIdentities, shell, SUITE_PARENT_ENV } from "./git.js";
|
|
5
5
|
// OBS-82: normalize known presentation tokens before measuring transcript extent or filtering an
|
|
6
6
|
// LLM-bound transcript. This remains a closed allowlist — ANSI/VT escapes, braille-range spinner
|
|
7
7
|
// glyphs, and elapsed-time tokens bound to time-unit suffixes. Every other byte passes through
|
|
@@ -71,7 +71,8 @@ export async function reapOwnedProcessGroup(group, cwd, ownership) {
|
|
|
71
71
|
return reapWorkerProcesses(group, cwd, ownership);
|
|
72
72
|
if (group === undefined)
|
|
73
73
|
return null;
|
|
74
|
-
|
|
74
|
+
// queue row 113: `ps` is not git, so the payload entry — the own-git entry's pin probes are two SYNC git spawns per call
|
|
75
|
+
const own = await shell(`ps -o pgid= -p ${process.pid}`, cwd, 5_000);
|
|
75
76
|
// An in-process driver fixture (or a non-isolating host) can share the daemon's
|
|
76
77
|
// group. It is not an owned worker group: let the driver's close retire that slot.
|
|
77
78
|
if (own.code !== 0 || !/^\d+$/.test(own.stdout.trim()) || Number(own.stdout.trim()) === group)
|
|
@@ -87,7 +88,7 @@ export async function reapOwnedProcessGroup(group, cwd, ownership) {
|
|
|
87
88
|
// SIGKILL delivery and process retirement are asynchronous. Bound the confirmation
|
|
88
89
|
// grace, retaining names if a process is still present after it.
|
|
89
90
|
for (let probe = 0; probe < 5; probe++) {
|
|
90
|
-
const snapshot = await
|
|
91
|
+
const snapshot = await shell("ps -Awwo pid=,pgid=,stat=", cwd, 5_000);
|
|
91
92
|
if (snapshot.code !== 0)
|
|
92
93
|
return null;
|
|
93
94
|
survivors = snapshot.stdout.split("\n").flatMap((line) => {
|
|
@@ -377,9 +378,9 @@ let linuxClockTickMs;
|
|
|
377
378
|
function linuxProcessCpuMs(pid, cwd) {
|
|
378
379
|
if (!existsSync("/proc/self/stat"))
|
|
379
380
|
return Promise.resolve(undefined);
|
|
380
|
-
//
|
|
381
|
-
// profile (nvm/pyenv/direnv side effects included)
|
|
382
|
-
linuxClockTickMs ??=
|
|
381
|
+
// shell (login false), not sh: the accountant samples at 100ms cadence and must not run the operator's login
|
|
382
|
+
// profile (nvm/pyenv/direnv side effects included). Not shGit either: getconf is not git (queue row 113).
|
|
383
|
+
linuxClockTickMs ??= shell("getconf CLK_TCK", cwd, 15_000).then((r) => {
|
|
383
384
|
const ticks = r.code === 0 ? Number(r.stdout.trim()) : Number.NaN;
|
|
384
385
|
return Number.isFinite(ticks) && ticks > 0 ? 1_000 / ticks : undefined;
|
|
385
386
|
});
|
|
@@ -403,12 +404,21 @@ function linuxProcessCpuMs(pid, cwd) {
|
|
|
403
404
|
// finds that root and its descendants. An empty tree is measurable zero; a failed or unparseable
|
|
404
405
|
// snapshot is undefined because missing evidence can never prove inactivity.
|
|
405
406
|
async function workerTreeCpuSnapshot(marker, cwd, group, identities) {
|
|
406
|
-
|
|
407
|
+
// queue row 113: `ps` is not git, so the payload entry — the own-git entry's pin probes are two SYNC git spawns per call
|
|
408
|
+
// ONE snapshot carries the group column too (it was a second `ps -Awwo pid=,pgid=` per sample).
|
|
409
|
+
const snapshot = await shell("ps -Awwo pid=,ppid=,pgid=,time=,command=", cwd, 15_000);
|
|
407
410
|
if (snapshot.code !== 0)
|
|
408
411
|
return undefined;
|
|
409
412
|
const rows = [];
|
|
413
|
+
const grouped = [];
|
|
410
414
|
for (const line of snapshot.stdout.split("\n")) {
|
|
411
|
-
|
|
415
|
+
// Group membership reads EVERY row, as the separate group snapshot did; a CPU row also needs a parseable time.
|
|
416
|
+
const ids = /^\s*(\d+)\s+(\d+)\s+(\d+)(?:\s|$)/.exec(line);
|
|
417
|
+
if (!ids)
|
|
418
|
+
continue;
|
|
419
|
+
if (group !== undefined && Number(ids[3]) === group)
|
|
420
|
+
grouped.push(ids[1]);
|
|
421
|
+
const m = /^\s*(\d+)\s+(\d+)\s+\d+\s+(\S+)\s+(.*)$/.exec(line);
|
|
412
422
|
if (!m)
|
|
413
423
|
continue;
|
|
414
424
|
const cpu = parsePsCpu(m[3]);
|
|
@@ -420,16 +430,8 @@ async function workerTreeCpuSnapshot(marker, cwd, group, identities) {
|
|
|
420
430
|
const tree = new Set(rows.filter((p) => p.cmd.includes(marker)).map((p) => p.pid));
|
|
421
431
|
// CPU presentation matching is intentionally broader than authority to signal a process.
|
|
422
432
|
const owned = new Set(rows.filter((p) => p.cmd === `bash ${marker}` || p.cmd === `/bin/bash ${marker}`).map((p) => p.pid));
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
if (groups.code !== 0)
|
|
426
|
-
return undefined;
|
|
427
|
-
for (const line of groups.stdout.split("\n")) {
|
|
428
|
-
const row = /^\s*(\d+)\s+(\d+)\s*$/.exec(line);
|
|
429
|
-
if (row && Number(row[2]) === group)
|
|
430
|
-
tree.add(row[1]);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
+
for (const pid of grouped)
|
|
434
|
+
tree.add(pid);
|
|
433
435
|
// ps output is not topologically ordered; relax the parent -> child closure until stable.
|
|
434
436
|
for (let grew = true; grew;) {
|
|
435
437
|
grew = false;
|