fix(nasiko): use descriptor lock identity

This commit is contained in:
haelyra
2026-08-25 13:49:26 -04:00
parent 307bbd53a6
commit e10c4bb5bf
3 changed files with 40 additions and 10 deletions
@@ -40,13 +40,13 @@ Commit `5aa66021` moved ambient-override checks into isolated child processes an
## GREEN
- Focused installer, lifecycle, packaging, release-workflow, manifest, OpenCode, Antigravity, and uninstall tests passed.
- Full repository suite: 3,985 passed, 0 failed.
- Full repository suite: 3,986 passed, 0 failed.
- `npm audit --audit-level=low`: 0 vulnerabilities.
- Supply-chain IOC scan: 207 files inspected, no findings.
- Both release workflow YAML files parsed successfully.
- Both release workflows derive reviewed notes from the validated tag and fail clearly when that version's notes are absent.
- Release-note selection follows the lowercase filename convention shared by prior release directories.
- Exact packed archive lifecycle passed on macOS with Node 24.9.0 using SHA-256 `de51641fee3fd7318937ec3bb45fe86f597b06b36501bf31960efe5ab7c8b42c`.
- Exact packed archive lifecycle passed on macOS with Node 24.9.0 using SHA-256 `cf3a5ccefda2608389c7039b6c8b7f5707fd3fdd99579e843ed1aa593c7b1a15`.
- The packed lifecycle covered npm installation, public CLI setup, cumulative Cursor install, drift detection, repair, uninstall, user-file preservation, Antigravity install/doctor/uninstall, and OpenCode install/doctor/uninstall.
- Simulated hosted-runner `OPENCODE_CONFIG_DIR` and `XDG_CONFIG_HOME` overrides passed the adapter, MCP inventory, lifecycle, legacy migration, doctor, repair, list, and uninstall suites while explicit CLI environments continued to honor those overrides.
+15 -8
View File
@@ -273,11 +273,11 @@ function processIsAlive(pid) {
function inspectLifecycleLock(lockPath, fileSystem) {
const descriptor = fileSystem.openSync(lockPath, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0));
try {
const descriptorStats = fileSystem.fstatSync(descriptor);
if (!descriptorStats.isFile() || descriptorStats.size <= 0 || descriptorStats.size > 4096) return null;
const descriptorStats = fileSystem.fstatSync(descriptor, { bigint: true });
if (!descriptorStats.isFile() || descriptorStats.size <= 0n || descriptorStats.size > 4096n) return null;
const bytes = fileSystem.readFileSync(descriptor);
const pathStats = fileSystem.lstatSync(lockPath);
if (pathStats.isSymbolicLink() || !pathStats.isFile() || !sameFileIdentity(descriptorStats, pathStats)) return null;
if (pathStats.isSymbolicLink() || !pathStats.isFile()) return null;
let metadata;
try { metadata = JSON.parse(bytes.toString('utf8')); } catch (_error) { return null; }
if (
@@ -291,14 +291,21 @@ function inspectLifecycleLock(lockPath, fileSystem) {
}
function removeLockIfOwned(lockPath, expectedStats, fileSystem) {
let descriptor;
try {
const current = fileSystem.lstatSync(lockPath);
if (!current.isSymbolicLink() && current.isFile() && sameFileIdentity(current, expectedStats)) {
descriptor = fileSystem.openSync(lockPath, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0));
const current = fileSystem.fstatSync(descriptor, { bigint: true });
const pathStats = fileSystem.lstatSync(lockPath);
if (!pathStats.isSymbolicLink() && pathStats.isFile() && current.isFile() && sameFileIdentity(current, expectedStats)) {
fileSystem.closeSync(descriptor);
descriptor = undefined;
fileSystem.rmSync(lockPath, { force: true });
return true;
}
} catch (error) {
if (error.code !== 'ENOENT') throw error;
if (error.code !== 'ENOENT' && error.code !== 'ELOOP') throw error;
} finally {
if (descriptor !== undefined) fileSystem.closeSync(descriptor);
}
return false;
}
@@ -316,12 +323,12 @@ function createLifecycleLock(lockPath, fileSystem) {
}
catch (error) {
if (descriptor !== undefined) {
const ownedStats = fileSystem.fstatSync(descriptor);
const ownedStats = fileSystem.fstatSync(descriptor, { bigint: true });
try { fileSystem.closeSync(descriptor); } finally { removeLockIfOwned(lockPath, ownedStats, fileSystem); }
}
throw error;
}
const ownedStats = fileSystem.fstatSync(descriptor);
const ownedStats = fileSystem.fstatSync(descriptor, { bigint: true });
let released = false;
return () => {
if (released) return;
+23
View File
@@ -180,6 +180,29 @@ async function main() {
assert.strictEqual(fs.existsSync(lockPath), true);
} finally { fs.rmSync(installRoot, { recursive: true, force: true }); }
}],
['uses descriptor identity when Windows path stats disagree', () => {
const { acquireLifecycleLock } = require('../../scripts/lib/nasiko-release');
const installRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-nasiko-windows-identity-'));
const lockPath = path.join(installRoot, '.ecc-nasiko-lifecycle.lock');
const windowsLikeFileSystem = {
...fs,
lstatSync: target => {
const stats = fs.lstatSync(target);
return {
...stats,
dev: Number(stats.dev) + 1,
isDirectory: () => stats.isDirectory(),
isFile: () => stats.isFile(),
isSymbolicLink: () => stats.isSymbolicLink(),
};
},
};
try {
const releaseLock = acquireLifecycleLock(installRoot, windowsLikeFileSystem);
releaseLock();
assert.strictEqual(fs.existsSync(lockPath), false);
} finally { fs.rmSync(installRoot, { recursive: true, force: true }); }
}],
['verifies manifest and blob digests before an atomic install', async () => {
const { installNasiko } = require('../../scripts/lib/nasiko-release');
const installRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-nasiko-green-'));