fix(studio): recover uncertain imports with persistent request identities
CI / check (push) Successful in 3m12s
CI / deploy (push) Successful in 19s
CI / publish-container (push) Successful in 1m12s

This commit is contained in:
Simo committed 2026-09-13 18:33:06 +02:00
1 parent afea80708c
commit c977fe95ba
12 files changed
+365 -31

No files matched your search

+44 -1
View File
@@ -243,11 +243,12 @@ it("attaches recovery files only to safe failed items and preserves retry histor
).rejects.toThrow("latest retry");
});
it("clears old source provenance and phase when creating a retry", async () => {
it("clears old source provenance, request identity and phase when creating a retry", async () => {
const s = await store();
const original: ImportJob = {
...job(),
state: "completed",
requestFingerprint: "original-request",
items: [
{
id: 1,
@@ -271,7 +272,49 @@ it("clears old source provenance and phase when creating a retry", async () => {
};
await s.create(original);
const child = await s.retry(original.id, 4);
expect(child?.requestFingerprint).toBeUndefined();
expect(child?.items[0].phase).toBeUndefined();
expect(child?.items[0].recoveredSource).toBeUndefined();
expect(child?.items[0].sourceAttempt).toBeUndefined();
});
it("rejects reuse of a request ID with a different immutable fingerprint", async () => {
const s = await store();
const original = { ...job(), requestFingerprint: "original" };
await s.create(original);
await expect(
s.create({ ...original, requestFingerprint: "changed" }),
).rejects.toThrow("Request ID already used");
expect((await s.read(original.id)).requestFingerprint).toBe("original");
});
it("preserves the request identity while checkpoint data changes", async () => {
const s = await store();
const original = { ...job(), requestFingerprint: "original" };
await s.create(original);
await s.save({ ...original, state: "completed" });
expect((await s.create(original)).state).toBe("completed");
});
it("does not guess request identity for legacy jobs without a fingerprint", async () => {
const s = await store();
const original = job();
await s.create(original);
await expect(
s.create({ ...original, requestFingerprint: "new" }),
).rejects.toThrow("Request ID already used");
});
it("rejects concurrent conflicting submissions without overwriting the winner", async () => {
const s = await store();
const original = { ...job(), requestFingerprint: "one" };
const results = await Promise.allSettled([
s.create(original),
s.create({ ...original, requestFingerprint: "two" }),
]);
expect(
results.filter((result) => result.status === "fulfilled"),
).toHaveLength(1);
expect(results.filter((result) => result.status === "rejected")).toHaveLength(
1,
);
expect(await s.list()).toHaveLength(1);
});