1650 lines
70 KiB
TypeScript
1650 lines
70 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { test } from "node:test";
|
|
|
|
import { DreaminaCliArbiter } from "../src/dreamina-cli-arbiter.js";
|
|
import { DreaminaCliRuntime } from "../src/dreamina-cli-runtime.js";
|
|
import { DreaminaCliService } from "../src/dreamina-cli.js";
|
|
import { DreaminaCliError } from "../src/dreamina-cli-process.js";
|
|
|
|
const ownerId = "owner-arbiter-fixture-0001";
|
|
const installation = { installed: true as const, executable: "dreamina-fixture" };
|
|
|
|
|
|
test("arbiter replacement recovery is revisioned and fail-closed for equal-revision divergence", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-revision-recovery-"));
|
|
const stateFile = path.join(root, "arbiter.json");
|
|
const backupFile = `${stateFile}.replace-backup`;
|
|
const base = {
|
|
version: 1,
|
|
revision: 5,
|
|
nextTicket: 4,
|
|
nextFenceEpoch: 7,
|
|
sessionEpoch: 2,
|
|
accountBinding: "a".repeat(64),
|
|
queue: [],
|
|
};
|
|
try {
|
|
await fs.writeFile(backupFile, JSON.stringify(base));
|
|
await fs.writeFile(stateFile, JSON.stringify(base));
|
|
const arbiter = new DreaminaCliArbiter({ stateFile, pollMs: 1 });
|
|
assert.equal((await arbiter.readSession()).sessionEpoch, 2);
|
|
assert.equal(await exists(backupFile), false);
|
|
|
|
await fs.writeFile(backupFile, JSON.stringify(base));
|
|
await fs.writeFile(stateFile, JSON.stringify({
|
|
...base,
|
|
queue: [{
|
|
requestId: "11111111-1111-4111-8111-111111111111",
|
|
ticket: 3,
|
|
expiresAt: Date.now() + 60_000,
|
|
}],
|
|
}));
|
|
await assert.rejects(arbiter.readSession(), (error: unknown) => (
|
|
error instanceof DreaminaCliError && error.code === "dreamina_arbiter_state_invalid"
|
|
));
|
|
assert.equal(await exists(backupFile), true);
|
|
} finally {
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("arbiter recovery never rolls back or reuses durable ticket fence or session epochs", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-revision-progress-"));
|
|
const stateFile = path.join(root, "arbiter.json");
|
|
const backupFile = `${stateFile}.replace-backup`;
|
|
const durable = {
|
|
version: 1,
|
|
revision: 8,
|
|
nextTicket: 9,
|
|
nextFenceEpoch: 12,
|
|
sessionEpoch: 3,
|
|
queue: [],
|
|
};
|
|
try {
|
|
await fs.writeFile(backupFile, JSON.stringify(durable));
|
|
const arbiter = new DreaminaCliArbiter({ stateFile, pollMs: 1 });
|
|
const restored = await arbiter.acquire();
|
|
assert.equal(restored.fenceEpoch, 12);
|
|
await restored.release();
|
|
assert.equal(await exists(backupFile), false);
|
|
|
|
const current = JSON.parse(await fs.readFile(stateFile, "utf8")) as Record<string, unknown>;
|
|
await fs.writeFile(backupFile, JSON.stringify({
|
|
...current,
|
|
revision: Number(current.revision) + 2,
|
|
nextTicket: Number(current.nextTicket) + 2,
|
|
nextFenceEpoch: Number(current.nextFenceEpoch) + 4,
|
|
sessionEpoch: Number(current.sessionEpoch) + 1,
|
|
}));
|
|
await assert.rejects(arbiter.readSession(), (error: unknown) => (
|
|
error instanceof DreaminaCliError && error.code === "dreamina_arbiter_state_invalid"
|
|
));
|
|
assert.equal(await exists(backupFile), true);
|
|
} finally {
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("arbiter recovery scavenges orphan replacement temporaries without using them as committed state", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-temp-recovery-"));
|
|
const stateFile = path.join(root, "arbiter.json");
|
|
const committed = {
|
|
version: 1,
|
|
revision: 3,
|
|
nextTicket: 5,
|
|
nextFenceEpoch: 6,
|
|
sessionEpoch: 1,
|
|
queue: [],
|
|
};
|
|
const temporary = `${stateFile}.${process.pid}.11111111-1111-4111-8111-111111111111.tmp`;
|
|
try {
|
|
await fs.writeFile(stateFile, JSON.stringify(committed));
|
|
await fs.writeFile(temporary, JSON.stringify({
|
|
...committed,
|
|
revision: 99,
|
|
nextFenceEpoch: 999,
|
|
}));
|
|
const arbiter = new DreaminaCliArbiter({ stateFile, pollMs: 1 });
|
|
assert.equal((await arbiter.readSession()).sessionEpoch, 1);
|
|
assert.equal(await exists(temporary), false);
|
|
const lease = await arbiter.acquire();
|
|
assert.equal(lease.fenceEpoch, 6);
|
|
await lease.release();
|
|
} finally {
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("arbiter Windows replacement cleanup faults preserve the committed mutation without reusing its fence", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-windows-replace-fault-"));
|
|
const stateFile = path.join(root, "arbiter.json");
|
|
const backupFile = `${stateFile}.replace-backup`;
|
|
const arbiter = new DreaminaCliArbiter({ stateFile, pollMs: 1 });
|
|
const first = await arbiter.acquire();
|
|
const firstEpoch = first.fenceEpoch;
|
|
await first.release();
|
|
|
|
const mutableFs = fs as unknown as {
|
|
rename: (...args: any[]) => Promise<void>;
|
|
rm: (...args: any[]) => Promise<void>;
|
|
};
|
|
const originalRename = mutableFs.rename;
|
|
const originalRm = mutableFs.rm;
|
|
let forcedOverwriteDenial = false;
|
|
let forcedBackupCleanupFailure = false;
|
|
mutableFs.rename = async (...args: any[]) => {
|
|
const source = String(args[0]);
|
|
const destination = String(args[1]);
|
|
if (!forcedOverwriteDenial && source.endsWith(".tmp") && destination === stateFile) {
|
|
forcedOverwriteDenial = true;
|
|
throw Object.assign(new Error("fixture overwrite denial"), { code: "EPERM" });
|
|
}
|
|
return originalRename(...args);
|
|
};
|
|
mutableFs.rm = async (...args: any[]) => {
|
|
const target = String(args[0]);
|
|
if (!forcedBackupCleanupFailure && target === backupFile) {
|
|
forcedBackupCleanupFailure = true;
|
|
throw Object.assign(new Error("fixture backup cleanup denial"), { code: "EACCES" });
|
|
}
|
|
return originalRm(...args);
|
|
};
|
|
try {
|
|
const second = await arbiter.acquire();
|
|
assert.equal(second.fenceEpoch, firstEpoch + 1);
|
|
await second.release();
|
|
const third = await arbiter.acquire();
|
|
assert.equal(third.fenceEpoch, firstEpoch + 2);
|
|
await third.release();
|
|
assert.equal(forcedOverwriteDenial, true);
|
|
assert.equal(forcedBackupCleanupFailure, true);
|
|
assert.equal(await exists(backupFile), false);
|
|
assert.equal(await exists(`${stateFile}.lock`), false);
|
|
assert.deepEqual((await fs.readdir(root)).filter((entry) => entry.endsWith(".tmp")), []);
|
|
} finally {
|
|
mutableFs.rename = originalRename;
|
|
mutableFs.rm = originalRm;
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("arbiter session mutations use the injected clock consistently with acquisition", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-injected-clock-"));
|
|
const stateFile = path.join(root, "arbiter.json");
|
|
let now = 1_000;
|
|
const arbiter = new DreaminaCliArbiter({
|
|
stateFile,
|
|
now: () => now,
|
|
leaseMs: 1_000,
|
|
heartbeatMs: 0,
|
|
pollMs: 1,
|
|
});
|
|
try {
|
|
const lease = await arbiter.acquire();
|
|
const bound = await arbiter.commitSession(lease, "b".repeat(64));
|
|
assert.equal(bound.accountBinding, "b".repeat(64));
|
|
assert.equal(bound.sessionEpoch, 1);
|
|
now = 2_001;
|
|
await assert.rejects(arbiter.advanceSession(lease), (error: unknown) => (
|
|
error instanceof DreaminaCliError && error.code === "dreamina_cli_fenced"
|
|
));
|
|
await lease.release();
|
|
} finally {
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("Dreamina lifecycle service and Runtime share one FIFO arbiter per physical CLI process", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-service-runtime-"));
|
|
const arbiterFile = path.join(root, "arbiter.json");
|
|
const stateFile = path.join(root, "runtime.json");
|
|
const serviceArbiter = new DreaminaCliArbiter({ stateFile: arbiterFile, pollMs: 1 });
|
|
const runtimeArbiter = new DreaminaCliArbiter({ stateFile: arbiterFile, pollMs: 1 });
|
|
await fs.writeFile(stateFile, JSON.stringify({
|
|
version: 1,
|
|
records: [acceptedRuntimeRecord("dreamina-service-runtime-query-0001", "receipt-service-runtime-query", "a", "2020-01-01T00:00:00.000Z")],
|
|
}));
|
|
let active = 0;
|
|
let maxActive = 0;
|
|
const events: string[] = [];
|
|
let releaseVersion!: () => void;
|
|
let markQueryIntent!: () => void;
|
|
const versionGate = new Promise<void>((resolve) => { releaseVersion = resolve; });
|
|
const queryIntent = new Promise<void>((resolve) => { markQueryIntent = resolve; });
|
|
const runProcess = async (request: { args: string[] }) => {
|
|
active += 1;
|
|
maxActive = Math.max(maxActive, active);
|
|
const command = request.args[0] ?? "";
|
|
events.push(`start:${command}`);
|
|
try {
|
|
if (command === "--version") await versionGate;
|
|
if (command === "--version") return { exitCode: 0, stdout: '{"version":"1.2.3"}', stderr: "" };
|
|
if (command === "user_credit") return { exitCode: 0, stdout: '{"total_credit":100}', stderr: "" };
|
|
if (command === "query_result") return { exitCode: 0, stdout: '{"gen_status":"processing"}', stderr: "" };
|
|
throw new Error(`unexpected command ${command}`);
|
|
} finally {
|
|
events.push(`end:${command}`);
|
|
active -= 1;
|
|
}
|
|
};
|
|
const service = new DreaminaCliService({
|
|
ownerId,
|
|
arbiter: serviceArbiter,
|
|
discover: async () => installation,
|
|
runProcess,
|
|
});
|
|
const runtime = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
arbiter: runtimeArbiter,
|
|
ensureReady: async () => { markQueryIntent(); },
|
|
discover: async () => installation,
|
|
runProcess,
|
|
});
|
|
try {
|
|
const status = service.status();
|
|
await waitFor(() => events.includes("start:--version"));
|
|
await runtime.start();
|
|
await queryIntent;
|
|
await waitForAsync(async () => (await readArbiterQueueLength(arbiterFile)) === 1);
|
|
releaseVersion();
|
|
await status;
|
|
await waitFor(() => events.includes("start:query_result"));
|
|
assert.equal(maxActive, 1);
|
|
assert.deepEqual(events.filter((event) => event.startsWith("start:")), [
|
|
"start:--version",
|
|
"start:query_result",
|
|
"start:user_credit",
|
|
]);
|
|
} finally {
|
|
releaseVersion();
|
|
await runtime.dispose();
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("authenticated status establishes one opaque stable account binding without using provider output identity", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-binding-"));
|
|
const stateFile = path.join(root, "arbiter.json");
|
|
const serviceA = new DreaminaCliService({
|
|
ownerId,
|
|
arbiter: new DreaminaCliArbiter({ stateFile, pollMs: 1 }),
|
|
discover: async () => installation,
|
|
runProcess: async (request) => request.args[0] === "--version"
|
|
? { exitCode: 0, stdout: '{"version":"1.2.3"}', stderr: "" }
|
|
: { exitCode: 0, stdout: '{"total_credit":100,"account":"must-not-be-used"}', stderr: "" },
|
|
});
|
|
const serviceB = new DreaminaCliService({
|
|
ownerId,
|
|
arbiter: new DreaminaCliArbiter({ stateFile, pollMs: 1 }),
|
|
discover: async () => installation,
|
|
runProcess: async (request) => request.args[0] === "--version"
|
|
? { exitCode: 0, stdout: '{"version":"1.2.3"}', stderr: "" }
|
|
: { exitCode: 0, stdout: '{"total_credit":999,"account":"different-raw-value"}', stderr: "" },
|
|
});
|
|
try {
|
|
const first = await serviceA.statusWithSession();
|
|
const second = await serviceB.statusWithSession();
|
|
assert.equal(first.status.authenticated, true);
|
|
assert.match(first.session.accountBinding ?? "", /^[a-f0-9]{64}$/);
|
|
assert.equal(second.session.accountBinding, first.session.accountBinding);
|
|
assert.equal(second.session.sessionEpoch, first.session.sessionEpoch);
|
|
assert.equal(JSON.stringify(first.status).includes(first.session.accountBinding!), false);
|
|
assert.equal(first.session.accountBinding, (await new DreaminaCliArbiter({ stateFile }).readSession()).accountBinding);
|
|
} finally {
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("logout commits a session fence before releasing the CLI lease so an old-session queued query never spawns", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-logout-session-"));
|
|
const arbiterFile = path.join(root, "arbiter.json");
|
|
const stateFile = path.join(root, "runtime.json");
|
|
const serviceArbiter = new DreaminaCliArbiter({ stateFile: arbiterFile, pollMs: 1 });
|
|
const runtimeArbiter = new DreaminaCliArbiter({ stateFile: arbiterFile, pollMs: 1 });
|
|
let logoutEntered = false;
|
|
let releaseLogout!: () => void;
|
|
const logoutGate = new Promise<void>((resolve) => { releaseLogout = resolve; });
|
|
const service = new DreaminaCliService({
|
|
ownerId,
|
|
arbiter: serviceArbiter,
|
|
discover: async () => installation,
|
|
runProcess: async (request) => {
|
|
if (request.args[0] === "--version") return { exitCode: 0, stdout: '{"version":"1.2.3"}', stderr: "" };
|
|
if (request.args[0] === "user_credit") return { exitCode: 0, stdout: '{"total_credit":100}', stderr: "" };
|
|
if (request.args[0] === "logout") {
|
|
logoutEntered = true;
|
|
await logoutGate;
|
|
return { exitCode: 0, stdout: "", stderr: "" };
|
|
}
|
|
throw new Error("unexpected lifecycle command");
|
|
},
|
|
});
|
|
const authenticated = await service.statusWithSession();
|
|
await fs.writeFile(stateFile, JSON.stringify({
|
|
version: 1,
|
|
records: [{
|
|
ownerId,
|
|
idempotencyKey: "dreamina-logout-bound-query-0001",
|
|
requestHash: "e".repeat(64),
|
|
state: "accepted",
|
|
submitId: "receipt-old-session-query",
|
|
accountBinding: authenticated.session.accountBinding,
|
|
sessionEpoch: authenticated.session.sessionEpoch,
|
|
updatedAt: "2026-08-13T00:00:00.000Z",
|
|
taskVersion: 1,
|
|
operation: "text2video",
|
|
mode: "video",
|
|
model: "seedance2.0mini",
|
|
createdAt: "2026-08-13T00:00:00.000Z"
|
|
}],
|
|
}));
|
|
let queryCalls = 0;
|
|
const runtime = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
arbiter: runtimeArbiter,
|
|
ensureReady: async () => authenticated.session,
|
|
discover: async () => installation,
|
|
runProcess: async () => {
|
|
queryCalls += 1;
|
|
return { exitCode: 0, stdout: '{"gen_status":"processing"}', stderr: "" };
|
|
},
|
|
});
|
|
try {
|
|
const logout = service.logout();
|
|
await waitFor(() => logoutEntered);
|
|
const query = runtime.waitForTask("dreamina-logout-bound-query-0001", "video");
|
|
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
releaseLogout();
|
|
await logout;
|
|
await assert.rejects(query, (error: unknown) => (
|
|
error instanceof DreaminaCliError && error.code === "dreamina_account_session_changed"
|
|
));
|
|
assert.equal(queryCalls, 0);
|
|
const after = await runtimeArbiter.readSession();
|
|
assert.equal(after.accountBinding, undefined);
|
|
assert.ok(after.sessionEpoch > authenticated.session.sessionEpoch);
|
|
} finally {
|
|
releaseLogout();
|
|
await runtime.dispose();
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("Dreamina CLI invocation is exclusive across two Runtime instances", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-cross-runtime-"));
|
|
const stateFile = path.join(root, "runtime-state.json");
|
|
await fs.writeFile(stateFile, JSON.stringify({
|
|
version: 1,
|
|
records: [
|
|
acceptedRuntimeRecord("dreamina-arbiter-runtime-A-0001", "receipt-arbiter-A", "b", "2099-01-01T00:00:00.000Z"),
|
|
acceptedRuntimeRecord("dreamina-arbiter-runtime-B-0001", "receipt-arbiter-B", "c", "2099-01-01T00:00:00.000Z"),
|
|
],
|
|
}));
|
|
let active = 0;
|
|
let maxActive = 0;
|
|
let entered = 0;
|
|
let release!: () => void;
|
|
const gate = new Promise<void>((resolve) => { release = resolve; });
|
|
const runProcess = async () => {
|
|
active += 1;
|
|
entered += 1;
|
|
maxActive = Math.max(maxActive, active);
|
|
try {
|
|
if (entered === 1) await gate;
|
|
return { exitCode: 0, stdout: '{"gen_status":"processing"}', stderr: "" };
|
|
} finally {
|
|
active -= 1;
|
|
}
|
|
};
|
|
const runtimeA = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
ensureReady: async () => undefined,
|
|
discover: async () => installation,
|
|
runProcess,
|
|
});
|
|
const runtimeB = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
ensureReady: async () => undefined,
|
|
discover: async () => installation,
|
|
runProcess,
|
|
});
|
|
try {
|
|
await Promise.all([
|
|
runtimeA.refreshTask("dreamina-arbiter-runtime-A-0001"),
|
|
runtimeB.refreshTask("dreamina-arbiter-runtime-B-0001"),
|
|
]);
|
|
await waitFor(() => entered >= 1);
|
|
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
assert.equal(maxActive, 1);
|
|
release();
|
|
await waitFor(() => entered >= 2);
|
|
} finally {
|
|
release();
|
|
await Promise.allSettled([runtimeA.dispose(), runtimeB.dispose()]);
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("arbiter owner release hands off to a queued successor without leaving the state lock", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-release-handoff-"));
|
|
const stateFile = path.join(root, "arbiter.json");
|
|
const ownerArbiter = new DreaminaCliArbiter({ stateFile, pollMs: 1, heartbeatMs: 25, leaseMs: 250 });
|
|
const waiterArbiter = new DreaminaCliArbiter({ stateFile, pollMs: 1, heartbeatMs: 25, leaseMs: 250 });
|
|
try {
|
|
for (let round = 0; round < 20; round += 1) {
|
|
const events: string[] = [];
|
|
const owner = await ownerArbiter.acquire();
|
|
events.push(`owner:${owner.fenceEpoch}`);
|
|
const controller = new AbortController();
|
|
const successorPromise = waiterArbiter.acquire({ signal: controller.signal }).then((lease) => {
|
|
events.push(`successor:${lease.fenceEpoch}`);
|
|
return lease;
|
|
});
|
|
await waitForAsync(async () => (await readArbiterQueueLength(stateFile)) === 1);
|
|
events.push("waiter_queued");
|
|
await owner.release();
|
|
events.push("owner_released");
|
|
const successor = await promiseBeforeAbort(successorPromise, controller, 1_000);
|
|
assert.equal(successor.fenceEpoch, owner.fenceEpoch + 1, JSON.stringify(events));
|
|
await successor.release();
|
|
events.push("successor_released");
|
|
assert.equal(await exists(`${stateFile}.lock`), false, JSON.stringify(events));
|
|
}
|
|
} finally {
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("an aborted queued arbiter waiter releases its state-lock activity and does not block the next owner", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-abort-handoff-"));
|
|
const stateFile = path.join(root, "arbiter.json");
|
|
const ownerArbiter = new DreaminaCliArbiter({ stateFile, pollMs: 1, heartbeatMs: 25, leaseMs: 250 });
|
|
const waiterArbiter = new DreaminaCliArbiter({ stateFile, pollMs: 1, heartbeatMs: 25, leaseMs: 250 });
|
|
try {
|
|
for (let round = 0; round < 20; round += 1) {
|
|
const owner = await ownerArbiter.acquire();
|
|
const controller = new AbortController();
|
|
const waiter = waiterArbiter.acquire({ signal: controller.signal });
|
|
await waitForAsync(async () => (await readArbiterQueueLength(stateFile)) === 1);
|
|
controller.abort();
|
|
await assert.rejects(waiter, (error: unknown) => error instanceof DreaminaCliError && error.code === "dreamina_cancelled");
|
|
await owner.release();
|
|
const nextController = new AbortController();
|
|
const next = await promiseBeforeAbort(waiterArbiter.acquire({ signal: nextController.signal }), nextController, 1_000);
|
|
await next.release();
|
|
assert.equal(await exists(`${stateFile}.lock`), false);
|
|
}
|
|
} finally {
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("expired owner cannot commit a late submit result after another arbiter takes over", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-fence-"));
|
|
const arbiterFile = path.join(root, "arbiter.json");
|
|
const stateA = path.join(root, "runtime-a.json");
|
|
const stateB = path.join(root, "runtime-b.json");
|
|
const arbiterA = new DreaminaCliArbiter({ stateFile: arbiterFile, leaseMs: 100, heartbeatMs: 0, pollMs: 5 });
|
|
const arbiterB = new DreaminaCliArbiter({ stateFile: arbiterFile, leaseMs: 100, heartbeatMs: 0, pollMs: 5 });
|
|
await fs.writeFile(stateB, JSON.stringify({
|
|
version: 1,
|
|
records: [acceptedRuntimeRecord("dreamina-fenced-owner-B-0001", "receipt-owner-B-query", "d", "2020-01-01T00:00:00.000Z")],
|
|
}));
|
|
let submitEntered = false;
|
|
let releaseSubmit!: () => void;
|
|
const submitGate = new Promise<void>((resolve) => { releaseSubmit = resolve; });
|
|
const runtimeA = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile: stateA,
|
|
arbiter: arbiterA,
|
|
ensureReady: async () => undefined,
|
|
discover: async () => installation,
|
|
runProcess: async (input) => {
|
|
if (input.args[0] !== "text2image") throw new Error("unexpected owner-A command");
|
|
submitEntered = true;
|
|
input.onSpawn?.(4242);
|
|
await submitGate;
|
|
return { exitCode: 0, stdout: '{"submit_id":"receipt-fenced-owner-A"}', stderr: "" };
|
|
},
|
|
});
|
|
let ownerBQueries = 0;
|
|
const runtimeB = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile: stateB,
|
|
arbiter: arbiterB,
|
|
ensureReady: async () => undefined,
|
|
discover: async () => installation,
|
|
runProcess: async () => {
|
|
ownerBQueries += 1;
|
|
return { exitCode: 0, stdout: '{"gen_status":"processing"}', stderr: "" };
|
|
},
|
|
});
|
|
try {
|
|
const late = runtimeA.run({
|
|
operation: "text2image",
|
|
idempotencyKey: "dreamina-fenced-owner-A-0001",
|
|
prompt: "fixture",
|
|
resolutionType: "2k",
|
|
});
|
|
await waitFor(() => submitEntered);
|
|
await new Promise((resolve) => setTimeout(resolve, 140));
|
|
await runtimeB.start();
|
|
await waitFor(() => ownerBQueries === 1);
|
|
releaseSubmit();
|
|
await assert.rejects(late, (error: unknown) => error instanceof DreaminaCliError);
|
|
|
|
const disk = JSON.parse(await fs.readFile(stateA, "utf8")) as { records: Array<Record<string, unknown>> };
|
|
assert.equal(disk.records[0]?.state, "pending");
|
|
assert.equal(disk.records[0]?.fenceEpoch, 1);
|
|
assert.equal(disk.records[0]?.submitId, undefined);
|
|
assert.equal(disk.records[0]?.officialStatus, undefined);
|
|
} finally {
|
|
releaseSubmit();
|
|
await Promise.allSettled([runtimeA.dispose(), runtimeB.dispose()]);
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("submit receipt is durably accepted before the invocation lease is released", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-receipt-before-release-"));
|
|
const stateFile = path.join(root, "runtime.json");
|
|
const baseArbiter = new DreaminaCliArbiter({ stateFile: path.join(root, "arbiter.json"), pollMs: 5 });
|
|
let stateAtFirstRelease: Record<string, unknown> | undefined;
|
|
const arbiter = new Proxy(baseArbiter, {
|
|
get(target, property, receiver) {
|
|
if (property !== "acquire") return Reflect.get(target, property, receiver);
|
|
return async (...args: Parameters<DreaminaCliArbiter["acquire"]>) => {
|
|
const lease = await target.acquire(...args);
|
|
return {
|
|
...lease,
|
|
release: async () => {
|
|
if (!stateAtFirstRelease) {
|
|
stateAtFirstRelease = await readJournalRecord(stateFile, "dreamina-receipt-before-release-0001");
|
|
}
|
|
await lease.release();
|
|
},
|
|
};
|
|
};
|
|
},
|
|
}) as DreaminaCliArbiter;
|
|
const runtime = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
arbiter,
|
|
ensureReady: async () => undefined,
|
|
discover: async () => installation,
|
|
maxPollAttempts: 1,
|
|
runProcess: async (input) => {
|
|
if (input.args[0] === "text2video") {
|
|
input.onSpawn?.(4242);
|
|
return { exitCode: 0, stdout: '{"submit_id":"receipt-before-release"}', stderr: "" };
|
|
}
|
|
return { exitCode: 0, stdout: '{"gen_status":"failed"}', stderr: "" };
|
|
},
|
|
});
|
|
try {
|
|
const task = await runtime.enqueue({
|
|
operation: "text2video",
|
|
idempotencyKey: "dreamina-receipt-before-release-0001",
|
|
prompt: "fixture",
|
|
modelVersion: "seedance2.0mini",
|
|
ratio: "16:9",
|
|
videoResolution: "720p",
|
|
duration: 4,
|
|
});
|
|
assert.equal(task.receiptRecorded, true);
|
|
assert.equal(stateAtFirstRelease?.state, "accepted");
|
|
assert.equal(stateAtFirstRelease?.submitId, "receipt-before-release");
|
|
} finally {
|
|
await runtime.dispose();
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("a successor invocation can acquire only after the prior receipt is durably accepted", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-receipt-successor-fence-"));
|
|
const stateFile = path.join(root, "runtime.json");
|
|
const arbiterFile = path.join(root, "arbiter.json");
|
|
const arbiterA = new DreaminaCliArbiter({ stateFile: arbiterFile, pollMs: 5 });
|
|
const arbiterB = new DreaminaCliArbiter({ stateFile: arbiterFile, pollMs: 5 });
|
|
const originalAcquire = arbiterA.acquire.bind(arbiterA);
|
|
let acceptedBeforeSuccessor = false;
|
|
arbiterA.acquire = async (...args: Parameters<DreaminaCliArbiter["acquire"]>) => {
|
|
const lease = await originalAcquire(...args);
|
|
return {
|
|
...lease,
|
|
release: async () => {
|
|
const record = await readJournalRecord(stateFile, "dreamina-former-owner-0001");
|
|
acceptedBeforeSuccessor = record?.state === "accepted" && record?.submitId === "receipt-former-owner";
|
|
await lease.release();
|
|
const successor = await arbiterB.acquire();
|
|
await successor.release();
|
|
},
|
|
};
|
|
};
|
|
const runtime = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
arbiter: arbiterA,
|
|
ensureReady: async () => undefined,
|
|
discover: async () => installation,
|
|
maxPollAttempts: 1,
|
|
runProcess: async (input) => {
|
|
if (input.args[0] === "text2video") {
|
|
input.onSpawn?.(4242);
|
|
return { exitCode: 0, stdout: '{"submit_id":"receipt-former-owner"}', stderr: "" };
|
|
}
|
|
return { exitCode: 0, stdout: '{"gen_status":"failed"}', stderr: "" };
|
|
},
|
|
});
|
|
try {
|
|
const task = await runtime.enqueue({
|
|
operation: "text2video",
|
|
idempotencyKey: "dreamina-former-owner-0001",
|
|
prompt: "fixture",
|
|
modelVersion: "seedance2.0mini",
|
|
ratio: "16:9",
|
|
videoResolution: "720p",
|
|
duration: 4,
|
|
});
|
|
assert.equal(task.receiptRecorded, true);
|
|
assert.equal(acceptedBeforeSuccessor, true);
|
|
const record = await readJournalRecord(stateFile, "dreamina-former-owner-0001");
|
|
assert.equal(record?.state, "accepted");
|
|
assert.equal(record?.submitId, "receipt-former-owner");
|
|
} finally {
|
|
await runtime.dispose();
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("account-A query queued before a session switch never runs in account-B session", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-account-"));
|
|
const arbiterFile = path.join(root, "arbiter.json");
|
|
const stateFile = path.join(root, "runtime.json");
|
|
const accountA = "a".repeat(64);
|
|
const accountB = "b".repeat(64);
|
|
const arbiter = new DreaminaCliArbiter({ stateFile: arbiterFile, leaseMs: 5_000, pollMs: 5 });
|
|
const initLease = await arbiter.acquire();
|
|
const sessionA = await arbiter.commitSession(initLease, accountA);
|
|
await initLease.release();
|
|
await fs.writeFile(stateFile, JSON.stringify({
|
|
version: 1,
|
|
records: [{
|
|
ownerId,
|
|
idempotencyKey: "dreamina-account-query-0001",
|
|
requestHash: "c".repeat(64),
|
|
state: "accepted",
|
|
submitId: "receipt-account-query-0001",
|
|
accountBinding: accountA,
|
|
sessionEpoch: sessionA.sessionEpoch,
|
|
updatedAt: "2026-08-13T00:00:00.000Z",
|
|
taskVersion: 1,
|
|
operation: "text2video",
|
|
mode: "video",
|
|
model: "seedance2.0mini",
|
|
createdAt: "2026-08-13T00:00:00.000Z"
|
|
}],
|
|
}));
|
|
let readyEntered = false;
|
|
let queryCalls = 0;
|
|
const runtime = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
arbiter,
|
|
ensureReady: async () => {
|
|
readyEntered = true;
|
|
return sessionA;
|
|
},
|
|
discover: async () => installation,
|
|
maxPollAttempts: 1,
|
|
runProcess: async () => {
|
|
queryCalls += 1;
|
|
return { exitCode: 0, stdout: '{"gen_status":"processing"}', stderr: "" };
|
|
},
|
|
});
|
|
const blocker = await arbiter.acquire();
|
|
try {
|
|
const pending = runtime.waitForTask("dreamina-account-query-0001", "video");
|
|
await waitFor(() => readyEntered);
|
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
await arbiter.commitSession(blocker, accountB);
|
|
await blocker.release();
|
|
|
|
await assert.rejects(pending, (error: unknown) => (
|
|
error instanceof DreaminaCliError && error.code === "dreamina_account_session_changed"
|
|
));
|
|
assert.equal(queryCalls, 0);
|
|
const disk = JSON.parse(await fs.readFile(stateFile, "utf8")) as { records: Array<Record<string, unknown>> };
|
|
assert.equal(disk.records[0]?.officialStatus, undefined);
|
|
} finally {
|
|
await blocker.release();
|
|
await runtime.dispose();
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("a second Runtime first load does not recover another Runtime's live pending reservation", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-live-reservation-"));
|
|
const stateFile = path.join(root, "runtime.json");
|
|
const arbiterFile = path.join(root, "arbiter.json");
|
|
const arbiterA = new DreaminaCliArbiter({ stateFile: arbiterFile, pollMs: 5 });
|
|
const arbiterB = new DreaminaCliArbiter({ stateFile: arbiterFile, pollMs: 5 });
|
|
const blocker = await arbiterB.acquire();
|
|
let submitCalls = 0;
|
|
const runtimeA = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
arbiter: arbiterA,
|
|
ensureReady: async () => undefined,
|
|
discover: async () => installation,
|
|
maxPollAttempts: 1,
|
|
runProcess: async (input) => {
|
|
if (input.args[0] === "text2video") {
|
|
submitCalls += 1;
|
|
input.onSpawn?.(4242);
|
|
return { exitCode: 0, stdout: '{"submit_id":"receipt-live-reservation"}', stderr: "" };
|
|
}
|
|
return { exitCode: 0, stdout: '{"gen_status":"failed"}', stderr: "" };
|
|
},
|
|
});
|
|
const runtimeB = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
arbiter: arbiterB,
|
|
ensureReady: async () => undefined,
|
|
discover: async () => installation,
|
|
maxPollAttempts: 1,
|
|
runProcess: async () => { throw new Error("runtime B must not invoke provider"); },
|
|
});
|
|
try {
|
|
const pending = runtimeA.enqueue({
|
|
operation: "text2video",
|
|
idempotencyKey: "dreamina-live-reservation-0001",
|
|
prompt: "fixture",
|
|
modelVersion: "seedance2.0mini",
|
|
ratio: "16:9",
|
|
videoResolution: "720p",
|
|
duration: 4,
|
|
});
|
|
await waitForAsync(async () => {
|
|
const disk = await readJournalRecord(stateFile, "dreamina-live-reservation-0001");
|
|
return disk?.state === "pending";
|
|
});
|
|
|
|
const seenByB = await runtimeB.listTasks();
|
|
assert.equal(seenByB.find((task) => task.id === "dreamina-live-reservation-0001")?.stage, "submitting");
|
|
const afterBLoad = await readJournalRecord(stateFile, "dreamina-live-reservation-0001");
|
|
assert.equal(afterBLoad?.state, "pending");
|
|
assert.equal(typeof afterBLoad?.reservationId, "string");
|
|
assert.equal(typeof afterBLoad?.reservationExpiresAt, "string");
|
|
|
|
await blocker.release();
|
|
const accepted = await pending;
|
|
assert.equal(accepted.receiptRecorded, true);
|
|
assert.equal(submitCalls, 1);
|
|
} finally {
|
|
await blocker.release();
|
|
await Promise.allSettled([runtimeA.dispose(), runtimeB.dispose()]);
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("an expired pending reservation becomes submission-uncertain and is never resubmitted", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-expired-reservation-"));
|
|
const stateFile = path.join(root, "runtime.json");
|
|
await fs.writeFile(stateFile, JSON.stringify({
|
|
version: 1,
|
|
records: [{
|
|
ownerId,
|
|
idempotencyKey: "dreamina-expired-reservation-0001",
|
|
requestHash: "d".repeat(64),
|
|
state: "pending",
|
|
reservationId: "11111111-1111-4111-8111-111111111111",
|
|
reservationExpiresAt: "2026-08-12T23:59:00.000Z",
|
|
updatedAt: "2026-08-12T23:58:00.000Z",
|
|
taskVersion: 1,
|
|
operation: "text2video",
|
|
mode: "video",
|
|
model: "seedance2.0mini",
|
|
createdAt: "2026-08-12T23:58:00.000Z"
|
|
}],
|
|
}));
|
|
let submitCalls = 0;
|
|
const runtime = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
ensureReady: async () => undefined,
|
|
discover: async () => installation,
|
|
now: () => new Date("2026-08-13T00:00:00.000Z"),
|
|
maxPollAttempts: 1,
|
|
runProcess: async (input) => {
|
|
if (input.args[0] === "text2video") submitCalls += 1;
|
|
return { exitCode: 0, stdout: '{"submit_id":"receipt-must-not-replay"}', stderr: "" };
|
|
},
|
|
});
|
|
try {
|
|
const recovered = await runtime.getTask("dreamina-expired-reservation-0001");
|
|
assert.equal(recovered.stage, "submission_unknown");
|
|
assert.equal(submitCalls, 0);
|
|
const sameRequest = await runtime.enqueue({
|
|
operation: "text2video",
|
|
idempotencyKey: "dreamina-expired-reservation-0001",
|
|
prompt: "fixture",
|
|
modelVersion: "seedance2.0mini",
|
|
ratio: "16:9",
|
|
videoResolution: "720p",
|
|
duration: 4,
|
|
}, { requestFingerprint: "d".repeat(64) });
|
|
assert.equal(sameRequest.stage, "submission_unknown");
|
|
assert.equal(sameRequest.receiptRecorded, false);
|
|
assert.equal(submitCalls, 0);
|
|
} finally {
|
|
await runtime.dispose();
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("a live reservation heartbeat survives beyond its TTL while waiting for the CLI arbiter", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-reservation-heartbeat-"));
|
|
const stateFile = path.join(root, "runtime.json");
|
|
const arbiterFile = path.join(root, "arbiter.json");
|
|
const blockerArbiter = new DreaminaCliArbiter({ stateFile: arbiterFile, pollMs: 1 });
|
|
const runtimeArbiter = new DreaminaCliArbiter({ stateFile: arbiterFile, pollMs: 1 });
|
|
const observerArbiter = new DreaminaCliArbiter({ stateFile: arbiterFile, pollMs: 1 });
|
|
const blocker = await blockerArbiter.acquire();
|
|
let submitCalls = 0;
|
|
const runtimeA = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
arbiter: runtimeArbiter,
|
|
reservationLeaseMs: 500,
|
|
reservationHeartbeatMs: 50,
|
|
ensureReady: async () => undefined,
|
|
discover: async () => installation,
|
|
maxPollAttempts: 1,
|
|
runProcess: async (input) => {
|
|
if (input.args[0] === "text2video") {
|
|
submitCalls += 1;
|
|
input.onSpawn?.(4242);
|
|
return { exitCode: 0, stdout: '{"submit_id":"receipt-heartbeat"}', stderr: "" };
|
|
}
|
|
return { exitCode: 0, stdout: '{"gen_status":"failed"}', stderr: "" };
|
|
},
|
|
});
|
|
const runtimeB = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
arbiter: observerArbiter,
|
|
reservationLeaseMs: 500,
|
|
reservationHeartbeatMs: 50,
|
|
ensureReady: async () => undefined,
|
|
discover: async () => installation,
|
|
runProcess: async () => { throw new Error("observer must not invoke provider"); },
|
|
});
|
|
try {
|
|
const pending = runtimeA.enqueue({
|
|
operation: "text2video",
|
|
idempotencyKey: "dreamina-heartbeat-reservation-0001",
|
|
prompt: "fixture",
|
|
modelVersion: "seedance2.0mini",
|
|
ratio: "16:9",
|
|
videoResolution: "720p",
|
|
duration: 4,
|
|
});
|
|
await waitForAsync(async () => (await readJournalRecord(stateFile, "dreamina-heartbeat-reservation-0001"))?.state === "pending");
|
|
const first = await readJournalRecord(stateFile, "dreamina-heartbeat-reservation-0001");
|
|
const firstExpiry = Date.parse(String(first?.reservationExpiresAt));
|
|
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
const observed = await runtimeB.listTasks();
|
|
const after = await readJournalRecord(stateFile, "dreamina-heartbeat-reservation-0001");
|
|
assert.equal(observed.find((task) => task.id === "dreamina-heartbeat-reservation-0001")?.stage, "submitting");
|
|
assert.equal(after?.state, "pending");
|
|
assert.ok(Date.parse(String(after?.reservationExpiresAt)) > firstExpiry);
|
|
assert.equal(submitCalls, 0);
|
|
await blocker.release();
|
|
assert.equal((await pending).receiptRecorded, true);
|
|
assert.equal(submitCalls, 1);
|
|
} finally {
|
|
await blocker.release();
|
|
await Promise.allSettled([runtimeA.dispose(), runtimeB.dispose()]);
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("an expired pre-spawn reservation releases its slot and fences the former owner before spawn", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-reservation-pre-spawn-expiry-"));
|
|
const stateFile = path.join(root, "runtime.json");
|
|
const arbiterFile = path.join(root, "arbiter.json");
|
|
const blockerArbiter = new DreaminaCliArbiter({ stateFile: arbiterFile, pollMs: 1 });
|
|
const blocker = await blockerArbiter.acquire();
|
|
let submitCalls = 0;
|
|
const runtimeA = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
arbiter: new DreaminaCliArbiter({ stateFile: arbiterFile, pollMs: 1 }),
|
|
reservationLeaseMs: 100,
|
|
reservationHeartbeatMs: 0,
|
|
ensureReady: async () => undefined,
|
|
discover: async () => installation,
|
|
runProcess: async (input) => {
|
|
if (input.args[0] === "text2video") submitCalls += 1;
|
|
input.onSpawn?.(4242);
|
|
return { exitCode: 0, stdout: '{"submit_id":"receipt-must-not-spawn"}', stderr: "" };
|
|
},
|
|
});
|
|
const runtimeB = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
arbiter: new DreaminaCliArbiter({ stateFile: arbiterFile, pollMs: 1 }),
|
|
reservationLeaseMs: 100,
|
|
reservationHeartbeatMs: 0,
|
|
ensureReady: async () => undefined,
|
|
discover: async () => installation,
|
|
runProcess: async () => { throw new Error("recovery runtime must not invoke provider"); },
|
|
});
|
|
try {
|
|
const attempt = runtimeA.enqueue({
|
|
operation: "text2video",
|
|
idempotencyKey: "dreamina-expired-reserved-0001",
|
|
prompt: "fixture",
|
|
modelVersion: "seedance2.0mini",
|
|
ratio: "16:9",
|
|
videoResolution: "720p",
|
|
duration: 4,
|
|
});
|
|
await waitForAsync(async () => (await readJournalRecord(stateFile, "dreamina-expired-reserved-0001"))?.state === "pending");
|
|
await new Promise((resolve) => setTimeout(resolve, 140));
|
|
const recovered = await runtimeB.getTask("dreamina-expired-reserved-0001");
|
|
assert.equal(recovered.status, "failed");
|
|
assert.equal(recovered.errorCode, "dreamina_interrupted_before_submission");
|
|
await blocker.release();
|
|
await assert.rejects(attempt, (error: unknown) => error instanceof DreaminaCliError && error.code === "dreamina_cli_fenced");
|
|
assert.equal(submitCalls, 0);
|
|
} finally {
|
|
await blocker.release();
|
|
await Promise.allSettled([runtimeA.dispose(), runtimeB.dispose()]);
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("an expired spawn-permitted reservation becomes submission-uncertain and keeps its slot", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-reservation-post-fence-expiry-"));
|
|
const stateFile = path.join(root, "runtime.json");
|
|
await fs.writeFile(stateFile, JSON.stringify({
|
|
version: 1,
|
|
records: [{
|
|
ownerId,
|
|
idempotencyKey: "dreamina-expired-spawn-permitted-0001",
|
|
requestHash: "e".repeat(64),
|
|
state: "pending",
|
|
reservationId: "22222222-2222-4222-8222-222222222222",
|
|
reservationOwnerId: "33333333-3333-4333-8333-333333333333",
|
|
reservationExpiresAt: "2026-08-12T23:59:00.000Z",
|
|
submissionPhase: "spawn_permitted",
|
|
fenceEpoch: 7,
|
|
updatedAt: "2026-08-12T23:58:00.000Z",
|
|
taskVersion: 1,
|
|
operation: "text2video",
|
|
mode: "video",
|
|
model: "seedance2.0mini",
|
|
createdAt: "2026-08-12T23:58:00.000Z"
|
|
}],
|
|
}));
|
|
let submitCalls = 0;
|
|
const runtime = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
now: () => new Date("2026-08-13T00:00:00.000Z"),
|
|
ensureReady: async () => undefined,
|
|
discover: async () => installation,
|
|
runProcess: async (input) => {
|
|
if (input.args[0] === "text2video") submitCalls += 1;
|
|
return { exitCode: 0, stdout: '{"submit_id":"receipt-must-not-replay"}', stderr: "" };
|
|
},
|
|
});
|
|
try {
|
|
const recovered = await runtime.getTask("dreamina-expired-spawn-permitted-0001");
|
|
assert.equal(recovered.stage, "submission_unknown");
|
|
assert.equal(submitCalls, 0);
|
|
const same = await runtime.enqueue({
|
|
operation: "text2video",
|
|
idempotencyKey: "dreamina-expired-spawn-permitted-0001",
|
|
prompt: "fixture",
|
|
modelVersion: "seedance2.0mini",
|
|
ratio: "16:9",
|
|
videoResolution: "720p",
|
|
duration: 4,
|
|
}, { requestFingerprint: "e".repeat(64) });
|
|
assert.equal(same.stage, "submission_unknown");
|
|
assert.equal(submitCalls, 0);
|
|
} finally {
|
|
await runtime.dispose();
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("legacy run cannot cross the provider boundary while five durable slots are occupied", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-legacy-five-slots-"));
|
|
const stateFile = path.join(root, "runtime.json");
|
|
await fs.writeFile(stateFile, JSON.stringify({
|
|
version: 1,
|
|
records: Array.from({ length: 5 }, (_, index) => ({
|
|
ownerId,
|
|
idempotencyKey: `dreamina-legacy-capacity-existing-${index + 1}`,
|
|
requestHash: String(index + 1).repeat(64),
|
|
state: index === 0 ? "accepted" : "unknown",
|
|
...(index === 0 ? { submitId: "receipt-capacity-existing" } : { errorCode: "dreamina_submission_unknown" }),
|
|
updatedAt: `2026-08-13T00:0${index}:00.000Z`,
|
|
taskVersion: 1,
|
|
operation: "text2video",
|
|
mode: "video",
|
|
model: "seedance2.0mini",
|
|
createdAt: `2026-08-13T00:0${index}:00.000Z`
|
|
})),
|
|
}));
|
|
let providerCalls = 0;
|
|
const runtime = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
ensureReady: async () => undefined,
|
|
discover: async () => installation,
|
|
runProcess: async () => {
|
|
providerCalls += 1;
|
|
return { exitCode: 0, stdout: '{"submit_id":"receipt-legacy-over-capacity"}', stderr: "" };
|
|
},
|
|
});
|
|
try {
|
|
await assert.rejects(runtime.run({
|
|
operation: "text2image",
|
|
idempotencyKey: "dreamina-legacy-capacity-new-0001",
|
|
prompt: "fixture",
|
|
resolutionType: "2k",
|
|
}), (error: unknown) => error instanceof DreaminaCliError && error.code === "dreamina_generation_capacity_full");
|
|
assert.equal(providerCalls, 0);
|
|
} finally {
|
|
await runtime.dispose();
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("two Runtime instances reserve at most five durable generation slots", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-slots-"));
|
|
const stateFile = path.join(root, "runtime.json");
|
|
const arbiterFile = path.join(root, "arbiter.json");
|
|
const arbiterA = new DreaminaCliArbiter({ stateFile: arbiterFile, pollMs: 5 });
|
|
const arbiterB = new DreaminaCliArbiter({ stateFile: arbiterFile, pollMs: 5 });
|
|
await fs.writeFile(stateFile, JSON.stringify({
|
|
version: 1,
|
|
records: Array.from({ length: 4 }, (_, index) => ({
|
|
ownerId,
|
|
idempotencyKey: `dreamina-existing-slot-${index + 1}`,
|
|
requestHash: String(index + 1).repeat(64),
|
|
state: "unknown",
|
|
updatedAt: `2026-08-13T00:0${index}:00.000Z`,
|
|
taskVersion: 1,
|
|
operation: "text2video",
|
|
mode: "video",
|
|
model: "seedance2.0mini",
|
|
createdAt: `2026-08-13T00:0${index}:00.000Z`,
|
|
errorCode: "dreamina_submission_unknown"
|
|
})),
|
|
}));
|
|
const submitted: string[] = [];
|
|
let releaseQuery!: () => void;
|
|
const queryGate = new Promise<void>((resolve) => { releaseQuery = resolve; });
|
|
const runProcess = async (input: { args: string[]; onSpawn?: (pid: number) => void }) => {
|
|
if (input.args[0] === "text2video") {
|
|
const prompt = input.args.find((arg) => arg.startsWith("--prompt="))!.slice("--prompt=".length);
|
|
submitted.push(prompt);
|
|
input.onSpawn?.(4242);
|
|
return { exitCode: 0, stdout: JSON.stringify({ submit_id: `receipt-slot-${prompt}` }), stderr: "" };
|
|
}
|
|
return { exitCode: 0, stdout: '{"gen_status":"processing"}', stderr: "" };
|
|
};
|
|
const makeRuntime = (arbiter: DreaminaCliArbiter) => new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
arbiter,
|
|
ensureReady: async () => undefined,
|
|
discover: async () => installation,
|
|
maxActiveTasks: 5,
|
|
maxPollAttempts: 2,
|
|
sleep: async () => { await queryGate; },
|
|
runProcess,
|
|
});
|
|
const runtimeA = makeRuntime(arbiterA);
|
|
const runtimeB = makeRuntime(arbiterB);
|
|
try {
|
|
const calls = [runtimeA, runtimeB].map((runtime, index) => runtime.enqueue({
|
|
operation: "text2video",
|
|
idempotencyKey: `dreamina-cross-slot-${index + 5}`,
|
|
prompt: `task-${index + 5}`,
|
|
modelVersion: "seedance2.0mini",
|
|
ratio: "16:9",
|
|
videoResolution: "720p",
|
|
duration: 4,
|
|
}));
|
|
const settled = await Promise.allSettled(calls);
|
|
const outcomes = settled.map((result) => result.status === "fulfilled"
|
|
? "fulfilled"
|
|
: `rejected:${(result.reason as { code?: string })?.code ?? String(result.reason)}`);
|
|
const diagnosticDisk = JSON.parse(await fs.readFile(stateFile, "utf8")) as { records: Array<Record<string, unknown>> };
|
|
const diagnosticRecords = diagnosticDisk.records
|
|
.filter((record) => String(record.idempotencyKey).startsWith("dreamina-cross-slot-"))
|
|
.map((record) => ({
|
|
id: record.idempotencyKey,
|
|
state: record.state,
|
|
fenceEpoch: record.fenceEpoch,
|
|
journalVersion: record.journalVersion,
|
|
hasReceipt: record.submitId !== undefined,
|
|
}));
|
|
assert.deepEqual(outcomes, ["fulfilled", "fulfilled"], JSON.stringify({ outcomes, diagnosticRecords }));
|
|
const tasks = settled.map((result) => {
|
|
if (result.status !== "fulfilled") throw result.reason;
|
|
return result.value;
|
|
});
|
|
assert.equal(submitted.length, 1);
|
|
assert.equal(tasks.filter((task) => task.status === "running").length, 1);
|
|
assert.equal(tasks.filter((task) => task.status === "queued").length, 1);
|
|
const disk = JSON.parse(await fs.readFile(stateFile, "utf8")) as { records: Array<Record<string, unknown>> };
|
|
const submittedRecord = disk.records.find((record) => record.submitId !== undefined);
|
|
const queuedRecord = disk.records.find((record) => String(record.idempotencyKey).startsWith("dreamina-cross-slot-") && record.state === "queued");
|
|
assert.equal(submittedRecord?.state, "accepted");
|
|
assert.equal(typeof submittedRecord?.fenceEpoch, "number");
|
|
assert.equal(queuedRecord?.fenceEpoch, undefined);
|
|
assert.equal(typeof queuedRecord?.queueOwnerId, "string");
|
|
assert.equal(Number.isFinite(Date.parse(String(queuedRecord?.queueExpiresAt))), true);
|
|
} finally {
|
|
releaseQuery();
|
|
await Promise.allSettled([runtimeA.dispose(), runtimeB.dispose()]);
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("different tasks may commit sequential increasing fence epochs without fencing each other", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-sequential-epochs-"));
|
|
const stateFile = path.join(root, "runtime.json");
|
|
const arbiterFile = path.join(root, "arbiter.json");
|
|
const makeRuntime = () => new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
arbiter: new DreaminaCliArbiter({ stateFile: arbiterFile, pollMs: 5 }),
|
|
ensureReady: async () => undefined,
|
|
discover: async () => installation,
|
|
maxPollAttempts: 1,
|
|
runProcess: async (input) => {
|
|
if (input.args[0] === "text2image") {
|
|
const prompt = input.args.find((arg) => arg.startsWith("--prompt="))!.slice("--prompt=".length);
|
|
input.onSpawn?.(4242);
|
|
return { exitCode: 0, stdout: JSON.stringify({ submit_id: `receipt-epoch-${prompt}` }), stderr: "" };
|
|
}
|
|
return { exitCode: 0, stdout: '{"gen_status":"failed"}', stderr: "" };
|
|
},
|
|
});
|
|
const runtimeA = makeRuntime();
|
|
const runtimeB = makeRuntime();
|
|
try {
|
|
await Promise.all([
|
|
runtimeA.run({ operation: "text2image", idempotencyKey: "dreamina-epoch-task-0001", prompt: "one", resolutionType: "2k" }),
|
|
runtimeB.run({ operation: "text2image", idempotencyKey: "dreamina-epoch-task-0002", prompt: "two", resolutionType: "2k" }),
|
|
]);
|
|
const disk = JSON.parse(await fs.readFile(stateFile, "utf8")) as { records: Array<Record<string, unknown>> };
|
|
const records = disk.records.filter((record) => String(record.idempotencyKey).startsWith("dreamina-epoch-task-"));
|
|
assert.equal(records.length, 2);
|
|
assert.equal(records.every((record) => record.state === "accepted" && typeof record.fenceEpoch === "number"), true);
|
|
const epochs = records.map((record) => record.fenceEpoch as number).sort((left, right) => left - right);
|
|
assert.equal(epochs[1], epochs[0]! + 1);
|
|
} finally {
|
|
await Promise.allSettled([runtimeA.dispose(), runtimeB.dispose()]);
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("five durable submission-uncertain records retain all slots after Runtime restart", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-uncertain-slots-"));
|
|
const stateFile = path.join(root, "runtime.json");
|
|
const arbiter = new DreaminaCliArbiter({ stateFile: path.join(root, "arbiter.json"), pollMs: 5 });
|
|
await fs.writeFile(stateFile, JSON.stringify({
|
|
version: 1,
|
|
records: Array.from({ length: 5 }, (_, index) => ({
|
|
ownerId,
|
|
idempotencyKey: `dreamina-uncertain-slot-${index + 1}`,
|
|
requestHash: String(index + 1).repeat(64),
|
|
state: "unknown",
|
|
updatedAt: `2026-08-13T00:0${index}:00.000Z`,
|
|
taskVersion: 1,
|
|
operation: "text2video",
|
|
mode: "video",
|
|
model: "seedance2.0mini",
|
|
createdAt: `2026-08-13T00:0${index}:00.000Z`,
|
|
errorCode: "dreamina_submission_unknown"
|
|
})),
|
|
}));
|
|
let submitCalls = 0;
|
|
const runtime = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
arbiter,
|
|
ensureReady: async () => undefined,
|
|
discover: async () => installation,
|
|
maxActiveTasks: 5,
|
|
maxPollAttempts: 1,
|
|
runProcess: async (input) => {
|
|
if (input.args[0] === "text2video") {
|
|
submitCalls += 1;
|
|
input.onSpawn?.(4242);
|
|
return { exitCode: 0, stdout: '{"submit_id":"receipt-should-not-submit"}', stderr: "" };
|
|
}
|
|
return { exitCode: 0, stdout: '{"gen_status":"processing"}', stderr: "" };
|
|
},
|
|
});
|
|
try {
|
|
const sixth = await runtime.enqueue({
|
|
operation: "text2video",
|
|
idempotencyKey: "dreamina-uncertain-slot-6",
|
|
prompt: "sixth",
|
|
modelVersion: "seedance2.0mini",
|
|
ratio: "16:9",
|
|
videoResolution: "720p",
|
|
duration: 4,
|
|
});
|
|
assert.equal(sixth.status, "queued");
|
|
assert.equal(submitCalls, 0);
|
|
assert.equal((await runtime.listTasks()).filter((task) => task.stage === "submission_unknown").length, 5);
|
|
} finally {
|
|
await runtime.dispose();
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
async function readArbiterQueueLength(stateFile: string) {
|
|
try {
|
|
const state = JSON.parse(await fs.readFile(stateFile, "utf8")) as { queue?: unknown[] };
|
|
return state.queue?.length ?? 0;
|
|
} catch (error) {
|
|
if ((error as NodeJS.ErrnoException).code === "ENOENT") return 0;
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function promiseBeforeAbort<T>(promise: Promise<T>, controller: AbortController, timeoutMs: number) {
|
|
const timer = globalThis.setTimeout(() => controller.abort(), timeoutMs);
|
|
try {
|
|
return await promise;
|
|
} finally {
|
|
globalThis.clearTimeout(timer);
|
|
}
|
|
}
|
|
|
|
async function exists(target: string) {
|
|
try {
|
|
await fs.access(target);
|
|
return true;
|
|
} catch (error) {
|
|
if ((error as NodeJS.ErrnoException).code === "ENOENT") return false;
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function readJournalRecord(stateFile: string, idempotencyKey: string) {
|
|
try {
|
|
const disk = JSON.parse(await fs.readFile(stateFile, "utf8")) as { records?: Array<Record<string, unknown>> };
|
|
return disk.records?.find((record) => record.idempotencyKey === idempotencyKey);
|
|
} catch (error) {
|
|
if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
test("legacy run persists its receipt before releasing the invocation lease", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-legacy-receipt-release-"));
|
|
const stateFile = path.join(root, "runtime.json");
|
|
const arbiter = new DreaminaCliArbiter({ stateFile: path.join(root, "arbiter.json"), pollMs: 1 });
|
|
const originalAcquire = arbiter.acquire.bind(arbiter);
|
|
let stateAtRelease: string | undefined;
|
|
(arbiter as unknown as { acquire: DreaminaCliArbiter["acquire"] }).acquire = async (options = {}) => {
|
|
const lease = await originalAcquire(options);
|
|
const originalRelease = lease.release.bind(lease);
|
|
lease.release = async () => {
|
|
const disk = JSON.parse(await fs.readFile(stateFile, "utf8")) as { records: Array<Record<string, unknown>> };
|
|
stateAtRelease = String(disk.records[0]?.state);
|
|
await originalRelease();
|
|
};
|
|
return lease;
|
|
};
|
|
const runtime = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
arbiter,
|
|
ensureReady: async () => undefined,
|
|
discover: async () => installation,
|
|
runProcess: async (input) => {
|
|
input.onSpawn?.(4242);
|
|
return { exitCode: 0, stdout: '{"submit_id":"receipt-legacy-before-release"}', stderr: "" };
|
|
},
|
|
});
|
|
try {
|
|
await runtime.run({
|
|
operation: "text2image",
|
|
idempotencyKey: "dreamina-legacy-before-release-0001",
|
|
prompt: "fixture",
|
|
resolutionType: "2k",
|
|
});
|
|
assert.equal(stateAtRelease, "accepted");
|
|
} finally {
|
|
await runtime.dispose();
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("legacy run cannot commit a receipt after its released lease has a successor", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-legacy-successor-"));
|
|
const stateFile = path.join(root, "runtime.json");
|
|
const arbiterFile = path.join(root, "arbiter.json");
|
|
const arbiter = new DreaminaCliArbiter({ stateFile: arbiterFile, pollMs: 1 });
|
|
const successorArbiter = new DreaminaCliArbiter({ stateFile: arbiterFile, pollMs: 1 });
|
|
const originalAcquire = arbiter.acquire.bind(arbiter);
|
|
let successor: Awaited<ReturnType<DreaminaCliArbiter["acquire"]>> | undefined;
|
|
let stateWhenSuccessorAcquired: string | undefined;
|
|
(arbiter as unknown as { acquire: DreaminaCliArbiter["acquire"] }).acquire = async (options = {}) => {
|
|
const lease = await originalAcquire(options);
|
|
const originalRelease = lease.release.bind(lease);
|
|
lease.release = async () => {
|
|
await originalRelease();
|
|
successor = await successorArbiter.acquire();
|
|
const disk = JSON.parse(await fs.readFile(stateFile, "utf8")) as { records: Array<Record<string, unknown>> };
|
|
stateWhenSuccessorAcquired = String(disk.records[0]?.state);
|
|
};
|
|
return lease;
|
|
};
|
|
const runtime = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
arbiter,
|
|
ensureReady: async () => undefined,
|
|
discover: async () => installation,
|
|
runProcess: async (input) => {
|
|
input.onSpawn?.(4242);
|
|
return { exitCode: 0, stdout: '{"submit_id":"receipt-legacy-successor"}', stderr: "" };
|
|
},
|
|
});
|
|
try {
|
|
await runtime.run({
|
|
operation: "text2image",
|
|
idempotencyKey: "dreamina-legacy-successor-0001",
|
|
prompt: "fixture",
|
|
resolutionType: "2k",
|
|
});
|
|
assert.equal(stateWhenSuccessorAcquired, "accepted");
|
|
} finally {
|
|
await successor?.release();
|
|
await runtime.dispose();
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("accepted provider work persists its account binding and never exposes it in the public task DTO", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-task-binding-"));
|
|
const stateFile = path.join(root, "runtime.json");
|
|
const arbiter = new DreaminaCliArbiter({ stateFile: path.join(root, "arbiter.json"), pollMs: 1 });
|
|
const accountA = "a".repeat(64);
|
|
const lease = await arbiter.acquire();
|
|
const sessionA = await arbiter.commitSession(lease, accountA);
|
|
await lease.release();
|
|
let releaseQuery!: () => void;
|
|
const queryGate = new Promise<void>((resolve) => { releaseQuery = resolve; });
|
|
const runtime = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
arbiter,
|
|
ensureReady: async () => arbiter.readSession(),
|
|
discover: async () => installation,
|
|
maxPollAttempts: 1,
|
|
runProcess: async (input) => {
|
|
if (input.args[0] === "text2video") {
|
|
input.onSpawn?.(4242);
|
|
return { exitCode: 0, stdout: '{"submit_id":"receipt-account-bound-A"}', stderr: "" };
|
|
}
|
|
await queryGate;
|
|
return { exitCode: 0, stdout: '{"gen_status":"processing"}', stderr: "" };
|
|
},
|
|
});
|
|
try {
|
|
const task = await runtime.enqueue({
|
|
operation: "text2video",
|
|
idempotencyKey: "dreamina-account-bound-submit-0001",
|
|
prompt: "fixture",
|
|
modelVersion: "seedance2.0mini",
|
|
ratio: "16:9",
|
|
videoResolution: "720p",
|
|
duration: 4,
|
|
});
|
|
const disk = JSON.parse(await fs.readFile(stateFile, "utf8")) as { records: Array<Record<string, unknown>> };
|
|
const durable = disk.records.find((record) => record.idempotencyKey === "dreamina-account-bound-submit-0001");
|
|
assert.equal(durable?.state, "accepted");
|
|
assert.equal(durable?.accountBinding, accountA);
|
|
assert.equal(durable?.sessionEpoch, sessionA.sessionEpoch);
|
|
assert.equal(JSON.stringify(task).includes(accountA), false);
|
|
assert.equal("accountBinding" in task, false);
|
|
} finally {
|
|
releaseQuery();
|
|
await runtime.dispose();
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("accepted work is queried only under its bound account across manual refresh and restart", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-bound-recovery-"));
|
|
const stateFile = path.join(root, "runtime.json");
|
|
const arbiter = new DreaminaCliArbiter({ stateFile: path.join(root, "arbiter.json"), pollMs: 1 });
|
|
const accountA = "a".repeat(64);
|
|
const accountB = "b".repeat(64);
|
|
let lease = await arbiter.acquire();
|
|
const sessionA = await arbiter.commitSession(lease, accountA);
|
|
await lease.release();
|
|
await fs.writeFile(stateFile, JSON.stringify({
|
|
version: 1,
|
|
records: [{
|
|
ownerId,
|
|
idempotencyKey: "dreamina-bound-recovery-0001",
|
|
requestHash: "c".repeat(64),
|
|
state: "accepted",
|
|
submitId: "same-provider-task-id",
|
|
accountBinding: accountA,
|
|
sessionEpoch: sessionA.sessionEpoch,
|
|
updatedAt: "2026-08-13T00:00:00.000Z",
|
|
taskVersion: 1,
|
|
operation: "text2video",
|
|
mode: "video",
|
|
model: "seedance2.0mini",
|
|
createdAt: "2026-08-13T00:00:00.000Z"
|
|
}],
|
|
}));
|
|
lease = await arbiter.acquire();
|
|
await arbiter.commitSession(lease, accountB);
|
|
await lease.release();
|
|
let queryCalls = 0;
|
|
const makeRuntime = () => new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
arbiter,
|
|
ensureReady: async () => arbiter.readSession(),
|
|
discover: async () => installation,
|
|
maxPollAttempts: 1,
|
|
runProcess: async () => {
|
|
queryCalls += 1;
|
|
return { exitCode: 0, stdout: '{"gen_status":"processing"}', stderr: "" };
|
|
},
|
|
});
|
|
const manual = makeRuntime();
|
|
try {
|
|
await assert.rejects(manual.waitForTask("dreamina-bound-recovery-0001", "video"), (error: unknown) => (
|
|
error instanceof DreaminaCliError && error.code === "dreamina_account_session_changed"
|
|
));
|
|
assert.equal(queryCalls, 0);
|
|
await manual.dispose();
|
|
|
|
const restarted = makeRuntime();
|
|
await restarted.getTask("dreamina-bound-recovery-0001");
|
|
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
assert.equal(queryCalls, 0);
|
|
await restarted.dispose();
|
|
|
|
lease = await arbiter.acquire();
|
|
await arbiter.commitSession(lease, accountA);
|
|
await lease.release();
|
|
const restored = makeRuntime();
|
|
await restored.refreshTask("dreamina-bound-recovery-0001");
|
|
await waitFor(() => queryCalls === 1);
|
|
assert.equal(queryCalls, 1);
|
|
await restored.dispose();
|
|
} finally {
|
|
await manual.dispose();
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("the same provider task id remains isolated by account binding", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-same-provider-id-"));
|
|
const stateFile = path.join(root, "runtime.json");
|
|
const arbiter = new DreaminaCliArbiter({ stateFile: path.join(root, "arbiter.json"), pollMs: 1 });
|
|
const accountA = "a".repeat(64);
|
|
const accountB = "b".repeat(64);
|
|
let lease = await arbiter.acquire();
|
|
const sessionA = await arbiter.commitSession(lease, accountA);
|
|
await lease.release();
|
|
await fs.writeFile(stateFile, JSON.stringify({
|
|
version: 1,
|
|
records: [accountA, accountB].map((accountBinding, index) => ({
|
|
ownerId,
|
|
idempotencyKey: `dreamina-same-provider-id-${index + 1}`,
|
|
requestHash: String(index + 1).repeat(64),
|
|
state: "accepted",
|
|
submitId: "shared-provider-task-id",
|
|
accountBinding,
|
|
sessionEpoch: sessionA.sessionEpoch,
|
|
updatedAt: "2026-08-13T00:00:00.000Z",
|
|
taskVersion: 1,
|
|
operation: "text2video",
|
|
mode: "video",
|
|
model: "seedance2.0mini",
|
|
createdAt: "2026-08-13T00:00:00.000Z"
|
|
})),
|
|
}));
|
|
let queryCalls = 0;
|
|
const runtime = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
arbiter,
|
|
ensureReady: async () => arbiter.readSession(),
|
|
discover: async () => installation,
|
|
runProcess: async () => {
|
|
queryCalls += 1;
|
|
return { exitCode: 0, stdout: '{"gen_status":"processing"}', stderr: "" };
|
|
},
|
|
});
|
|
try {
|
|
await runtime.refreshTask("dreamina-same-provider-id-1");
|
|
await waitFor(() => queryCalls === 1);
|
|
assert.equal(queryCalls, 1);
|
|
await assert.rejects(runtime.waitForTask("dreamina-same-provider-id-2", "video"), (error: unknown) => (
|
|
error instanceof DreaminaCliError && error.code === "dreamina_account_session_changed"
|
|
));
|
|
assert.equal(queryCalls, 1);
|
|
|
|
lease = await arbiter.acquire();
|
|
await arbiter.commitSession(lease, accountB);
|
|
await lease.release();
|
|
await runtime.refreshTask("dreamina-same-provider-id-2");
|
|
await waitFor(() => queryCalls === 2);
|
|
assert.equal(queryCalls, 2);
|
|
} finally {
|
|
await runtime.dispose();
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("legacy accepted work without an account binding fails closed before any provider query", async () => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "dreamina-arbiter-unbound-accepted-"));
|
|
const stateFile = path.join(root, "runtime.json");
|
|
const arbiter = new DreaminaCliArbiter({ stateFile: path.join(root, "arbiter.json"), pollMs: 1 });
|
|
await fs.writeFile(stateFile, JSON.stringify({
|
|
version: 1,
|
|
records: [{
|
|
ownerId,
|
|
idempotencyKey: "dreamina-unbound-accepted-0001",
|
|
requestHash: "d".repeat(64),
|
|
state: "accepted",
|
|
submitId: "same-provider-task-id",
|
|
updatedAt: "2026-08-13T00:00:00.000Z",
|
|
taskVersion: 1,
|
|
operation: "text2video",
|
|
mode: "video",
|
|
model: "seedance2.0mini",
|
|
createdAt: "2026-08-13T00:00:00.000Z"
|
|
}],
|
|
}));
|
|
let queryCalls = 0;
|
|
const runtime = new DreaminaCliRuntime({
|
|
ownerId,
|
|
stateFile,
|
|
arbiter,
|
|
ensureReady: async () => arbiter.readSession(),
|
|
discover: async () => installation,
|
|
runProcess: async () => {
|
|
queryCalls += 1;
|
|
return { exitCode: 0, stdout: '{"gen_status":"processing"}', stderr: "" };
|
|
},
|
|
});
|
|
try {
|
|
await assert.rejects(runtime.waitForTask("dreamina-unbound-accepted-0001", "video"), (error: unknown) => (
|
|
error instanceof DreaminaCliError && error.code === "dreamina_account_session_changed"
|
|
));
|
|
assert.equal(queryCalls, 0);
|
|
} finally {
|
|
await runtime.dispose();
|
|
await fs.rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
function acceptedRuntimeRecord(idempotencyKey: string, submitId: string, hashDigit: string, nextPollAt: string) {
|
|
return {
|
|
ownerId,
|
|
idempotencyKey,
|
|
requestHash: hashDigit.repeat(64),
|
|
state: "accepted",
|
|
updatedAt: "2026-08-12T00:00:00.000Z",
|
|
submitId,
|
|
taskVersion: 1,
|
|
operation: "text2video",
|
|
mode: "video",
|
|
model: "seedance2.0mini",
|
|
createdAt: "2026-08-12T00:00:00.000Z",
|
|
nextPollAt,
|
|
};
|
|
}
|
|
|
|
async function waitFor(condition: () => boolean) {
|
|
const deadline = Date.now() + 2_000;
|
|
while (!condition()) {
|
|
if (Date.now() >= deadline) throw new Error("timed out waiting for Dreamina arbiter fixture");
|
|
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
}
|
|
}
|
|
|
|
async function waitForAsync(condition: () => Promise<boolean>) {
|
|
const deadline = Date.now() + 2_000;
|
|
while (!(await condition())) {
|
|
if (Date.now() >= deadline) throw new Error("timed out waiting for Dreamina arbiter fixture");
|
|
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
}
|
|
}
|