diff --git a/lib/dev/deps/gh_integration.rb b/lib/dev/deps/gh_integration.rb index 90be50f..a4274fe 100644 --- a/lib/dev/deps/gh_integration.rb +++ b/lib/dev/deps/gh_integration.rb @@ -178,8 +178,16 @@ def install_from_source(dep) sig { params(base_dir: Pathname, target_dir: Pathname).void } def publish_current(base_dir, target_dir) link = base_dir / "current" + desired = target_dir.basename.to_s + # A pointer that already resolves to the target is converged — skip + # the rewrite. The swap is not free idempotence but a write, and + # identities that can only read the shared tree (the ai-agent user + # on the human-owned engine tree, plans#26) crash on it (caught + # live at the plans#36 ceremony via dev install-deps). + return if File.symlink?(link.to_s) && File.readlink(link.to_s) == desired + tmp = base_dir / ".current-#{Process.pid}-#{SecureRandom.hex(4)}" - File.symlink(target_dir.basename.to_s, tmp.to_s) + File.symlink(desired, tmp.to_s) File.rename(tmp.to_s, link.to_s) rescue StandardError FileUtils.rm_f(tmp.to_s) if tmp diff --git a/test/dev/deps/gh_integration_test.rb b/test/dev/deps/gh_integration_test.rb index 16c3439..c2bc74e 100644 --- a/test/dev/deps/gh_integration_test.rb +++ b/test/dev/deps/gh_integration_test.rb @@ -150,6 +150,33 @@ def build_integration(fixture_files, cache_dir) FileUtils.rm_rf(dir) end + test "install_all leaves an already-correct current pointer untouched — a read-only shared tree stays usable" do + Given "a published version whose current pointer already resolves to it, in a base dir the " \ + "caller cannot write (the agent identity on the human-owned shared engine tree, plans#26)" + dir = Dir.mktmpdir("dev-gh-int-test-") + parts = build_split_archive(dir, "engine.tar.zst", part_size: 64) + install_dir = File.join(dir, "engines", "unreal-engine-css") + version_dir = File.join(install_dir, "5.6.1-css-83") + FileUtils.mkdir_p(version_dir) + File.write(File.join(version_dir, ".dev-gh-release"), "5.6.1-css-83") + File.symlink("5.6.1-css-83", File.join(install_dir, "current")) + FileUtils.chmod(0o555, install_dir) + dep = build_dependency(parts, install_dir) + integration = build_integration(parts, File.join(dir, "cache")) + + When "installing again" + integration.install_all([dep]) + + Then "the converged pointer was recognized, not rewritten (caught live at the plans#36 " \ + "ceremony: the unconditional rewrite crashed dev install-deps with EACCES)" + File.readlink(File.join(install_dir, "current")) == "5.6.1-css-83" + integration.download_count == 0 + + Cleanup + FileUtils.chmod(0o755, install_dir) + FileUtils.rm_rf(dir) + end + test "install_all installs a new version alongside the existing one when the locked tag changes" do Given "an existing version dir for an older tag" dir = Dir.mktmpdir("dev-gh-int-test-")