openclaw / src /cron /service /ops.run-execution-binding.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
5cb63c1 verified
Raw History Blame Contribute Delete
8.47 kB
// Focused proof that manual cron admission binds exact owner-native rows.
import { describe, expect, it, vi } from "vitest";
import {
createCronRegressionState,
createDueIsolatedJob,
setupCronRegressionFixtures,
} from "../../../test/helpers/cron/service-regression-fixtures.js";
import type { AdmittedRunContext } from "../../agents/admitted-run-context.js";
import { createExecutionIdentityAdmissionToken } from "../../audit/execution-identity-admission.js";
import { tableExists } from "../../state/openclaw-state-db-schema-helpers.js";
import {
openOpenClawStateDatabase,
runOpenClawStateWriteTransaction,
} from "../../state/openclaw-state-db.js";
import { createManagedTaskFlow } from "../../tasks/task-flow-registry.js";
import {
resetTaskFlowRegistryForTests,
resetTaskRegistryForTests,
} from "../../tasks/task-runtime.test-helpers.js";
import { withOpenClawTestState } from "../../test-utils/openclaw-test-state.js";
import { saveCronStore } from "../store.js";
import {
claimCronRunReceiptInDatabase,
finishCronRunReceipt,
prepareCronRunReceiptClaim,
} from "../store/run-receipt-store.js";
import { run } from "./ops-run.js";
import {
createCronOwnerExecutionIdentityAdmission,
tryCreateCronTaskRunHandle,
} from "./task-runs.js";
const fixtures = setupCronRegressionFixtures({
prefix: "cron-service-execution-binding-",
});
describe("cron run execution binding", () => {
it("binds the exact admitted execution to the cron receipt and task rows", async () => {
await withOpenClawTestState(
{ layout: "state-only", prefix: "openclaw-cron-execution-binding-" },
async () => {
resetTaskRegistryForTests();
resetTaskFlowRegistryForTests();
const store = fixtures.makeStorePath();
const dueAt = Date.parse("2026-02-06T10:05:06.525Z");
const job = createDueIsolatedJob({
id: "exact-owner-binding",
nowMs: dueAt,
nextRunAtMs: dueAt,
});
await saveCronStore(store.storePath, { version: 1, jobs: [job] });
const runIsolatedAgentJob = vi.fn(
async (params: {
executionIdentity?: {
ingress: { kind: string };
onPostAdmission?: (context: AdmittedRunContext) => void;
onExecutionStarted?: () => void;
};
}) => {
const admitted = {
operationalRunInstance: { instanceId: "instance-exact", runId: "run-exact" },
executionIdentityToken: createExecutionIdentityAdmissionToken("run-exact", {
contextId: "context-exact",
executionId: "execution-exact",
now: dueAt,
}),
} satisfies AdmittedRunContext;
const beforeAdmissionSettles = openOpenClawStateDatabase().db;
expect(tableExists(beforeAdmissionSettles, "execution_owner_lifecycle_bindings")).toBe(
false,
);
params.executionIdentity?.onPostAdmission?.(admitted);
expect(tableExists(beforeAdmissionSettles, "execution_owner_lifecycle_bindings")).toBe(
false,
);
params.executionIdentity?.onExecutionStarted?.();
return { status: "ok" as const };
},
);
const state = createCronRegressionState({
storePath: store.storePath,
nowMs: () => dueAt,
runIsolatedAgentJob,
});
await expect(run(state, job.id, "force")).resolves.toEqual({ ok: true, ran: true });
expect(runIsolatedAgentJob).toHaveBeenCalledWith(
expect.objectContaining({
executionIdentity: expect.objectContaining({
ingress: expect.objectContaining({ kind: "schedule" }),
}),
}),
);
const db = openOpenClawStateDatabase().db;
expect(
db
.prepare(
`SELECT binding.context_id, binding.execution_id, receipt.status, receipt.error_text
FROM cron_run_receipts AS receipt
JOIN execution_owner_lifecycle_bindings AS binding
ON binding.owner_kind = 'cron' AND binding.owner_id = receipt.receipt_id
WHERE receipt.job_id = ?`,
)
.get(job.id),
).toEqual({
context_id: "context-exact",
execution_id: "execution-exact",
status: "ok",
error_text: null,
});
expect(
db
.prepare(
`SELECT binding.context_id, binding.execution_id, task.status
FROM task_runs AS task
JOIN execution_owner_lifecycle_bindings AS binding
ON binding.owner_kind = 'task' AND binding.owner_id = task.task_id
WHERE task.source_id = ? AND task.runtime = 'cron'`,
)
.get(job.id),
).toEqual({
context_id: "context-exact",
execution_id: "execution-exact",
status: "succeeded",
});
},
);
});
it("does not partially bind task or flow rows after the cron owner is replaced", async () => {
await withOpenClawTestState(
{ layout: "state-only", prefix: "openclaw-cron-stale-execution-binding-" },
async () => {
resetTaskRegistryForTests();
resetTaskFlowRegistryForTests();
const store = fixtures.makeStorePath();
const dueAt = Date.parse("2026-02-06T10:06:06.525Z");
const job = {
...createDueIsolatedJob({
id: "stale-owner-binding",
nowMs: dueAt,
nextRunAtMs: dueAt,
}),
agentId: "main",
};
await saveCronStore(store.storePath, { version: 1, jobs: [job] });
const state = createCronRegressionState({
storePath: store.storePath,
nowMs: () => dueAt,
runIsolatedAgentJob: vi.fn(),
});
const prepared = prepareCronRunReceiptClaim({
storePath: store.storePath,
job,
agentId: job.agentId!,
startedAtMs: dueAt,
});
const initial = runOpenClawStateWriteTransaction(({ db }) =>
claimCronRunReceiptInDatabase({
database: db,
prepared,
resolveAgentId: (current) => current.agentId!,
}),
);
const task = tryCreateCronTaskRunHandle({ state, job, startedAt: dueAt });
expect(task).toBeDefined();
const flow = createManagedTaskFlow({
ownerKey: "agent:main:main",
controllerId: "tests/stale-owner-binding",
goal: "Reject partial stale-owner provenance",
status: "running",
});
expect(flow).not.toBeNull();
if (!task || !flow) {
throw new Error("expected live task and flow owners");
}
const executionIdentity = createCronOwnerExecutionIdentityAdmission({
state,
runReceipt: initial,
taskId: task.taskId,
flowId: flow.flowId,
});
const admitted = {
operationalRunInstance: { instanceId: "instance-stale", runId: "run-stale" },
executionIdentityToken: createExecutionIdentityAdmissionToken("run-stale", {
contextId: "context-stale",
executionId: "execution-stale",
now: dueAt,
}),
} satisfies AdmittedRunContext;
executionIdentity.onPostAdmission?.(admitted);
const db = openOpenClawStateDatabase().db;
db.prepare("UPDATE cron_run_receipts SET owner_pid = ? WHERE receipt_id = ?").run(
2_147_483_647,
initial.receiptId,
);
const replacementPrepared = prepareCronRunReceiptClaim({
storePath: store.storePath,
job,
agentId: job.agentId!,
startedAtMs: dueAt + 1,
});
const replacement = runOpenClawStateWriteTransaction(({ db: transactionDb }) =>
claimCronRunReceiptInDatabase({
database: transactionDb,
prepared: replacementPrepared,
resolveAgentId: (current) => current.agentId!,
}),
);
executionIdentity.onExecutionStarted?.();
expect(
tableExists(openOpenClawStateDatabase().db, "execution_owner_lifecycle_bindings"),
).toBe(false);
finishCronRunReceipt({
handle: replacement,
status: "skipped",
finishedAtMs: dueAt + 2,
});
},
);
});
});