From ddbea0a0400cdcb8787fe1cd03fac7dc930b099b Mon Sep 17 00:00:00 2001 From: Hermes Date: Sat, 22 Aug 2026 17:22:13 -0700 Subject: [PATCH] [glm-grade=A] fix(config): teach schema KNOWN_KEYS the licenseBackup/_version writer keys (DC-091) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every startup logged two false-positive 'Unknown config key — possible typo?' warns: licenseBackup (written by src/managers/license-manager.js:510 activation persistence) and _version (stamped by src/config/migrations.js). Both are first-party writers the validator was never taught about (DC-091). - config-schema.js: add both keys to KNOWN_KEYS with a source-of-writes comment - config-schema.test.js (new): 5 regression tests — live production config keyset validates with zero unknown-key warnings, writer keys never warn, genuine typos still warn (exact string), license/licenseBackup sync guard, _version recognized at every migration value Verified: full jest suite 111 suites / 2621 tests green (baseline 110/2616). Warns reproduced in live container logs 2026-08-22T23:53:54Z; live config.json contains both keys (licenseBackup activation, _version 2). Judge: GLM-5.3 cold read via delegate_task (deleg_30e52384, 36s) — grade A, ship. Verdict URN: urn:ump:ermkvz6ifbp5svga5cnapv5jhm7c5b7qdbwjjerfpxbwcrolm2za (readback verified) Codex quota-walled until 2026-08-29; GLM stand-in per Sami 2026-08-17 directive. --- dashcaddy-api/__tests__/config-schema.test.js | 72 +++++++++++++++++++ dashcaddy-api/src/utilities/config-schema.js | 6 +- 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 dashcaddy-api/__tests__/config-schema.test.js diff --git a/dashcaddy-api/__tests__/config-schema.test.js b/dashcaddy-api/__tests__/config-schema.test.js new file mode 100644 index 0000000..4334557 --- /dev/null +++ b/dashcaddy-api/__tests__/config-schema.test.js @@ -0,0 +1,72 @@ +'use strict'; + +/** + * Regression tests for config-schema.js KNOWN_KEYS — DC-091. + * + * Bug: license-manager.js persists config.licenseBackup (activation + * restore-on-restart) and src/config/migrations.js stamps config._version, + * but neither key was in KNOWN_KEYS — so every startup logged + * `Unknown config key "licenseBackup" / "_version" — possible typo?` + * false positives (verified in live dashcaddy-api container logs, + * 2026-08-22T23:53:54Z restart). + * + * These tests pin: (1) the live production config key set validates with + * zero unknown-key warnings, (2) genuine typos still warn, (3) the schema + * stays in sync with the first-party writer keys. + */ + +const { validateConfig } = require('../src/utilities/config-schema'); + +describe('config-schema KNOWN_KEYS vs first-party writers (DC-091)', () => { + // Exact key set of the live production config.json (DNS2, verified + // 2026-08-23). If a new key appears here, teach KNOWN_KEYS about it — + // or fix the writer if it's a typo. + const LIVE_CONFIG_KEYS = [ + '_version', 'configurationType', 'customFavicon', 'customLogo', + 'dashboardHost', 'dashboardTitle', 'dns', 'dnsServers', 'language', + 'license', 'licenseBackup', 'logoPosition', 'pylon', 'setupComplete', + 'timestamp', 'tld', 'updatedAt' + ]; + + test('live production config key set produces zero unknown-key warnings', () => { + const config = {}; + for (const key of LIVE_CONFIG_KEYS) { + // Minimal valid-ish values; validateConfig only cares about shape + // for these keys, and unknown-key detection is the target here. + config[key] = key === '_version' ? 2 : (key === 'dnsServers' ? {} : 'x'); + } + const result = validateConfig(config); + const unknownWarnings = result.warnings.filter((w) => w.includes('Unknown config key')); + expect(unknownWarnings).toEqual([]); + }); + + test('licenseBackup and _version (first-party writer keys) do not warn', () => { + const result = validateConfig({ licenseBackup: { code: 'DC-...' }, _version: 2 }); + expect(result.warnings).toEqual([]); + }); + + test('genuine typos still warn (guard against over-allowing)', () => { + const result = validateConfig({ dashboadTitle: 'typo' }); + expect(result.warnings).toEqual([ + 'Unknown config key "dashboadTitle" — possible typo?' + ]); + }); + + test('KNOWN_KEYS stays in sync with license-manager writer keys', () => { + // license-manager writes config.licenseBackup and config.license — both + // must be recognized. We assert via validateConfig (public surface) + // rather than importing the private KNOWN_KEYS array. + const result = validateConfig({ license: { code: 'DC-...' }, licenseBackup: { code: 'DC-...' } }); + expect(result.warnings.filter((w) => w.includes('Unknown config key'))).toEqual([]); + }); +}); + +describe('config-schema sync guard: migrations writer', () => { + test('_version is recognized at every migration version value', () => { + // migrations.js bumps _version 0→1→2; the key itself must never warn. + for (const v of [0, 1, 2, 99]) { + const result = validateConfig({ _version: v }); + expect(result.warnings).toEqual([]); + } + }); +}); diff --git a/dashcaddy-api/src/utilities/config-schema.js b/dashcaddy-api/src/utilities/config-schema.js index 3a4034c..dac8ad2 100644 --- a/dashcaddy-api/src/utilities/config-schema.js +++ b/dashcaddy-api/src/utilities/config-schema.js @@ -14,7 +14,11 @@ const KNOWN_KEYS = [ 'configurationType', 'defaults', 'customLogo', 'customFavicon', 'dashboardTitle', 'tailscale', 'license', 'skipped', 'routingMode', 'domain', 'email', 'defaultIP', 'pylon', - 'customLogoDark', 'customLogoLight', 'language' + 'customLogoDark', 'customLogoLight', 'language', + // license-manager.js persists the last activation to config.licenseBackup + // (restore-on-restart path); src/config/migrations.js stamps _version. + // Both are first-party writes — see DC-091. + 'licenseBackup', '_version' ]; /**