diff --git a/scripts/model-invariants.mjs b/scripts/model-invariants.mjs index a20fab68..ab10d0f8 100644 --- a/scripts/model-invariants.mjs +++ b/scripts/model-invariants.mjs @@ -225,7 +225,7 @@ export function checkReferences({ config, layout = {} } = {}, { notes = [] } = { 'комнаты назначения не существует в том же пространстве'); } } - if (Number(config?.model_version || 0) >= 8) { + if (Number(config?.model_version || 0) === 8) { const wallIds = Array.isArray(room?.wall_ids) ? room.wall_ids : []; const poly = roomPolygon(room) || []; if (wallIds.length !== poly.length) { @@ -246,7 +246,11 @@ export function checkReferences({ config, layout = {} } = {}, { notes = [] } = { for (const opening of space?.openings || []) { const host = opening?.host; if (!host) { - if (Number(config?.model_version || 0) >= 8) { + // #316 §3.3: since model v9 an unhosted contour opening is a valid + // degraded state (the migration keeps an opening with no in-place + // carrier as data, inert until re-placed). Only model v8 documents + // still require an explicit host on every opening. + if (Number(config?.model_version || 0) === 8) { add('opening_host', `${spaceId}:${opening?.id ?? '?'}`, '?', 'в model v8 у проёма нет явной стены-хоста'); } diff --git a/test/model-invariants.test.mjs b/test/model-invariants.test.mjs index ea467155..93483120 100644 --- a/test/model-invariants.test.mjs +++ b/test/model-invariants.test.mjs @@ -688,3 +688,30 @@ test('реальный план: перегородка поверх наруж assert.equal(checkHiddenObstacles(config).length, expected, file); } }); + +test('#316: непривязанный контурный проём — валидное состояние v9, но не v8', () => { + const base = () => ({ + config: { + model_version: 9, + spaces: [{ + id: 'sp316', title: 'S', view_box: [0, 0, 1, 1], + rooms: [], openings: [{ + id: 'orphan', type: 'door', x: 0.9, y: 0.65, angle: 90, length: 0.05, + }], + }], + markers: [], settings: {}, + }, + layout: {}, + }); + const v9 = base(); + assert.deepEqual( + checkReferences(v9).filter((item) => item.kind === 'opening_host'), [], + 'model v9 keeps the unhosted degraded state of #316 §3.3 without a violation', + ); + const v8 = base(); + v8.config.model_version = 8; + assert.equal( + checkReferences(v8).filter((item) => item.kind === 'opening_host').length, 1, + 'model v8 still requires an explicit host on every opening', + ); +});