diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 41287a52..3ca81a87 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1523,27 +1523,3 @@ other world honest against it: `enforcedBy`. The lifecycle fixtures in `test/fixtures/config-lifecycle/` pin the load contract: oldest-supported and future-field configs pass the schema losslessly. - -## Schema as the source of truth (#33, 2026-08-30) - -The Voluptuous schema in `custom_components/houseplan/validation.py` is the -single owner of the persisted config/layout shape. Three artefacts keep every -other world honest against it: - -- `scripts/dump-config-schema.py` walks the schema into the deterministic - `scripts/config-schema.json` (265 leaf paths at introduction); - a pytest regenerates it and fails on any uncommitted drift. -- `test/config-schema-parity.test.mjs` compares manifest enums with the - exported frontend const lists (`DISPLAY_MODES`, `TAP_ACTIONS`, - `SPACE_FILL_MODES`/`ROOM_FILL_MODES`, `OPENING_TYPES`, - `VACUUM_TRAIL_MODES`, `ZERO_WALL_STYLES`, `BG_MODES`). Every divergence - must be blessed in `scripts/schema-compat-allowlist.mjs` with a reason and - an owning issue — and an allow-list entry that stops matching a real - divergence fails the test too, so the list cannot rot. -- `scripts/config-field-registry.mjs` stays the DECISION layer on top of the - manifest: only fields with a non-trivial fate live there, each resolving to - a manifest path or carrying an explicit `schema: 'allow-extra'` / - `'lovelace-card'` passport; implemented mechanisms cite their code point in - `enforcedBy`. The lifecycle fixtures in `test/fixtures/config-lifecycle/` - pin the load contract: oldest-supported and future-field configs pass the - schema losslessly. diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index a5906adf..f25e9f3d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,13 +2,6 @@ ## Unreleased -- Old and future plan configs are now guarded by machinery instead of habit: - the backend schema is dumped into a committed manifest, frontend and - backend value lists are compared in CI with an explicit allow-list for the - few legitimate compatibility divergences, and lifecycle fixtures prove a - config from the oldest supported era — or one from the future — loads - without silent loss ([#33](https://github.com/Matysh/houseplan-card/issues/33)). - - Old and future plan configs are now guarded by machinery instead of habit: the backend schema is dumped into a committed manifest, frontend and backend value lists are compared in CI with an explicit allow-list for the diff --git a/docs/CHANGELOG.ru.md b/docs/CHANGELOG.ru.md index c377e959..02d38e80 100755 --- a/docs/CHANGELOG.ru.md +++ b/docs/CHANGELOG.ru.md @@ -8,13 +8,6 @@ ## Не выпущено -- Старые и будущие конфигурации плана теперь защищены механикой, а не - привычкой: схема бэкенда выгружается в закоммиченный манифест, списки - значений фронта и бэкенда сверяются в CI с явным allow-list'ом немногих - легитимных компат-расхождений, а lifecycle-фикстуры доказывают, что конфиг - старейшей поддерживаемой эпохи — или из будущего — загружается без тихих - потерь ([#33](https://github.com/Matysh/houseplan-card/issues/33)). - - Старые и будущие конфигурации плана теперь защищены механикой, а не привычкой: схема бэкенда выгружается в закоммиченный манифест, списки значений фронта и бэкенда сверяются в CI с явным allow-list'ом немногих diff --git a/scripts/config-audit.mjs b/scripts/config-audit.mjs index 25529747..e6a5c741 100644 --- a/scripts/config-audit.mjs +++ b/scripts/config-audit.mjs @@ -80,9 +80,12 @@ if (isMain) { // #33: exit-code contract — 0 = clean, 3 = migration available // (any finding whose status names a pending mechanism), 2 = invalid // input (unchanged). 3 is chosen to avoid colliding with 2. + // Exactly the statuses that NAME a pending migration (#33 spec rev3): + // decision-required fields (group_lights, exclude_integrations) are + // live supported behaviour awaiting a #44 decision, not legacy, and + // drop-on-validation is handled by the backend itself. const MIGRATION_STATUSES = new Set([ 'migrate-on-write', 'migrate-on-settings-save', 'deprecated-read', - 'drop-on-validation', 'decision-required', ]); const migrationFindings = reports.some((report) => report.findings .some((finding) => MIGRATION_STATUSES.has(finding.status))); diff --git a/test/config-schema-parity.test.mjs b/test/config-schema-parity.test.mjs index ea7bb423..164d362a 100644 --- a/test/config-schema-parity.test.mjs +++ b/test/config-schema-parity.test.mjs @@ -1,6 +1,6 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import { readFileSync } from 'node:fs'; +import { readFileSync, readdirSync } from 'node:fs'; import { SCHEMA_COMPAT_ALLOWLIST } from '../scripts/schema-compat-allowlist.mjs'; import { CONFIG_FIELD_REGISTRY } from '../scripts/config-field-registry.mjs'; import { @@ -90,10 +90,17 @@ test('#33 AC4: every registry entry resolves to a manifest path or an explicit p } }); -/** #33 AC7: the manifest is a build/test artefact — the bundle must not grow. */ -test('#33 AC7: no production source imports the schema manifest', () => { - const source = readFileSync(new URL('../src/houseplan-card.ts', import.meta.url), 'utf8') - + readFileSync(new URL('../src/houseplan-editor-runtime.ts', import.meta.url), 'utf8'); - assert.ok(!source.includes('config-schema-manifest'), - 'the manifest is test infrastructure, not runtime data'); +/** #33 AC7: the manifest is a build/test artefact — the bundle must not grow. + * r1-M1: scan the WHOLE src tree and look for the CURRENT file name (the + * original assertion kept checking the pre-rename string and could not fail). */ +test('#33 AC7: no production source references the schema dump', () => { + const walk = (dir) => readdirSync(dir, { withFileTypes: true }).flatMap((entry) => { + const target = new URL(entry.name + (entry.isDirectory() ? '/' : ''), dir); + return entry.isDirectory() ? walk(target) + : /\.(ts|js|mjs|json)$/.test(entry.name) ? [target] : []; + }); + for (const file of walk(new URL('../src/', import.meta.url))) { + assert.ok(!readFileSync(file, 'utf8').includes('config-schema.json'), + `${file.pathname} references the schema dump — it is test infrastructure, not runtime data`); + } });