Skip to content

feat: isolate review cache roots - #129

Merged
norvalbv merged 1 commit into
mainfrom
codex/review-stack-17-cache-root
Jul 20, 2026
Merged

feat: isolate review cache roots#129
norvalbv merged 1 commit into
mainfrom
codex/review-stack-17-cache-root

Conversation

@norvalbv

@norvalbv norvalbv commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Review 17 of 20 — hardened replacement stack. This is not a patch-equivalent split of #95; it is one layer of the larger redesign.

What this PR does

Introduces a stable, checkout-external root for persistent review cache data:

  • Derives repository identity from physical Git/repository evidence rather than the current checkout path alone.
  • Keeps cache data outside the target checkout, common Git directory, and temporary review worktrees.
  • Handles ordinary clones, linked worktrees, nested targets, repository recreation, and platform/XDG cache locations.
  • Rejects unsafe symlink-parent and path-containment layouts.
  • Adds hermetic identity and isolation tests.

Why this slice exists

Convergence caches need to survive disposable review worktrees and be shared by reviews of the same repository, but they must never add bytes to the reviewed target or collide simply because two unrelated repositories use the same folder name.

Review carefully

  • Linked worktrees for one repository resolve to the intended shared identity.
  • Recreated or unrelated repositories do not inherit stale verdicts.
  • No resolved cache root can fall inside the checkout, Git administration directories, or ephemeral worktrees.
  • Physical path and filesystem identity checks remain portable and fail closed.
  • Nested target paths cannot redirect the cache into target-controlled storage.

Explicit non-goals

  • This PR defines cache-root identity and storage isolation; it does not promote or accept a review verdict.
  • Generation-aware verdict storage is added in feat: fence review verdict caches #135.
  • This slice does not expose the public devkit review command.

Stack position

Review bottom-up. These PRs currently target their predecessor branches; do not merge a middle PR directly to main. After review, successors must be retargeted/rebased as predecessors land, unless an explicit stack-collapse landing process is agreed.

Size and evidence

  • 2 files
  • +482 / -0 lines
  • Includes a dedicated cache-root identity and containment test suite.
  • Originally published through devkit ship.
  • This description update changes GitHub metadata only.

Summary by CodeRabbit

  • New Features

    • Added stable, checkout-independent cache locations for devkit review.
    • Cache locations remain consistent across linked worktrees and are isolated from temporary runtime directories.
    • Added platform-appropriate cache location support, including explicit cache-base and environment configuration.
    • Added validation to prevent unsafe or invalid cache paths.
  • Tests

    • Added comprehensive coverage for repository identity, worktrees, cache configuration, symlinks, permissions, and path safety.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds reviewCacheRoot, which derives stable external cache directories from Git repository identity, validates physical and non-overlapping paths, supports platform cache configuration, exposes a CLI entrypoint, and adds comprehensive repository and filesystem safety tests.

Changes

Review cache root

Layer / File(s) Summary
Git identity and physical-path validation
cli/lib/ship/review/cache/root.mts
Defines cache-root options, validates physical directories, sanitizes Git queries, derives history roots, and hashes repository metadata into a deterministic identity.
Cache-root resolution and CLI entrypoint
cli/lib/ship/review/cache/root.mts
Selects platform-specific cache bases, rejects overlapping or redirected locations, creates the persistent cache root, and exposes the null-terminated CLI command.
Repository and filesystem safety coverage
cli/__tests__/review-cache-root.test.mts
Tests stable namespaces, linked worktrees, repository recreation, unrelated histories, XDG configuration, overlap rejection, invalid cache bases, and symlink safety.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLIAdapter
  participant reviewCacheRoot
  participant GitSubprocess
  participant FileSystem
  CLIAdapter->>reviewCacheRoot: Provide target, runtime, and optional cache base
  reviewCacheRoot->>GitSubprocess: Query Git root, common directory, and history roots
  GitSubprocess-->>reviewCacheRoot: Return repository metadata
  reviewCacheRoot->>FileSystem: Validate boundaries and create cache directory
  FileSystem-->>reviewCacheRoot: Return resolved cache path
  reviewCacheRoot-->>CLIAdapter: Print cache path with null terminator
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: isolating review cache roots outside the checkout.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/review-stack-17-cache-root

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Establishes a private, platform-aware cache root outside the contributor
checkout for review runs. Squashes the feature commit and its follow-up refactor
simplifying the cache platform defaults.
@norvalbv
norvalbv force-pushed the codex/review-stack-17-cache-root branch from c5d9c2c to baa4f91 Compare July 20, 2026 18:52
@norvalbv
norvalbv merged commit 335afb2 into main Jul 20, 2026
1 of 2 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
cli/__tests__/review-cache-root.test.mts (2)

