/** * Aggregate patch invariants: every INSERT row id is web-ui-* namespaced and * unique within one aggregate, and no aggregate id collides with any * standalone package's own row id (the coexistence guarantee). The generated * files are the contract — scripts/aggregate.mjs --check enforces drift * separately. */ import { existsSync, readdirSync, readFileSync } from 'node:fs' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' import assert from 'node:assert/strict' import { test } from 'node:test' const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url)) const ROOT = join(SCRIPT_DIR, '..') /** Parse the INSERT row ids of one cordis.patch.yml (rows are indented under * an `- insert:` block; top-level `- id:` entries are excluded). */ function idsOf(relPath) { const lines = readFileSync(join(ROOT, relPath), 'utf8').split(/\r?\n/) return lines .filter((line) => /^ {4}- id: /.test(line)) .map((line) => line.trim().replace(/^- id: /, '')) } const AGGREGATES = ['packages/dsh-web-all/cordis.patch.yml'] test('aggregate rows are web-ui-* namespaced and unique', () => { for (const rel of AGGREGATES) { const ids = idsOf(rel) assert.ok(ids.length > 0, `${rel} should carry rows`) assert.equal(new Set(ids).size, ids.length, `${rel} ids must be unique`) for (const id of ids) { assert.match(id, /^web-ui-[a-z0-9-]+$/, `${rel} id must be namespaced: ${id}`) } } }) test('aggregate ids never collide with standalone package ids', () => { const aggregateIds = new Set(AGGREGATES.flatMap(idsOf)) const standalonePatches = [] for (const base of ['packages']) { if (!existsSync(join(ROOT, base))) continue for (const entry of readdirSync(join(ROOT, base), { withFileTypes: true })) { if (!entry.isDirectory()) continue const patch = join(base, entry.name, 'cordis.patch.yml') const abs = join(ROOT, patch) try { readFileSync(abs) } catch { continue } const normPatch = patch.replaceAll('\\', '/') if (normPatch === 'packages/dsh-web-all/cordis.patch.yml') continue standalonePatches.push(patch) } } assert.ok(standalonePatches.length > 10, 'expected to scan the standalone packages') for (const patch of standalonePatches) { for (const id of idsOf(patch)) { assert.ok(!aggregateIds.has(id), `aggregate id "${id}" collides with standalone row in ${patch}`) } } }) test('no aggregate deps entry resolves to a private workspace package', () => { const aggregates = [] for (const base of ['packages']) { if (!existsSync(join(ROOT, base))) continue for (const entry of readdirSync(join(ROOT, base), { withFileTypes: true })) { if (!entry.isDirectory()) continue const yml = join(base, entry.name, 'aggregate.yml') if (existsSync(join(ROOT, yml))) aggregates.push(yml) } } assert.ok(aggregates.length >= 1, 'expected at least one aggregate manifest') for (const yml of aggregates) { let section = null for (const raw of readFileSync(join(ROOT, yml), 'utf8').split(/\r?\n/)) { const line = raw.trim() if (!line || line.startsWith('#')) continue const sectionMatch = line.match(/^[A-Za-z0-9_-]+:\s*$/) if (sectionMatch) { section = line.slice(0, -1) continue } if (section !== 'deps' || !line.startsWith('- ')) continue const entry = line.slice(2).trim().replace(/\s+#.*$/, '') const pkgPath = join(ROOT, dirname(yml), entry, 'package.json') const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) assert.notEqual(pkg.private, true, 'aggregate deps entry is private: ' + yml + ' -> ' + entry) } } }) test('web-ui-all leaves the unbundled dsh-better-sidebar out of the patch', () => { const patch = readFileSync(join(ROOT, 'packages/dsh-web-all/cordis.patch.yml'), 'utf8') // Alpha-branch decision (2026-09-17): the plugin's 0.19.1 peers declare // ^0.1.5-rc.1, which does not cover this branch's 0.1.7-rc.2 cohort, so the // aggregate neither mounts nor depends on it. Re-add the row in aggregate.yml // together with this assertion when the branch bundles it again. assert.doesNotMatch(patch, /^ {4}- id: web-ui-better-sidebar$/m, 'dsh-better-sidebar must not be a bundled row on the alpha branch') assert.doesNotMatch(patch, /^ {6}name: 'dsh-better-sidebar'$/m) }) test('web-ui-all does not mount the dsh-client-runtime-dependent @mlgbnb/dsh-archive-manager', () => { const patch = readFileSync(join(ROOT, 'packages/dsh-web-all/cordis.patch.yml'), 'utf8') // @mlgbnb/dsh-archive-manager imports the removed @deepseek-ai/dsh-client-runtime // face, so on the alpha.2 cohort the host loader cannot resolve its entry and // the whole boot fails. Its latest upstream build is still 1.0.7, so it stays // excluded; re-add the row and this assertion together with package.json deps // when upstream ships an alpha.2-compatible build. assert.doesNotMatch(patch, /^ {4}- id: web-ui-archive-manager$/m, '@mlgbnb/dsh-archive-manager must not be mounted on the alpha.2 cohort') }) test('web-ui-all keeps the removed doctor/describe-image plugins out but their tombstone exports alive', () => { // 2026-09-23 product decision: dsh-doctor and dsh-tool-describe-image were // removed from the family. No patch row or workspace dependency may name // them again without an explicit re-adoption decision; their shell subpath // exports stay as tombstones so a stale profile row fails soft instead of // aborting the boot with ERR_PACKAGE_PATH_NOT_EXPORTED. const patch = readFileSync(join(ROOT, 'packages/dsh-web-all/cordis.patch.yml'), 'utf8') assert.doesNotMatch(patch, /web-ui-(doctor|describe-image)/, 'removed plugins must not reappear as patch rows') const pkg = JSON.parse(readFileSync(join(ROOT, 'packages/dsh-web-all/package.json'), 'utf8')) assert.ok(!('@linxin666/dsh-doctor' in pkg.dependencies), 'dsh-doctor must not be a workspace dependency') assert.ok(!('@linxin666/dsh-tool-describe-image' in pkg.dependencies), 'dsh-tool-describe-image must not be a workspace dependency') assert.equal(pkg.exports['./doctor'], './lib/shells/shell.js', 'doctor tombstone export must survive') assert.equal(pkg.exports['./describe-image'], './lib/shells/shell.js', 'describe-image tombstone export must survive') }) test('web-ui-all ships the opt-in family rows disabled by default', () => { // The manifest's inactive list renders trailing bare "disabled: true" // overrides; users opt in per row in the plugin manager (a user-layer // "disabled: false" override wins over the bundle default). const yml = readFileSync(join(ROOT, 'packages/dsh-web-all/aggregate.yml'), 'utf8') let section = null const inactive = [] for (const raw of yml.split(/\r?\n/)) { const line = raw.trim() if (!line || line.startsWith('#')) continue const sectionMatch = line.match(/^[A-Za-z0-9_-]+:\s*$/) if (sectionMatch) { section = line.slice(0, -1) continue } if (section === 'inactive' && line.startsWith('- ')) inactive.push(line.slice(2).trim()) } assert.ok(inactive.length > 0, 'aggregate.yml should declare inactive opt-in rows') const patch = readFileSync(join(ROOT, 'packages/dsh-web-all/cordis.patch.yml'), 'utf8') for (const id of inactive) { assert.match(patch, new RegExp('^- id: ' + id + '\\n disabled: true$', 'm'), 'inactive row missing its disabled override: ' + id) } }) test('family shell rows keep the real plugin config at the row config root', () => { // The Host settings surface edits an entry's OWN Config schema, so a // shell-wrapped family row must present the family plugin's fields where a // standalone install of that package keeps them: beside the shell's own // `plugin` key, never nested under a second `config:` key (which the // settings surface cannot address, and which left a shelled row silently // unconfigurable while its card showed an editable form). const patch = readFileSync(join(ROOT, 'packages/dsh-web-all/cordis.patch.yml'), 'utf8') const lines = patch.split(/\r?\n/) let checked = 0 for (let i = 0; i < lines.length; i++) { const name = lines[i].match(/^ name: '@linxin666\/dsh-web-all\/(\S+)'$/) if (!name) continue assert.equal(lines[i + 1], ' config:', 'shell row ' + name[1] + ' must carry a config block') assert.match(lines[i + 2] ?? '', /^ plugin: '@linxin666\/[^']+'$/, 'shell row ' + name[1] + ' must name the real plugin first') for (let j = i + 3; j < lines.length && /^ {8}\S/.test(lines[j]); j++) { assert.doesNotMatch(lines[j], /^ {8}config:/, 'shell row ' + name[1] + ' nests the family config one level too deep') } checked += 1 } assert.ok(checked > 10, 'expected the generated patch to carry the family rows, found ' + checked) }) test('web-ui-all declares no retire target the current cohort no longer mounts', () => { // The official bundles alpha.2 ships mount no row this aggregate supersedes: // the official archived-sessions page (`ui-settings-unarchive-sessions`) is // gone from dsh-web-app, so a retire override for it changed nothing while // making the loader warn `patch: entry ... not found` on every profile boot. // `dsh-session-archive` owning the official `archived-sessions` section id // (2026-09-15 native-first decision) stands on its own. Declaring a target // again requires proving the foreign row exists in the SAME cohort with // `dsh --profile --dump-config`, then updating this test. const yml = readFileSync(join(ROOT, 'packages/dsh-web-all/aggregate.yml'), 'utf8') let section = null const retire = [] for (const raw of yml.split(/\r?\n/)) { const line = raw.trim() if (!line || line.startsWith('#')) continue const sectionMatch = line.match(/^[A-Za-z0-9_-]+:\s*$/) if (sectionMatch) { section = line.slice(0, -1) continue } if (section === 'retire' && line.startsWith('- ')) retire.push(line.slice(2).trim()) } assert.deepEqual(retire, [], 'retire targets must be verified against the installed cohort before being declared') const patch = readFileSync(join(ROOT, 'packages/dsh-web-all/cordis.patch.yml'), 'utf8') assert.doesNotMatch(patch, /^- id: ui-settings-unarchive-sessions$/m, 'the aggregate must not patch a foreign row no bundle mounts') }) test('web-ui-all leaves the deprecated @morlay/better-session integration out', () => { const patch = readFileSync(join(ROOT, 'packages/dsh-web-all/cordis.patch.yml'), 'utf8') // The deprecated integration was removed from the aggregate; these rows must // never come back without an explicit re-adoption decision (see the // simplification note removing better-session). assert.doesNotMatch(patch, /@morlay\//, 'the deprecated better-session integration must not reappear in the aggregate patch') assert.doesNotMatch(patch, /^- id: web-ui-(session-branch|session-rdb|conversation-message-actions)$/m, 'better-session sub-plugin rows must not mount') assert.doesNotMatch(patch, /@linxin666\/dsh-perf/, 'the removed dsh-perf plugin must not reappear in the aggregate patch') })