[GH-ISSUE #2651] fix(reference-source): default directory exclusion makes a retained symlink abort import #953

Closed
opened 2026-10-15 18:29:51 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @coygeek on GitHub (Oct 13, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/2651

Summary

rea import-reference-source returns no historical-source graph for a readable local tree containing alias -> dist, even when dist is an empty directory. The default dist/ exclusion conflicts with the retained symlink's graph validation. This blocks importing the unrelated source file in the same tree. The fixture and its symlink remain unchanged by the failed import.

Runtime or environment

Verified at source revision a2d8b94dd5 using the shared application importer and the checkout's already-built public CLI dispatcher, with Node.js 24.18.0 and npm 11.16.0 on macOS. The latest published rea-agents package is 6.4.0; that package was not exercised. This is a local source-directory import, with default configuration and no analysis provider. Current upstream was rechecked from source at 3ae826ceba: the exclusion policy, symlink observation, entry conversion and graph-validation path remain unchanged, so the failure is still present; neither the current source runtime nor the published package was rerun for this recheck.

Steps to reproduce

From the built checkout root, run the following Node script. It constructs the same tiny tree and invokes the same public dispatcher used in verification. The executed verification was a focused source test using these filesystem operations and execFile, with the application importer tested separately.

import { execFile } from 'node:child_process';
import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import { promisify } from 'node:util';

const run = promisify(execFile);
const entry = resolve('scripts/rea.mjs');
const fixture = await mkdtemp(join(tmpdir(), 'rea-reference-link-'));
const tree = join(fixture, 'tree');
const home = join(fixture, 'home');
try {
  await mkdir(join(tree, 'dist'), { recursive: true });
  await mkdir(home);
  await writeFile(join(tree, 'source.ts'), 'export const value = 1;\n');
  await symlink('dist', join(tree, 'alias'));
  try {
    const result = await run(process.execPath,
      [entry, 'import-reference-source', tree, '--json'], {
        cwd: fixture,
        env: { PATH: process.env.PATH, HOME: home,
          XDG_CONFIG_HOME: home, XDG_CACHE_HOME: home,
          REA_LOG_LEVEL: 'silent' },
        timeout: 10000, maxBuffer: 1024 * 1024,
      });
    console.log(JSON.stringify({ exitCode: 0, result: JSON.parse(result.stdout) }));
  } catch (error) {
    console.log(JSON.stringify({ exitCode: error.code,
      result: JSON.parse(error.stdout), stderr: error.stderr }));
  }
} finally {
  await rm(fixture, { recursive: true, force: true });
}

The application-level control replaces the link with alias -> source.ts, leaving dist/ in place. That import succeeds, retains alias as an internal symlink and reports the default exclusion for dist. A second control restores alias -> dist and adds alias to .gitignore; that import succeeds and records both exclusions, but omits the symlink entry.

Expected behavior

Import this supported local tree as historical reference, preserve the source file's hash and metadata, and record dist as an exclusion with reason default-ignored and pattern dist/. Observing a retained symlink must remain compatible with intentionally excluding its target. The graph must describe the resulting omitted-target limitation without inventing an inventoried target or reading the excluded directory. This expectation follows the documented directory-import and exclusion contract and the existing support for retaining symlink metadata; it does not require any particular new target-state representation.

Actual behavior

The public CLI exits 1, writes no stderr under the selected silent logging configuration, and returns error: "Import failed" with category: "execution_failure". Its message contains this graph-validation diagnostic:

Internal symlink target must resolve to an inventoried entry

The diagnostic points to entries[0].target and ends with Reference source could not be indexed. Check that the source tree is readable, then try again. The shared application importer returns ok: false, code parse, and the same validation diagnostic, without a graph or partial inventory. The source file is readable, and the target directory exists. The successful inventoried-target control rules out unreadable-root admission and symlink support as explanations.

Evidence

  • Expected source: docs/cli.md:209 documents the caller-selected directory import, file hashes and metadata, and default generated/dependency exclusions. src/application/ReferenceSourceImport.ts:175 specifies omission of caller-excluded paths. tests/boundary/filesystem/referenceSourceImport.test.ts:119 exercises retention of symlink metadata rather than traversal of the target.
  • Failure source: Independently observed through scripts/rea.mjs import-reference-source and importReferenceSource, with the outputs recorded under Actual behavior. The diagnostic is defined at src/domain/referenceSourceGraph.ts:348.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Related: #2443 proposes exposing caller-selected exclusions in the CLI; this failure already occurs with default exclusions. PR #1535 fixed Unicode spelling mismatches for inventoried symlink targets; the target here is intentionally absent from the inventory.

Restoration check

The failing CLI invocation must return a historical-source graph and exit 0. That graph must preserve source.ts, record the dist/ exclusion, and describe the observed retained link without claiming that its excluded target was inventoried. The alias -> source.ts control must still retain an internal symlink resolving to an inventoried file. Explicitly ignoring alias must still omit it and record the matched project exclusion. Import must leave all fixture paths and contents unchanged.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A valid, bounded source-directory import fails completely for the observed excluded-directory target case. No source modification or loss occurred, and unaffected symlink targets work. The evidence establishes a consequential compatibility failure, not severe impact across all source imports.
  • Workaround status: unverified
  • Workaround basis: Adding alias to .gitignore was observed to restore import of the source file, but removes the symlink metadata. Whether that omission is acceptable depends on the caller's required inventory; no workaround preserving the retained link and the original exclusions was verified.
Originally created by @coygeek on GitHub (Oct 13, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2651 ## Summary `rea import-reference-source` returns no historical-source graph for a readable local tree containing `alias -> dist`, even when `dist` is an empty directory. The default `dist/` exclusion conflicts with the retained symlink's graph validation. This blocks importing the unrelated source file in the same tree. The fixture and its symlink remain unchanged by the failed import. ## Runtime or environment Verified at source revision a2d8b94dd50dab8d966239dd63ab84718aa8b4b4 using the shared application importer and the checkout's already-built public CLI dispatcher, with Node.js 24.18.0 and npm 11.16.0 on macOS. The latest published `rea-agents` package is 6.4.0; that package was not exercised. This is a local source-directory import, with default configuration and no analysis provider. Current upstream was rechecked from source at 3ae826ceba6f25b08bce50bcb51001209bc16b57: the exclusion policy, symlink observation, entry conversion and graph-validation path remain unchanged, so the failure is still present; neither the current source runtime nor the published package was rerun for this recheck. ## Steps to reproduce From the built checkout root, run the following Node script. It constructs the same tiny tree and invokes the same public dispatcher used in verification. The executed verification was a focused source test using these filesystem operations and `execFile`, with the application importer tested separately. ```js import { execFile } from 'node:child_process'; import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { promisify } from 'node:util'; const run = promisify(execFile); const entry = resolve('scripts/rea.mjs'); const fixture = await mkdtemp(join(tmpdir(), 'rea-reference-link-')); const tree = join(fixture, 'tree'); const home = join(fixture, 'home'); try { await mkdir(join(tree, 'dist'), { recursive: true }); await mkdir(home); await writeFile(join(tree, 'source.ts'), 'export const value = 1;\n'); await symlink('dist', join(tree, 'alias')); try { const result = await run(process.execPath, [entry, 'import-reference-source', tree, '--json'], { cwd: fixture, env: { PATH: process.env.PATH, HOME: home, XDG_CONFIG_HOME: home, XDG_CACHE_HOME: home, REA_LOG_LEVEL: 'silent' }, timeout: 10000, maxBuffer: 1024 * 1024, }); console.log(JSON.stringify({ exitCode: 0, result: JSON.parse(result.stdout) })); } catch (error) { console.log(JSON.stringify({ exitCode: error.code, result: JSON.parse(error.stdout), stderr: error.stderr })); } } finally { await rm(fixture, { recursive: true, force: true }); } ``` The application-level control replaces the link with `alias -> source.ts`, leaving `dist/` in place. That import succeeds, retains `alias` as an internal symlink and reports the default exclusion for `dist`. A second control restores `alias -> dist` and adds `alias` to `.gitignore`; that import succeeds and records both exclusions, but omits the symlink entry. ## Expected behavior Import this supported local tree as historical reference, preserve the source file's hash and metadata, and record `dist` as an exclusion with reason `default-ignored` and pattern `dist/`. Observing a retained symlink must remain compatible with intentionally excluding its target. The graph must describe the resulting omitted-target limitation without inventing an inventoried target or reading the excluded directory. This expectation follows the documented directory-import and exclusion contract and the existing support for retaining symlink metadata; it does not require any particular new target-state representation. ## Actual behavior The public CLI exits 1, writes no stderr under the selected silent logging configuration, and returns `error: "Import failed"` with `category: "execution_failure"`. Its message contains this graph-validation diagnostic: ```text Internal symlink target must resolve to an inventoried entry ``` The diagnostic points to `entries[0].target` and ends with `Reference source could not be indexed. Check that the source tree is readable, then try again.` The shared application importer returns `ok: false`, code `parse`, and the same validation diagnostic, without a graph or partial inventory. The source file is readable, and the target directory exists. The successful inventoried-target control rules out unreadable-root admission and symlink support as explanations. ## Evidence - Expected source: `docs/cli.md:209` documents the caller-selected directory import, file hashes and metadata, and default generated/dependency exclusions. `src/application/ReferenceSourceImport.ts:175` specifies omission of caller-excluded paths. `tests/boundary/filesystem/referenceSourceImport.test.ts:119` exercises retention of symlink metadata rather than traversal of the target. - Failure source: Independently observed through `scripts/rea.mjs import-reference-source` and `importReferenceSource`, with the outputs recorded under Actual behavior. The diagnostic is defined at `src/domain/referenceSourceGraph.ts:348`. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete Related: #2443 proposes exposing caller-selected exclusions in the CLI; this failure already occurs with default exclusions. PR #1535 fixed Unicode spelling mismatches for inventoried symlink targets; the target here is intentionally absent from the inventory. ## Restoration check The failing CLI invocation must return a historical-source graph and exit 0. That graph must preserve `source.ts`, record the `dist/` exclusion, and describe the observed retained link without claiming that its excluded target was inventoried. The `alias -> source.ts` control must still retain an internal symlink resolving to an inventoried file. Explicitly ignoring `alias` must still omit it and record the matched project exclusion. Import must leave all fixture paths and contents unchanged. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A valid, bounded source-directory import fails completely for the observed excluded-directory target case. No source modification or loss occurred, and unaffected symlink targets work. The evidence establishes a consequential compatibility failure, not severe impact across all source imports. - Workaround status: unverified - Workaround basis: Adding `alias` to `.gitignore` was observed to restore import of the source file, but removes the symlink metadata. Whether that omission is acceptable depends on the caller's required inventory; no workaround preserving the retained link and the original exclusions was verified.
bsakaguchi 2026-10-15 18:29:51 +00:00
Author
Owner

@morluto commented on GitHub (Oct 14, 2026):

Addressed on main in ec271121e.

Reference-source imports retain links into excluded paths. Only a proven directory exclusion establishes descendant applicability; file and unknown ancestor exclusions do not.

Validation: 18 reference-source import boundary tests passed; graph ownership and exclusion controls passed.

Verification scope: No platform beyond the exercised macOS filesystem is claimed.

<!-- gh-comment-id:6158654631 --> @morluto commented on GitHub (Oct 14, 2026): Addressed on main in [ec271121e](https://github.com/morluto/rea/commit/ec271121e). Reference-source imports retain links into excluded paths. Only a proven directory exclusion establishes descendant applicability; file and unknown ancestor exclusions do not. Validation: 18 reference-source import boundary tests passed; graph ownership and exclusion controls passed. Verification scope: No platform beyond the exercised macOS filesystem is claimed.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#953
No description provided.