137-147: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cache-base platform coverage is Linux-only.

Only the linux XDG_CACHE_HOME branch is exercised here. The stack context describes this layer's dependency as "Selects the platform cache base" and the PR summary claims support for "platform/XDG cache locations" generally — macOS (~/Library/Caches) and Windows (%LOCALAPPDATA%) default-selection branches have no coverage in this file. Consider adding equivalent platform: 'darwin' / platform: 'win32' cases to lock in that contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/__tests__/review-cache-root.test.mts` around lines 137 - 147, Add
equivalent reviewCacheRoot tests for platform values 'darwin' and 'win32' in
review-cache-root.test.mts, covering the default macOS ~/Library/Caches and
Windows %LOCALAPPDATA% cache-base selection branches. Use the existing test
setup and assertions to verify each platform resolves to its documented cache
location without relying on shell HOME expansion.

108-135: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Strengthen the multi-root canonicalization assertion.

This only asserts existsSync(...) is true, i.e. that the call doesn't throw. It doesn't verify canonicalization actually works (e.g. that the derived identity is stable/deterministic for a repo with multiple unrelated root commits), which is what the test name implies is being covered.

♻️ Suggested strengthening
-    expect(
-      existsSync(
-        reviewCacheRoot(fx.target, physicalTemp('multi-root-runtime'), {
-          cacheBase: physicalTemp('multi-root-base'),
-        }),
-      ),
-    ).toBe(true);
+    const cacheBase = physicalTemp('multi-root-base');
+    const first = reviewCacheRoot(fx.target, physicalTemp('multi-root-runtime-one'), { cacheBase });
+    const second = reviewCacheRoot(fx.target, physicalTemp('multi-root-runtime-two'), { cacheBase });
+    expect(second).toBe(first);
+    expect(existsSync(first)).toBe(true);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/__tests__/review-cache-root.test.mts` around lines 108 - 135, Strengthen
the assertion around reviewCacheRoot in the multi-root test by calling it
repeatedly with the same repository and cache inputs and verifying the returned
canonical cache root is identical each time. Preserve the existing multi-root
setup and ensure the assertion validates the derived identity, not merely that
the path exists.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cli/__tests__/review-cache-root.test.mts`:
- Around line 195-213: Update the symlink-redirection assertion in the
reviewCacheRoot test to check the redirected cache directory or path that
reviewCacheRoot would create if the symlink were followed, rather than checking
redirected/checkpoint. Keep the existing rejection expectation and verify that
no cache-related path is created under redirected.

In `@cli/lib/ship/review/cache/root.mts`:
- Around line 29-41: Update assertPhysicalDirectory so the
realpathSync(requested) comparison is normalized for Windows path casing and
drive-letter differences, or bypass that equality check on win32. Preserve the
existing directory and symlink validation, error propagation, and
physical-directory failure behavior.

---

Nitpick comments:
In `@cli/__tests__/review-cache-root.test.mts`:
- Around line 137-147: Add equivalent reviewCacheRoot tests for platform values
'darwin' and 'win32' in review-cache-root.test.mts, covering the default macOS
~/Library/Caches and Windows %LOCALAPPDATA% cache-base selection branches. Use
the existing test setup and assertions to verify each platform resolves to its
documented cache location without relying on shell HOME expansion.
- Around line 108-135: Strengthen the assertion around reviewCacheRoot in the
multi-root test by calling it repeatedly with the same repository and cache
inputs and verifying the returned canonical cache root is identical each time.
Preserve the existing multi-root setup and ensure the assertion validates the
derived identity, not merely that the path exists.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 105578c2-6532-4745-afe7-0f3c339ee9ab

📥 Commits

Reviewing files that changed from the base of the PR and between f11c440 and baa4f91.

📒 Files selected for processing (2)
  • cli/__tests__/review-cache-root.test.mts
  • cli/lib/ship/review/cache/root.mts

