Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
170
scripts/proof-cron-on-exit.mts
Normal file
170
scripts/proof-cron-on-exit.mts
Normal file
@@ -0,0 +1,170 @@
|
||||
// Live-proof harness for PR #92037 (cron `on-exit` schedule kind).
|
||||
//
|
||||
// Drives the REAL gateway exit-watcher (`createCronExitWatchers`) against the
|
||||
// REAL ProcessSupervisor (`getProcessSupervisor`) with a REAL short-lived child
|
||||
// command, and captures the actual arm -> exit -> persist-before-fire -> fire
|
||||
// lifecycle. The persistCompletion/fireOnExit sinks mirror the real wiring in
|
||||
// server-cron.ts (disable-before-fire; force-run after exit) and only RECORD/LOG.
|
||||
//
|
||||
// Run: pnpm exec tsx scripts/proof-cron-on-exit.mts
|
||||
//
|
||||
// All identifiers are synthetic. No real Telegram chat ids / session keys.
|
||||
|
||||
import type { CronJob } from "../src/cron/types.js";
|
||||
import {
|
||||
createCronExitWatchers,
|
||||
resolveExitWatchShell,
|
||||
} from "../src/gateway/cron-exit-watchers.js";
|
||||
import { getProcessSupervisor } from "../src/process/supervisor/index.js";
|
||||
|
||||
const isWin = process.platform === "win32";
|
||||
// Commands phrased for the shell the watcher actually resolves on this host
|
||||
// (cmd.exe /d /s /c on Windows, bash -lc on POSIX).
|
||||
const delayThenExit = (code: number) =>
|
||||
isWin ? `ping -n 3 127.0.0.1 > nul & exit ${code}` : `sleep 2; exit ${code}`;
|
||||
const longRunning = () => (isWin ? `ping -n 31 127.0.0.1 > nul` : `sleep 30`);
|
||||
|
||||
type FireEvent = { jobId: string; exitCode: number | null };
|
||||
|
||||
const events: { armed: string[]; persisted: string[]; fired: FireEvent[] } = {
|
||||
armed: [],
|
||||
persisted: [],
|
||||
fired: [],
|
||||
};
|
||||
// Monotonic call-order log so we can assert persist-before-fire directly
|
||||
// (the watcher's fail-closed guarantee), not merely that both happened.
|
||||
const order: string[] = [];
|
||||
|
||||
const logger = {
|
||||
info: (obj: unknown, msg?: string) => {
|
||||
const o = obj as { jobId?: string; exitCode?: number | null; reason?: string };
|
||||
if (msg?.includes("watcher armed") && o.jobId) {
|
||||
events.armed.push(o.jobId);
|
||||
}
|
||||
console.log(`[cron-exit] ${msg ?? ""} ${JSON.stringify(obj)}`);
|
||||
},
|
||||
warn: (obj: unknown, msg?: string) =>
|
||||
console.log(`[cron-exit][warn] ${msg ?? ""} ${JSON.stringify(obj)}`),
|
||||
};
|
||||
|
||||
const watchers = createCronExitWatchers({
|
||||
getProcessSupervisor,
|
||||
// Real wiring disables the one-shot job in the store before firing.
|
||||
persistCompletion: async (jobId) => {
|
||||
events.persisted.push(jobId);
|
||||
order.push(`persist:${jobId}`);
|
||||
console.log(`[cron-exit] persistCompletion (job disabled, enabled=false) jobId=${jobId}`);
|
||||
},
|
||||
// Real wiring routes this into cron.run(job.id, "force").
|
||||
fireOnExit: (job, exit) => {
|
||||
events.fired.push({ jobId: job.id, exitCode: exit.exitCode });
|
||||
order.push(`fire:${job.id}`);
|
||||
console.log(`[gateway/cron] cron.run force jobId=${job.id}`);
|
||||
},
|
||||
logger,
|
||||
});
|
||||
|
||||
function onExitJob(id: string, command: string, extra?: Partial<CronJob>): CronJob {
|
||||
return {
|
||||
id,
|
||||
enabled: true,
|
||||
schedule: { kind: "on-exit", command },
|
||||
sessionKey: `agent:main:telegram:direct:SYN:thread:SYN`,
|
||||
payload: { text: `on-exit[${id}]` },
|
||||
...extra,
|
||||
} as unknown as CronJob;
|
||||
}
|
||||
|
||||
const sleep = (ms: number) =>
|
||||
new Promise<void>((r) => {
|
||||
setTimeout(r, ms);
|
||||
});
|
||||
async function waitFor(pred: () => boolean, timeoutMs: number, pollMs = 100): Promise<boolean> {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
if (pred()) {
|
||||
return true;
|
||||
}
|
||||
await sleep(pollMs);
|
||||
}
|
||||
return pred();
|
||||
}
|
||||
|
||||
let failures = 0;
|
||||
function assert(label: string, cond: boolean): void {
|
||||
console.log(` ${cond ? "PASS" : "FAIL"}: ${label}`);
|
||||
if (!cond) {
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
async function run(): Promise<void> {
|
||||
const shell = resolveExitWatchShell();
|
||||
console.log(`=== PR #92037 on-exit live proof (real watcher + real ProcessSupervisor) ===`);
|
||||
console.log(
|
||||
`platform=${process.platform} shell=${shell.command} argv=${JSON.stringify(shell.argsFor("<cmd>"))}`,
|
||||
);
|
||||
|
||||
// Scenario A: arm a real watcher; the watched command runs ~2s then exits 7.
|
||||
// Expect: armed -> exited -> persistCompletion BEFORE fire -> fire with exitCode 7.
|
||||
console.log(`\n=== A. arm -> watched command exits (code 7) -> force-run fires ===`);
|
||||
const a = onExitJob("onexit-A", delayThenExit(7));
|
||||
watchers.reconcile([a]);
|
||||
assert(
|
||||
"watcher is active immediately after reconcile",
|
||||
watchers.activeJobIds().includes("onexit-A"),
|
||||
);
|
||||
const aFired = await waitFor(() => events.fired.some((e) => e.jobId === "onexit-A"), 15000);
|
||||
assert("watcher armed (logged) for onexit-A", events.armed.includes("onexit-A"));
|
||||
assert("job fired after the command exited", aFired);
|
||||
const aEvt = events.fired.find((e) => e.jobId === "onexit-A");
|
||||
assert("exit code 7 captured from the real child", aEvt?.exitCode === 7);
|
||||
assert(
|
||||
"persistCompletion ran BEFORE fire (fail-closed ordering)",
|
||||
order.includes("persist:onexit-A") &&
|
||||
order.indexOf("persist:onexit-A") < order.indexOf("fire:onexit-A"),
|
||||
);
|
||||
assert("fire routed to the cron force-run sink", aEvt?.jobId === a.id);
|
||||
|
||||
// Scenario B: arm a long-running watcher, cancel before exit -> NO fire.
|
||||
console.log(`\n=== B. arm -> cancel before exit -> no fire (revocation) ===`);
|
||||
const b = onExitJob("onexit-B", longRunning());
|
||||
watchers.reconcile([b]);
|
||||
await waitFor(() => events.armed.includes("onexit-B"), 8000);
|
||||
assert("watcher armed for onexit-B", events.armed.includes("onexit-B"));
|
||||
watchers.cancel("onexit-B");
|
||||
assert(
|
||||
"watcher removed from active set after cancel",
|
||||
!watchers.activeJobIds().includes("onexit-B"),
|
||||
);
|
||||
await sleep(2500);
|
||||
assert("cancelled watcher never fired", !events.fired.some((e) => e.jobId === "onexit-B"));
|
||||
|
||||
// Scenario C: reconcile without the job cancels its watcher.
|
||||
console.log(`\n=== C. reconcile-removal cancels the watcher ===`);
|
||||
const c = onExitJob("onexit-C", longRunning());
|
||||
watchers.reconcile([c]);
|
||||
await waitFor(() => watchers.activeJobIds().includes("onexit-C"), 8000);
|
||||
assert("watcher active for onexit-C", watchers.activeJobIds().includes("onexit-C"));
|
||||
watchers.reconcile([]); // job gone
|
||||
assert("reconcile([]) cancelled onexit-C", !watchers.activeJobIds().includes("onexit-C"));
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
try {
|
||||
await run();
|
||||
} finally {
|
||||
// Always tear down watchers so a thrown assertion can't leak the
|
||||
// long-running ping/sleep children (B, C) until their 24h timeout.
|
||||
watchers.cancelAll();
|
||||
await sleep(300);
|
||||
}
|
||||
console.log(`\n=== RESULT: ${failures === 0 ? "ALL PASS" : `${failures} FAILURE(S)`} ===`);
|
||||
process.exit(failures === 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
main().catch((err: unknown) => {
|
||||
console.error("proof harness crashed:", err);
|
||||
watchers.cancelAll();
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user