From 9b75f5e26e460880c617ffd809057eb700233ae4 Mon Sep 17 00:00:00 2001 From: mrbobbytables Date: Wed, 21 Oct 2026 11:11:29 -0500 Subject: [PATCH 1/2] test: cover compiler and Actions configuration integration Exercise the real configured Docusaurus compiler against security and benign content, and import the actual mutation configuration under explicit Actions events. Keep legacy compatibility cases distinct from the current site settings. Signed-off-by: mrbobbytables Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- knip.config.js | 3 - package-lock.json | 2 + package.json | 2 + tests/mdx-active-content.test.mjs | 141 ++++++++++++++++++++++++++++++ tests/stryker-config.test.mjs | 28 ++++++ 5 files changed, 173 insertions(+), 3 deletions(-) diff --git a/knip.config.js b/knip.config.js index 6a4e0367..22f19c7f 100644 --- a/knip.config.js +++ b/knip.config.js @@ -38,9 +38,6 @@ const config = { // Run through `npx` inside xargs pipelines in package.json scripts. 'markdown-link-check', 'markdownlint-cli', - // Type-only JSDoc import in docusaurus.config.js and sidebars.js; installed - // by @docusaurus/preset-classic. - '@docusaurus/plugin-content-docs', ], ignoreIssues: { // Tests load it through importSource(), a dynamic import knip cannot follow. diff --git a/package-lock.json b/package-lock.json index 8574a663..e62915ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,9 @@ "devDependencies": { "@axe-core/playwright": "^4.14.0", "@docusaurus/faster": "^3.10.2", + "@docusaurus/mdx-loader": "3.10.2", "@docusaurus/module-type-aliases": "3.10.2", + "@docusaurus/plugin-content-docs": "3.10.2", "@docusaurus/types": "3.10.2", "@eslint/js": "^9.39.5", "@jridgewell/trace-mapping": "^0.3.31", diff --git a/package.json b/package.json index e9796e9b..f25aa789 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,9 @@ "devDependencies": { "@axe-core/playwright": "^4.14.0", "@docusaurus/faster": "^3.10.2", + "@docusaurus/mdx-loader": "3.10.2", "@docusaurus/module-type-aliases": "3.10.2", + "@docusaurus/plugin-content-docs": "3.10.2", "@docusaurus/types": "3.10.2", "@eslint/js": "^9.39.5", "@jridgewell/trace-mapping": "^0.3.31", diff --git a/tests/mdx-active-content.test.mjs b/tests/mdx-active-content.test.mjs index 273db7a6..be41b970 100644 --- a/tests/mdx-active-content.test.mjs +++ b/tests/mdx-active-content.test.mjs @@ -1,5 +1,11 @@ import assert from 'node:assert/strict'; import test from 'node:test'; +import { fileURLToPath } from 'node:url'; +import { resolve } from 'node:path'; +import { loadSiteConfig } from '@docusaurus/core/lib/server/config.js'; +import { compileToJSX } from '@docusaurus/mdx-loader/lib/utils.js'; +import { DEFAULT_OPTIONS } from '@docusaurus/plugin-content-docs/lib/options.js'; +import { parseSync } from '@swc/core'; import { findActiveContent } from '../scripts/lib/mdx-active-content.mjs'; const reasons = (markdown) => findActiveContent(markdown).map((f) => f.reason); @@ -677,3 +683,138 @@ test('leaves an ordinary admonition and a fenced copy of the payload alone', () ].join('\n'); assert.deepEqual(reasons(body), []); }); + +const siteDir = fileURLToPath(new URL('../', import.meta.url)); +const { siteConfig } = await loadSiteConfig({ siteDir }); +const classicPreset = siteConfig.presets.find( + (preset) => Array.isArray(preset) && preset[0] === 'classic', +); +const compilerOptions = { + ...DEFAULT_OPTIONS, + ...classicPreset[1].docs, + siteDir, + staticDirs: siteConfig.staticDirectories.map((dir) => resolve(siteDir, dir)), + markdownConfig: siteConfig.markdown, +}; + +// Inspect the real compiler's JavaScript, not the gate's parser or rewrite +// helpers. Rejected MDX is compiled but never evaluated. +async function compiledContent(body, options = compilerOptions) { + const result = await compileToJSX({ + fileContent: body, + filePath: resolve(siteDir, 'docs/architectures/_security-regression.md'), + frontMatter: {}, + options, + compilerName: 'server', + }); + const urls = []; + const text = []; + function visit(node) { + if (!node || typeof node !== 'object') return; + if (node.type === 'StringLiteral') text.push(node.value); + if ( + node.type === 'KeyValueProperty' && + ['href', 'src'].includes(node.key.value) && + node.value.type === 'StringLiteral' + ) + urls.push([node.key.value, node.value.value]); + for (const value of Object.values(node)) { + if (Array.isArray(value)) value.forEach(visit); + else visit(value); + } + } + visit(parseSync(result.content, { syntax: 'ecmascript' })); + return { urls, text }; +} + +const COMPILER_REJECTED = [ + [ + 'whole-document fence pairing exposes a remote image', + '```mdx-code-block\n\n````\n\n```', + ['src', 'https://beacon.invalid/px.png'], + 'disallowed element ', + ], + [ + 'unwrapping exposes an encoded URL in an admonition below a heading', + '## Overview\n\n```mdx-code-block\n:::note[x](javascript:alert(1))\n```', + ['href', 'javascript:alert(1)'], + 'disallowed URL scheme javascript:', + ], + [ + 'GFM table delimiters expose an image inside an apparent code span', + '| a | b |\n| - | - |\n| `x | ` |', + ['src', 'https://beacon.invalid/px.png'], + 'disallowed element ', + ], + [ + 'duplicate definitions resolve to the first destination', + '[x][d]\n\n[d]: javascript:alert(1)\n[d]: https://www.cncf.io/', + ['href', 'javascript:alert(1)'], + 'disallowed URL scheme javascript:', + ], +]; + +for (const [name, body, url, reason] of COMPILER_REJECTED) { + test(`the publish gate rejects what Docusaurus makes active: ${name}`, async () => { + assert.deepEqual((await compiledContent(body)).urls, [url]); + assert.ok(reasons(body).includes(reason)); + }); +} + +const COMPILER_ACCEPTED = [ + [ + 'headings, modern admonitions and reference links', + '## Overview\n\n:::note[Safe title]\n\n[Read more][d]\n\n:::\n\n[d]: https://www.cncf.io/', + [['href', 'https://www.cncf.io/']], + 'Read more', + ], + [ + 'ordinary fences keep a legacy admonition payload inert', + '```\n:::note x](javascript:alert(1))\n```', + [], + ':::note x](javascript:alert(1))', + ], + [ + 'inline code preserves JSX as text', + 'Use `` as an example.', + [], + '', + ], + [ + 'YAML fences preserve braces and angle brackets', + '```yaml\nkey: {a: 1}\nexample: \n```', + [], + 'key: {a: 1}\nexample: ', + ], +]; + +for (const [name, body, urls, text] of COMPILER_ACCEPTED) { + test(`the publish gate and Docusaurus preserve valid content: ${name}`, async () => { + assert.deepEqual(reasons(body), []); + const compiled = await compiledContent(body); + assert.deepEqual(compiled.urls, urls); + assert.ok(compiled.text.some((value) => value.includes(text))); + }); +} + +test('the gate also rejects a composed bypass when legacy compatibility is enabled', async () => { + // future.v4 disables these rewrites on the current site. Exercise opt-in + // compatibility explicitly rather than claiming legacy titles are live now. + const options = { + ...compilerOptions, + markdownConfig: { + ...siteConfig.markdown, + mdx1Compat: { + ...siteConfig.markdown.mdx1Compat, + headingIds: true, + admonitions: true, + }, + }, + }; + const body = + '## Overview {#overview}\n\n```mdx-code-block\n:::note x](javascript:alert(1))\n````\n\n```'; + assert.deepEqual((await compiledContent(body, options)).urls, [ + ['href', 'javascript:alert(1)'], + ]); + assert.ok(reasons(body).includes('disallowed URL scheme javascript:')); +}); diff --git a/tests/stryker-config.test.mjs b/tests/stryker-config.test.mjs index b142b0e6..4ca3ad02 100644 --- a/tests/stryker-config.test.mjs +++ b/tests/stryker-config.test.mjs @@ -3,6 +3,7 @@ // nothing, so a stale list would quietly stop explaining the run. import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; import { existsSync, readdirSync, readFileSync } from 'node:fs'; import test from 'node:test'; @@ -49,3 +50,30 @@ test('breakThreshold stands down for the full-tree report lanes', () => { assert.equal(breakThreshold('schedule'), null); assert.equal(breakThreshold('workflow_dispatch'), null); }); + +for (const [eventName, expected] of [ + [undefined, 83], + ['pull_request', 83], + ['merge_group', null], + ['schedule', null], + ['workflow_dispatch', null], +]) { + test(`exported mutation config selects the threshold for ${eventName ?? 'local runs'}`, () => { + const env = { ...process.env }; + delete env.GITHUB_EVENT_NAME; + if (eventName !== undefined) env.GITHUB_EVENT_NAME = eventName; + const run = spawnSync( + process.execPath, + [ + '--input-type=module', + '--eval', + `import config from ${JSON.stringify(new URL('../stryker.config.mjs', import.meta.url).href)}; +console.log(JSON.stringify(config.thresholds.break));`, + ], + { encoding: 'utf8', env }, + ); + assert.equal(run.error, undefined); + assert.equal(run.status, 0, run.stderr); + assert.equal(JSON.parse(run.stdout), expected); + }); +} From 980631f3faf25172cace7594eb722318603762bc Mon Sep 17 00:00:00 2001 From: mrbobbytables Date: Wed, 21 Oct 2026 11:26:05 -0500 Subject: [PATCH 2/2] docs: clarify integration-boundary evidence in ADR 0007 Signed-off-by: mrbobbytables Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...o-end-coverage-to-user-reachable-states.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/adr/0007-scope-end-to-end-coverage-to-user-reachable-states.md b/adr/0007-scope-end-to-end-coverage-to-user-reachable-states.md index 63d39a56..aef957a8 100644 --- a/adr/0007-scope-end-to-end-coverage-to-user-reachable-states.md +++ b/adr/0007-scope-end-to-end-coverage-to-user-reachable-states.md @@ -76,6 +76,25 @@ Option 3. 4. **The floors do not move.** The browser lane keeps 100% source lines, 96% source regions and `--require-source-files`; the unit lane keeps 100% / 100%. +## Clarification: integration-boundary evidence (2026-10-21) + +Decision 3 requires surviving-mutant evidence for assertion gaps in production +logic covered by the configured mutation run. It does not require inventing a +hand-constructed mutation or another mutation harness to qualify an integration +boundary the configured mutation scope or runner does not exercise. + +For such boundaries, including root configuration, Actions event environments, +and dependency/compiler contracts, a finding may instead cite a reproducible +integration failure or a documented historical regression. It must identify the +production failure mode, explain why the mutation run does not exercise that +boundary, and add a regression test against the actual consumer or exported +configuration rather than only a helper or a duplicate parser. + +This clarification does not relax decisions 1, 2 or 4, or ADR 0006's +restrictions on test-tree guards. It does not authorize data-shape browser +fixture builds, change coverage floors, or replace surviving-mutant evidence for +assertion gaps within mutation-covered production logic. + ## Consequences - Hive `quality` findings that do not meet decision 2 are closed as not planned