ML data that does not decrypt is recorded with its reason (closes #198)
check / check (push) Canceled after 0s

`fetchMLDataBatch` dropped an entry that did not decrypt, gunzip or parse,
so the file looked like one with no ML data and the run exited 0. It now
returns the reason beside the payloads. The library keeps the reasons from
its last ML data fetch in memory only and gives them in
`status().mlDataErrors` and the `fetchMLData` "done" event. `quak backup`
writes the reason as `mlDataError` for a file with no ML data cached and
records the file in `failures.json` as `ML data: REASON`. `backup-metadata`
writes `mlDataError` for it and exits 1; `runMetadataBackup` now returns how
many files have `mlDataError`.

Judgement call: a file whose ML data is cached keeps it in its JSON when a
newer copy does not decode, as when the fetch fails.

Model: opus-5-5
This commit is contained in:
2026-10-16 03:42:19 +00:00
parent 960a7ce8a6
commit 375d8ae28a
12 changed files with 332 additions and 84 deletions
+8 -6
View File
@@ -938,13 +938,14 @@ const ML_PAYLOAD = { face: { faces: [] }, clip: { embedding: [0.5, 0.25] } };
class MLClient extends MockClient {
mlError?: string;
async fetchMLData(args: {
fileIDs: number[];
}): Promise<Map<number, MLData>> {
async fetchMLData(args: { fileIDs: number[] }): Promise<{
payloads: Map<number, MLData>;
errors: Map<number, unknown>;
}> {
if (this.mlError) throw new Error(this.mlError);
const result = new Map<number, MLData>();
if (args.fileIDs.includes(100)) result.set(100, ML_PAYLOAD);
return result;
const payloads = new Map<number, MLData>();
if (args.fileIDs.includes(100)) payloads.set(100, ML_PAYLOAD);
return { payloads, errors: new Map() };
}
}
@@ -1517,6 +1518,7 @@ describe("backup album folders", () => {
contentConcurrency: 5,
fetchMLData: async () => {},
mlData: async () => undefined,
mlDataError: () => undefined,
whoami: () => ({ email: "backup@example.com", userID: USER_ID }),
});