fix(release): stage ECC 2.2 launch safely

This commit is contained in:
haelyra
2026-08-25 17:19:45 -04:00
parent d6d0c4e696
commit 204cc2d2a3
21 changed files with 494 additions and 75 deletions
+1 -1
View File
@@ -41,7 +41,7 @@ const COMMANDS = {
},
nasiko: {
script: 'nasiko.js',
description: 'Install or inspect the optional pinned Nasiko control-plane CLI',
description: 'Install or inspect the optional pinned Nasiko CLI lifecycle bridge',
},
memory: {
script: 'memory.js',
@@ -205,42 +205,79 @@ function verifyManagedLegacyFile(operation, location, sourceRoot) {
return { destinationPath, stat: destination.stat };
}
function removeVerifiedLegacyFile(entry, location) {
function pathExistsWith(fileSystem, filePath) {
try {
fileSystem.lstatSync(filePath);
return true;
} catch (error) {
if (error && (error.code === 'ENOENT' || error.code === 'ENOTDIR')) {
return false;
}
throw error;
}
}
function restoreQuarantinedFileNoClobber(quarantinePath, safePath, fileSystem) {
try {
fileSystem.linkSync(quarantinePath, safePath);
} catch (error) {
error.retainedPath = quarantinePath;
throw error;
}
try {
fileSystem.rmSync(quarantinePath);
} catch (error) {
error.retainedPath = quarantinePath;
throw error;
}
}
function removeVerifiedLegacyFile(entry, location, fileSystem = fs) {
const safePath = assertWithinTrustedRoot(
entry.destinationPath,
location.targetRoot,
'remove verified legacy OpenCode file'
);
const quarantineDir = fs.mkdtempSync(path.join(
const quarantineDir = fileSystem.mkdtempSync(path.join(
path.dirname(location.targetRoot),
'.ecc-opencode-remove-'
));
const quarantinePath = path.join(quarantineDir, path.basename(safePath));
try {
fs.renameSync(safePath, quarantinePath);
const quarantinedStat = fs.lstatSync(quarantinePath, { bigint: true });
fileSystem.renameSync(safePath, quarantinePath);
const quarantinedStat = fileSystem.lstatSync(quarantinePath, { bigint: true });
const identityMatches = !quarantinedStat.isSymbolicLink()
&& quarantinedStat.isFile()
&& quarantinedStat.dev === entry.stat.dev
&& quarantinedStat.ino === entry.stat.ino;
if (!identityMatches) {
fs.renameSync(quarantinePath, safePath);
fs.rmdirSync(quarantineDir);
return false;
const identityError = new Error(
`Legacy OpenCode file changed during quarantine: ${safePath}`
);
identityError.code = 'ESTALE';
throw identityError;
}
fs.rmSync(quarantinePath);
fs.rmdirSync(quarantineDir);
fileSystem.rmSync(quarantinePath);
fileSystem.rmdirSync(quarantineDir);
return true;
} catch (error) {
let restoreError = null;
try {
if (pathExists(quarantinePath) && !pathExists(safePath)) {
fs.renameSync(quarantinePath, safePath);
if (pathExistsWith(fileSystem, quarantinePath)) {
restoreQuarantinedFileNoClobber(quarantinePath, safePath, fileSystem);
}
if (pathExists(quarantineDir) && fs.readdirSync(quarantineDir).length === 0) {
fs.rmdirSync(quarantineDir);
if (
pathExistsWith(fileSystem, quarantineDir)
&& fileSystem.readdirSync(quarantineDir).length === 0
) {
fileSystem.rmdirSync(quarantineDir);
}
} catch (_restoreError) {
// Preserve the quarantined entry when restoration cannot be proven safe.
} catch (recoveryError) {
restoreError = recoveryError;
}
if (restoreError) {
restoreError.cause = error;
throw restoreError;
}
throw error;
}
@@ -294,8 +331,9 @@ function removeLegacyFiles(removable, location, retainedPaths) {
}
removedPaths.push(entry.destinationPath);
removeEmptyParents(entry.destinationPath, location.targetRoot);
} catch (_error) {
} catch (error) {
retainedPaths.push(entry.destinationPath);
if (error.retainedPath) retainedPaths.push(error.retainedPath);
}
}
return removedPaths;
@@ -356,4 +394,5 @@ module.exports = {
cleanupLegacyOpencodeInstall,
getLegacyOpencodeLocation,
inspectLegacyOpencodeState,
removeVerifiedLegacyFile,
};
+1 -1
View File
@@ -13,7 +13,7 @@ const {
function helpText() {
return `
ECC Nasiko control-plane bridge
ECC experimental Nasiko CLI lifecycle bridge
Usage:
ecc nasiko status [--install-dir <absolute-path>] [--json]