Comment on lines +195 to +213
it('rejects cache overlap, relative bases, and symlink redirection', () => {
const fx = initRepository(physicalTemp('unsafe-repo'));
const temp = physicalTemp('unsafe-runtime');
expect(() => reviewCacheRoot(fx.target, temp, { cacheBase: fx.target })).toThrow(
/must live outside the target Git worktree/,
);
expect(() => reviewCacheRoot(fx.target, temp, { cacheBase: 'relative/cache' })).toThrow(
/cache base must be an absolute path/,
);

const cacheBase = physicalTemp('symlink-base');
const redirected = physicalTemp('symlink-destination');
mkdirSync(join(cacheBase, 'devkit'));
symlinkSync(redirected, join(cacheBase, 'devkit/review'), 'dir');
expect(() => reviewCacheRoot(fx.target, temp, { cacheBase })).toThrow(
/persistent review cache must be a physical directory/,
);
expect(existsSync(join(redirected, 'checkpoint'))).toBe(false);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- test file outline ---\n'
ast-grep outline cli/__tests__/review-cache-root.test.mts --view expanded || true

printf '\n--- relevant test file excerpt ---\n'
sed -n '160,250p' cli/__tests__/review-cache-root.test.mts

printf '\n--- search reviewCacheRoot / ensurePhysicalDirectory ---\n'
rg -n "reviewCacheRoot|ensurePhysicalDirectory|checkpoint" cli -S

Repository: norvalbv/devkit

Length of output: 244


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- locate implementation files ---\n'
fd -a "review-cache-root" cli .
fd -a "physical" cli .
fd -a "directory" cli .

printf '\n--- inspect test file around target lines ---\n'
nl -ba cli/__tests__/review-cache-root.test.mts | sed -n '190,235p'

printf '\n--- inspect implementation symbols if file exists ---\n'
for f in $(git ls-files | rg 'review-cache-root|physical.*directory|ensurePhysicalDirectory|cache root|review cache'); do
  echo "### $f"
  nl -ba "$f" | sed -n '1,260p' | rg -n "reviewCacheRoot|ensurePhysicalDirectory|physical|checkpoint|symlink|cacheBase|mkdirSync|realpath" -n -A4 -B4 || true
done

Repository: norvalbv/devkit

Length of output: 244


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- test file outline ---'
ast-grep outline cli/__tests__/review-cache-root.test.mts --view expanded || true

echo
echo '--- relevant test file excerpt ---'
nl -ba cli/__tests__/review-cache-root.test.mts | sed -n '160,250p'

echo
echo '--- search reviewCacheRoot / ensurePhysicalDirectory ---'
rg -n "reviewCacheRoot|ensurePhysicalDirectory|checkpoint" cli -S

Repository: norvalbv/devkit

Length of output: 436


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- target test slice ---'
sed -n '190,235p' cli/__tests__/review-cache-root.test.mts | cat -n

echo
echo '--- find implementation files ---'
fd -a "review-cache-root" .
fd -a "ensurePhysicalDirectory" .
fd -a "review-cache-root.*" .
fd -a "cache-root" .

echo
echo '--- search symbols ---'
rg -n "reviewCacheRoot|ensurePhysicalDirectory|checkpoint|symlink" cli -S

Repository: norvalbv/devkit

Length of output: 25117


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- target test slice ---'
sed -n '190,235p' cli/__tests__/review-cache-root.test.mts | cat -n

echo
echo '--- find implementation files ---'
fd -a "review-cache-root" .
fd -a "ensurePhysicalDirectory" .
fd -a "cache-root" .

echo
echo '--- search symbols ---'
rg -n "reviewCacheRoot|ensurePhysicalDirectory|checkpoint|symlink" cli -S

Repository: norvalbv/devkit

Length of output: 25057


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- target test slice ---'
sed -n '190,235p' cli/__tests__/review-cache-root.test.mts | cat -n

echo
echo '--- locate implementation files ---'
fd -a "review-cache-root" .
fd -a "ensurePhysicalDirectory" .
fd -a "cache-root" .

echo
echo '--- search symbols in cli ---'
rg -n "reviewCacheRoot|ensurePhysicalDirectory|checkpoint|symlink" cli -S

Repository: norvalbv/devkit

Length of output: 25066


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- implementation slice: review cache root ---'
sed -n '1,320p' cli/lib/ship/review/cache/root.mts | cat -n

echo
echo '--- focused search within implementation ---'
rg -n "ensurePhysicalDirectory|mkdirSync|realpathSync|checkpoint|writeFileSync|symlink|lstatSync|statSync" cli/lib/ship/review/cache/root.mts cli/lib/fs-helpers.mts -n -A4 -B4

Repository: norvalbv/devkit

Length of output: 17882


Assert the redirected cache path, not checkpoint. reviewCacheRoot() never writes a checkpoint here, so existsSync(join(redirected, 'checkpoint')) doesn’t prove the symlink guard. Check the path that would be created under redirected if the link were followed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/__tests__/review-cache-root.test.mts` around lines 195 - 213, Update the
symlink-redirection assertion in the reviewCacheRoot test to check the
redirected cache directory or path that reviewCacheRoot would create if the
symlink were followed, rather than checking redirected/checkpoint. Keep the
existing rejection expectation and verify that no cache-related path is created
under redirected.

Comment on lines +29 to +41
function assertPhysicalDirectory(path: string, label: string): string {
const requested = resolve(path);
try {
const stat = lstatSync(requested);
if (!stat.isDirectory() || stat.isSymbolicLink() || realpathSync(requested) !== requested) {
fail(`${label} must be a physical directory: ${requested}`);
}
} catch (cause) {
if (cause instanceof Error && cause.message.startsWith('devkit review:')) throw cause;
fail(`${label} is not an available physical directory: ${requested}`);
}
return requested;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does Node.js fs.realpathSync normalize drive letter or path casing differently from path.resolve on Windows?

💡 Result:

Yes, Node.js fs.realpathSync (and its variants like fs.realpathSync.native) can normalize drive letter casing differently from path.resolve on Windows [1][2][3]. The difference stems from how these two methods operate: 1. path.resolve: This method performs string manipulation on the provided path segments and the current working directory [4]. It does not interact with the filesystem [5]. Consequently, if a path is constructed with a lowercase drive letter (e.g., c:...), path.resolve will generally maintain that casing [2][3]. 2. fs.realpathSync: This method interacts with the underlying operating system to resolve the absolute path and handle symbolic links or junctions [1][6]. On Windows, this often involves internal system calls (such as GetFinalPathNameByHandle) that may return a capitalized drive letter (e.g., C:...) even if the input used a lowercase drive letter [1][2][3]. Because of this behavior, inconsistencies can arise in applications that perform strict string comparisons between paths derived from these different methods [2][3]. For example, if one part of an application uses path.resolve to generate a path and another uses fs.realpathSync, they may produce different strings for the same location on disk (e.g., c:\project vs. C:\project), which can cause issues with caching, module resolution, or configuration lookups [2][3][7]. To ensure consistency on Windows, developers often prefer using fs.realpathSync (or fs.realpathSync.native) consistently across an application to ensure all paths are resolved to their canonical, system-provided forms [2][3][6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant file with line numbers and nearby context.
sed -n '1,220p' cli/lib/ship/review/cache/root.mts

# Search for any Windows-specific path handling or realpath comparisons in the repo.
rg -n "realpathSync|lstatSync|resolve\\(|case-sens|win32|platform === 'win32'|HOMEDRIVE|cacheBase" cli/lib -S

Repository: norvalbv/devkit

Length of output: 17204


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the path utility helpers that may influence Windows canonicalization.
sed -n '1,180p' cli/lib/ship/review/runtime-paths.mts

# Look for tests covering Windows paths, drive-letter casing, or realpath comparisons.
rg -n "realpathSync\\(|win32|drive letter|LOCALAPPDATA|HOMEDRIVE|case" cli -g '*test*' -g '*spec*' -S

Repository: norvalbv/devkit

Length of output: 7372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the review cache root tests around the path invariants.
sed -n '1,280p' cli/__tests__/review-cache-root.test.mts

# Inspect any tests that explicitly skip or vary behavior on Windows in nearby review path code.
sed -n '240,320p' cli/__tests__/review-asset-runtime.test.mts
sed -n '240,320p' cli/__tests__/review-setup-runtime.test.mts

Repository: norvalbv/devkit

Length of output: 15475


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,260p' cli/__tests__/review-cache-root.test.mts

Repository: norvalbv/devkit

Length of output: 9325


Normalize the physical-directory comparison on Windows. realpathSync(requested) !== requested can reject a real directory when Windows canonicalizes the drive letter or path casing differently from resolve(). Compare normalized canonical paths, or skip this equality check on win32.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cli/lib/ship/review/cache/root.mts` around lines 29 - 41, Update
assertPhysicalDirectory so the realpathSync(requested) comparison is normalized
for Windows path casing and drive-letter differences, or bypass that equality
check on win32. Preserve the existing directory and symlink validation, error
propagation, and physical-directory failure behavior.

norvalbv added a commit that referenced this pull request Jul 22, 2026
norvalbv added a commit that referenced this pull request Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant