fix(reference)!: preserve symlink races and root failure facts

Classify replacement EINVAL at the readlink syscall so the reader and importer
retain sibling observations. Give an admitted root's enumeration failure its
own typed field, use it for unresolved symlink coverage, and project it as a
graph limitation instead of an invalid dot-path child entry. Keep admission,
cancellation and cleanup failures distinct.

Closes #5135
Closes #5136

BREAKING CHANGE: Partial reference-source reader results report failed root
enumeration in rootFailure instead of a failed directory child at path ".".
Historical source graphs preserve the root failure as a top-level limitation.
This commit is contained in:
morluto committed 2026-10-19 08:36:53 +08:00
1 parent c85b44e1b1
commit 6296fdf560
8 files changed
+312 -42

No files matched your search

@@ -30,6 +30,33 @@ const exclusions = (
): TraversalState["exclusions"] => items;
describe("unobserved internal symlink reconciliation", () => {
it("uses the explicit root failure only when no closer completed directory or exclusion covers the target", () => {
const entries: ReferenceSourceEntry[] = [
internalLink("unobserved", "unseen/target.txt"),
internalLink("complete", "known/target.txt"),
internalLink("excluded", "omitted/target.txt"),
{ status: "read", kind: "directory", path: "known" },
];
reconcileUnobservedSymlinkTargets(
entries,
exclusions({ path: "omitted", kind: "directory" }),
undefined,
{ code: "changed", message: "Reference root changed" },
);
expect(entries[0]).toMatchObject({
status: "failed",
kind: "symlink",
code: "changed",
message: expect.stringContaining("Reference root changed"),
});
expect(entries.slice(1, 3)).toEqual([
internalLink("complete", "known/target.txt"),
internalLink("excluded", "omitted/target.txt"),
]);
});
it("retains unreadable targets below the nearest failed directory", () => {
const entries: ReferenceSourceEntry[] = [
internalLink("alias.txt", "hidden/target.txt"),