Три состояния владельца позиции вместо двух

Ревью r1, Medium: наблюдение о записи на удалённом пространстве говорило
«владелец жив» и про ключ, который ни во что не резолвится. Это заявление о
доказанности там, где её нет: продукт в этом состоянии ничего не удаляет не
потому, что владелец жив, а потому, что не знает — `space-reference-repair`
ведёт `live`, `absent` и `unverified`, три состояния, а не два.

Проверка ведёт те же три. Нерезолвящийся ключ получает наблюдение
`unknown_owner` с той же формулировкой, что и в живом пространстве: «владелец не
найден в конфигурации (возможно устройство HA)». Тест сверяет теперь и ТЕКСТ
причины — вид наблюдения без текста эту асимметрию пропускал, ровно так она и
проехала.

Issue: #566
User-Visible: no
This commit is contained in:
Codex
2026-09-14 02:28:50 +03:00
committed by Codex
parent 9cb1f4be6c
commit 733cd5b86b
4 changed files with 59 additions and 20 deletions
+22 -8
View File
@@ -212,21 +212,35 @@ export function checkReferences({ config, layout = {} } = {}, { notes = [] } = {
// пользователя. Значит конфиг, только что прошедший Optimize, законно
// содержит такие записи, и объявлять их нарушением — врать про модель.
//
// Судим тем же правилом: нарушение только там, где владелец ОТСУТСТВУЕТ
// по самой конфигурации. Остальное — наблюдение, как и у ветки
// `unknown_owner` ниже: проверка с ложными срабатываниями умирает первой.
// Судим тем же правилом и ТЕМИ ЖЕ ТРЕМЯ состояниями, что продукт: `absent`
// (нарушение), `live` и `unverified` (наблюдения). Разница между двумя
// последними — не педантизм: сказать «владелец жив» про ключ, который ни
// во что не резолвится, значит заявить доказанность там, где её нет, и
// следующий разбор поверит записи вместо проверки (r1 Medium).
const roomOwner = key.startsWith('rl_') ? key.slice(3) : '';
const areaOwner = key.startsWith('grp_') ? key.slice(4) : '';
const ownerGone = roomOwner ? !allRoomIds.has(roomOwner)
: areaOwner ? !allAreas.has(areaOwner)
: removedMarkerIds.has(key);
if (ownerGone) {
const owner = roomOwner
? (allRoomIds.has(roomOwner) ? 'live' : 'absent')
: areaOwner
? (allAreas.has(areaOwner) ? 'live' : 'absent')
: removedMarkerIds.has(key) ? 'absent'
: activeMarkerIds.has(key) ? 'live'
: 'unverified';
if (owner === 'absent') {
add('layout_space', key, space,
'пространства не существует, и владельца позиции тоже нет');
} else {
} else if (owner === 'live') {
notes.push({ invariant: 'references', kind: 'stale_layout_space', owner: key,
reference: space,
detail: 'пространства не существует, владелец жив — Optimize хранит позицию намеренно' });
} else {
// Тот же факт, что у ветки `unknown_owner` ниже, и та же формулировка:
// владельца в конфигурации не видно, отличить устройство HA от мусора
// по одному конфигу нельзя. Продукт в этом состоянии тоже ничего не
// удаляет — но не потому, что владелец жив, а потому, что не знает.
notes.push({ invariant: 'references', kind: 'unknown_owner', owner: key,
reference: key,
detail: 'владелец не найден в конфигурации (возможно устройство HA); пространства тоже нет' });
}
continue;
}
+18 -4
View File
@@ -80,8 +80,20 @@ const MUTANT_DEFINITIONS = [
+ 'после Optimize — и следующий разбор начнётся с ложного сигнала',
patches: [{
file: 'scripts/model-invariants.mjs',
find: ' const ownerGone = roomOwner ? !allRoomIds.has(roomOwner)',
replace: ' const ownerGone = true || roomOwner ? !allRoomIds.has(roomOwner)',
find: " if (owner === 'absent') {",
replace: " if (owner === 'absent' || true) {",
}],
},
{
id: 'invariants-claim-proof-for-an-unknown-owner',
guard: 'node --test --test-name-pattern="#566" test/model-invariants.test.mjs',
because: '#566 r1: ключ, который ни во что не резолвится, и живой маркер — разные '
+ 'состояния; общая формулировка «владелец жив» заявляет доказанность там, где её нет, '
+ 'и следующий разбор поверит записи вместо проверки',
patches: [{
file: 'scripts/model-invariants.mjs',
find: " : activeMarkerIds.has(key) ? 'live'\n : 'unverified';",
replace: " : 'live';",
}],
},
{
@@ -92,8 +104,10 @@ const MUTANT_DEFINITIONS = [
+ 'отключение проверки',
patches: [{
file: 'scripts/model-invariants.mjs',
find: ' : removedMarkerIds.has(key);',
replace: ' : false;',
// Файл на JS, поэтому литеральная ложь здесь безопасна: сужения типов,
// которые ломает статически мёртвая ветка в .ts (#568), тут нет.
find: " : removedMarkerIds.has(key) ? 'absent'",
replace: " : false ? 'absent'",
}],
},
{