From 0f4c484be552a4d5e8e1d0fa072441bfa900c815 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Wed, 14 Oct 2026 08:43:59 +0800 Subject: [PATCH] fix(reference): preserve missing symlink target facts Treat ENOTDIR while resolving a symlink target as missing target evidence, preserving its spelling and partial inventory. Keep graph-level excluded-ancestor rejection separate from import success. The compiled boundary regression passes. --- src/reference/ReferenceSourceReaderEntries.ts | 2 +- .../filesystem/referenceSourceImport.test.ts | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/reference/ReferenceSourceReaderEntries.ts b/src/reference/ReferenceSourceReaderEntries.ts index 8ec5e9d803..a7ae837e82 100644 --- a/src/reference/ReferenceSourceReaderEntries.ts +++ b/src/reference/ReferenceSourceReaderEntries.ts @@ -357,7 +357,7 @@ const readSymlinkTarget = async ( } catch (cause: unknown) { const code = cause instanceof Error ? Reflect.get(cause, "code") : undefined; - if (code !== "ENOENT") throw cause; + if (code !== "ENOENT" && code !== "ENOTDIR") throw cause; return { status: "read", kind: "symlink", diff --git a/tests/boundary/filesystem/referenceSourceImport.test.ts b/tests/boundary/filesystem/referenceSourceImport.test.ts index f49ef6ba4c..65c6d4b22d 100644 --- a/tests/boundary/filesystem/referenceSourceImport.test.ts +++ b/tests/boundary/filesystem/referenceSourceImport.test.ts @@ -167,10 +167,21 @@ describe("reference source symlink import", () => { policy: { secretPatterns: [] }, excludePaths: ["secret.txt"], }); - expect(result.ok).toBe(false); - if (result.ok) return; - expect(result.error.code).toBe("parse"); - expect(result.error.message).toMatch(/recorded exclusion/u); + if (!result.ok) throw new Error(result.error.message); + expect(result.value.inventory_state).toBe("partial"); + expect( + result.value.entries.find(({ path }) => path === "alias"), + ).toMatchObject({ + kind: "symlink", + target: "secret.txt/child", + target_state: "missing", + }); + expect(result.value.exclusions).toContainEqual({ + path: "secret.txt", + entry_kind: "file", + reason: "caller-excluded", + pattern: "secret.txt", + }); }); it("preserves an unreadable target as unknown instead of a synthetic path", () => {