Migration
Move legacy function-target jobs to task-first jobs safely.
Preserve durable job and task identities when changing application source or provider configuration. Preserve the existing public job ID, task ID, version, input schema, output schema, dedupe scope, schedule identity, and client fingerprint unless the change is intentional.
This is a one-release compatibility window. Legacy function-target definitions, .enqueue() producers,
and the deprecated profile spelling remain available only when explicitly enabled; new code must use a
task-first definition and service selection. Use a compatible release cohort before changing the
application graph; packages have independent versions, so follow the selected owner’s installation
requirements rather than assigning one version number to every RELKIT package.
The migration helper is exposed from the jobs package; run it against the previous and next snapshots before activation. It reports identity, binding, schema, history, schedule, dedupe, and client-fingerprint changes.
import { expect, test } from "vitest";
import { diagnoseJobMigration, diagnoseLegacyCompatibility } from "../src/index.ts";
const base = {
name: "exportOrders",
id: "orders.export",
idSource: "explicit" as const,
taskId: "orders.export",
taskVersion: "1",
inputSchemaHash: "input-v1",
binding: "implicit" as const,
publicFingerprint: "client-v1",
clientFingerprint: "client-v1",
hasHistoricalRuns: true,
scheduleIds: ["nightly"],
dedupeScope: "tenant",
};
test("explains identity, binding, schema, history, and client migration hazards", () => {
const diagnostics = diagnoseJobMigration(base, {
...base,
name: "exportOrderArchive",
id: "exportOrderArchive",
idSource: "name",
binding: "explicit",
taskVersion: "2",
inputSchemaHash: "input-v2",
publicFingerprint: "client-v2",
clientFingerprint: "client-v2",
scheduleIds: ["weekly"],
dedupeScope: "account",
});
expect(diagnostics.map(({ code }) => code)).toEqual([
"PINNED_JOB_ID_CHANGED",
"IMPLICIT_TO_EXPLICIT_BINDING",
"TASK_VERSION_CHANGED",
"TASK_INPUT_SCHEMA_CHANGED",
"PUBLIC_CLIENT_FINGERPRINT_CHANGED",
"DEDUPE_SCOPE_CHANGED",
"SCHEDULE_TARGET_CHANGED",
"HISTORICAL_RUNS_PINNED",
]);
expect(diagnostics.every((diagnostic) => diagnostic.message.length > 0)).toBe(true);
});
test("keeps legacy execution explicitly gated and aliases exclusive", () => {
expect(
diagnoseLegacyCompatibility({ usesFunctionTarget: true, legacyJobsEnabled: false })[0]?.code,
).toBe("LEGACY_JOBS_DISABLED");
expect(
diagnoseLegacyCompatibility({
usesFunctionTarget: true,
legacyJobsEnabled: true,
legacyKeys: ["job"],
newKeys: ["jobs"],
})[0]?.code,
).toBe("LEGACY_ALIAS_CONFLICT");
expect(
diagnoseLegacyCompatibility({
usesFunctionTarget: false,
legacyJobsEnabled: false,
legacyKeys: ["defaults.job"],
})[0]?.code,
).toBe("LEGACY_ALIAS_DEPRECATED");
});
Diagnostics flag pinned/default-derived ID changes, implicit-to-explicit binding changes, task version or schema incompatibility, history and schedule changes, dedupe changes, and client fingerprint changes. Legacy aliases and function-target definitions must be explicit during the migration window; the compiler does not silently rewrite them.
Roll out a compatible worker first, register the task-first definition, wait for readiness, then activate new triggers. Keep the old definition and worker generation until historical runs are terminal. If the input or output contract is incompatible, use a new task version or a new public job ID and document the boundary.
Provider retention, native listing, durable sleep, schedule recovery, and cancellation limits are variant-specific. Check the provider comparison and deployment evidence before relying on them; unsupported or not-tested capabilities are not emulated by the RELKIT runtime.