Storage: digest sidecars renamed to *.~digest (no collision with research *.sha256 files)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 changed files +12 −2
modified
packages/storage/src/index.ts
+3 −2
@@ -55,8 +55,9 @@ export class FilesystemStorage implements StorageBackend { | ||
| 55 | 55 | return abs; |
| 56 | 56 | } |
| 57 | 57 | |
| 58 | + /** Digest sidecar. The suffix cannot collide with user artifact names (research code often writes `*.sha256`). */ | |
| 58 | 59 | private sidecar(abs: string): string { |
| 59 | − return `${abs}.sha256`; | |
| 60 | + return `${abs}.~digest`; | |
| 60 | 61 | } |
| 61 | 62 | |
| 62 | 63 | async put(key: string, data: Buffer | Readable, expectedSha256?: string): Promise<StoredObject> { |
@@ -155,7 +156,7 @@ export class FilesystemStorage implements StorageBackend { | ||
| 155 | 156 | } |
| 156 | 157 | for (const e of entries) { |
| 157 | 158 | const name = String(e.name); |
| 158 | − if (name.endsWith(".sha256") || name.includes(".tmp-")) continue; | |
| 159 | + if (name.endsWith(".~digest") || name.includes(".tmp-")) continue; | |
| 159 | 160 | const r = rel ? `${rel}/${name}` : name; |
| 160 | 161 | if (e.isDirectory()) await walk(join(dir, name), r); |
| 161 | 162 | else out.push(`${assertSafeKey(prefix)}/${r}`.replace(/^\.\//, "")); |
modified
packages/storage/src/storage.test.ts
+9 −0
@@ -38,6 +38,15 @@ describe("FilesystemStorage", () => { | ||
| 38 | 38 | expect(() => store.absolutePath("runs/x/../../../y")).toThrow(); |
| 39 | 39 | }); |
| 40 | 40 | |
| 41 | + it("accepts artifacts named *.sha256 next to their siblings (no sidecar collision)", async () => { | |
| 42 | + await store.put("c/tool.c", Buffer.from("int main(){}")); | |
| 43 | + const obj = await store.put("c/tool.c.sha256", Buffer.from("abc tool.c\n")); | |
| 44 | + expect(obj.bytes).toBe(12); | |
| 45 | + expect((await store.get("c/tool.c")).toString()).toBe("int main(){}"); | |
| 46 | + expect((await store.head("c/tool.c"))?.sha256).toBe(sha256Hex("int main(){}")); | |
| 47 | + expect(await store.list("c")).toEqual(["c/tool.c", "c/tool.c.sha256"]); | |
| 48 | + }); | |
| 49 | + | |
| 41 | 50 | it("lists keys under a prefix", async () => { |
| 42 | 51 | await store.put("p/a.txt", Buffer.from("a")); |
| 43 | 52 | await store.put("p/sub/b.txt", Buffer.from("b")); |
| 44 | 53 | |