fix(electron): allow patch downgrades in the runtime guard

- Compare only major and minor versions, so same-minor patch downgrades are allowed
- Keep blocking Electron minor and major downgrades before Yomitan storage loads
- Add tests for patch downgrades, minor downgrades and Electron 42 reopening a 43 profile
- Document the new rules and the SubMiner-dev profile workaround in troubleshooting
This commit is contained in:
2026-09-29 18:44:58 -07:00
parent d4807e5382
commit cfc47c9ac6
4 changed files with 64 additions and 8 deletions
+56 -2
View File
@@ -97,7 +97,7 @@ test('runtime guard blocks a profile downgrade before rewriting its safety recor
});
});
test('runtime guard blocks a downgrade within the supported Electron major', () => {
test('runtime guard blocks a minor downgrade even when the patch is higher', () => {
withTempDir((userDataPath) => {
const statePath = path.join(userDataPath, 'electron-runtime.json');
fs.writeFileSync(
@@ -107,7 +107,7 @@ test('runtime guard blocks a downgrade within the supported Electron major', ()
);
const result = enforceElectronRuntimeGuard({
electronVersion: '43.3.0',
electronVersion: '43.3.99',
userDataPath,
supportedElectronMajor: 43,
});
@@ -123,6 +123,60 @@ test('runtime guard blocks a downgrade within the supported Electron major', ()
});
});
test('runtime guard allows a patch downgrade while preserving minor downgrade protection', () => {
withTempDir((userDataPath) => {
const statePath = path.join(userDataPath, 'electron-runtime.json');
fs.writeFileSync(
statePath,
JSON.stringify({ highestElectronMajor: 43, lastElectronVersion: '43.7.3' }),
'utf8',
);
const result = enforceElectronRuntimeGuard({
electronVersion: '43.7.2',
userDataPath,
});
assert.equal(result.ok, true);
const updatedState = fs.readFileSync(statePath, 'utf8');
assert.deepEqual(JSON.parse(updatedState), {
highestElectronMajor: 43,
lastElectronVersion: '43.7.2',
});
const minorDowngrade = enforceElectronRuntimeGuard({
electronVersion: '43.6.99',
userDataPath,
});
assert.equal(minorDowngrade.ok, false);
if (minorDowngrade.ok) return;
assert.equal(minorDowngrade.title, 'Electron downgrade blocked');
assert.equal(fs.readFileSync(statePath, 'utf8'), updatedState);
});
});
test('runtime guard blocks an Electron 42 build from reopening an Electron 43 profile', () => {
withTempDir((userDataPath) => {
const statePath = path.join(userDataPath, 'electron-runtime.json');
const previousState = JSON.stringify({
highestElectronMajor: 43,
lastElectronVersion: '43.7.2',
});
fs.writeFileSync(statePath, previousState, 'utf8');
const result = enforceElectronRuntimeGuard({
electronVersion: '42.11.8',
userDataPath,
supportedElectronMajor: 42,
});
assert.equal(result.ok, false);
if (result.ok) return;
assert.equal(result.title, 'Electron downgrade blocked');
assert.equal(fs.readFileSync(statePath, 'utf8'), previousState);
});
});
test('runtime guard fails closed when its safety record is malformed', () => {
withTempDir((userDataPath) => {
const statePath = path.join(userDataPath, 'electron-runtime.json');
+4 -4
View File
@@ -39,11 +39,11 @@ function parseElectronVersion(version: string): ParsedElectronVersion | null {
return { major, minor, patch };
}
function compareElectronVersions(
function compareElectronMajorMinorVersions(
left: ParsedElectronVersion,
right: ParsedElectronVersion,
): number {
return left.major - right.major || left.minor - right.minor || left.patch - right.patch;
return left.major - right.major || left.minor - right.minor;
}
function readRuntimeState(statePath: string): ValidatedElectronRuntimeState | null {
@@ -129,7 +129,7 @@ export function enforceElectronRuntimeGuard(options: {
if (
previousState &&
(currentVersion.major < previousState.state.highestElectronMajor ||
compareElectronVersions(currentVersion, previousState.version) < 0)
compareElectronMajorMinorVersions(currentVersion, previousState.version) < 0)
) {
return {
ok: false,
@@ -139,7 +139,7 @@ export function enforceElectronRuntimeGuard(options: {
`The current runtime is Electron ${options.electronVersion}.`,
`Runtime safety record: ${statePath}.`,
'',
'Opening Chromium storage with an older Electron version can destroy Yomitan dictionaries. Upgrade SubMiner before using this profile.',
'Opening Chromium storage with an older Electron major or minor version can destroy Yomitan dictionaries. Upgrade SubMiner before using this profile. Patch downgrades within the same major and minor version are allowed.',
].join('\n'),
statePath,
};