dsh-standards 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,99 +0,0 @@
1
- import { test } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { spawnSync } from 'node:child_process'
4
- import { fileURLToPath } from 'node:url'
5
- import path from 'node:path'
6
-
7
- const HERE = path.dirname(fileURLToPath(import.meta.url))
8
- const STD = path.resolve(HERE, '..', '..')
9
- const SCRIPT = path.join(STD, '40-checker', 'scripts', 'level-audit.mjs')
10
- const GOOD = path.join(STD, '40-checker', 'fixtures', 'good')
11
-
12
- test('level-audit: static ENTRY is recognized, FLOW/BOOST are not upgraded', () => {
13
- const run = spawnSync(process.execPath, [SCRIPT, GOOD, '--format', 'json'], { encoding: 'utf8', timeout: 30_000 })
14
- assert.equal(run.status, 0, run.stderr)
15
- const result = JSON.parse(run.stdout)
16
- assert.equal(result.schema, 'dsh-level-audit/1')
17
- assert.equal(result.levelsSpec.schema, 'dsh-levels/1')
18
- assert.equal(result.levels.entry.state, 'recognized')
19
- assert.equal(result.levels.flow.state, 'not-run')
20
- assert.equal(result.levels.boost.state, 'not-measured')
21
- assert.deepEqual(result.notClaimed.includes('certification'), true)
22
- })
23
-
24
- test('level-audit: a missing package exits 3 and does not print a fake pass', () => {
25
- const run = spawnSync(process.execPath, [SCRIPT, '/no/such/package', '--format', 'json'], { encoding: 'utf8', timeout: 30_000 })
26
- assert.equal(run.status, 3)
27
- assert.match(run.stderr, /not a package directory/)
28
- })
29
-
30
- import fs from 'node:fs'
31
- import os from 'node:os'
32
-
33
- const BOOST_IDS = ['LEV-1', 'LEV-2', 'LEV-3', 'LEV-4', 'LEV-5', 'LEV-6', 'LEV-7', 'LEV-8']
34
-
35
- function writeBoostEvidence(rows) {
36
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dsh-boost-evidence-'))
37
- const file = path.join(dir, 'boost.json')
38
- fs.writeFileSync(file, JSON.stringify(rows))
39
- return { dir, file, cleanup: () => fs.rmSync(dir, { recursive: true, force: true }) }
40
- }
41
-
42
- function validCard(expiresAt = '2099-01-01', generatedAt = '2026-11-01') {
43
- return {
44
- subject: 'fixture@1.0.0',
45
- host: 'fixture-host',
46
- method: 'fixture',
47
- scope: 'fixture',
48
- command: 'node fixture.mjs',
49
- result: 'pass',
50
- negativeControl: 'control fails as expected',
51
- generatedAt,
52
- expiresAt,
53
- recordHash: `sha256:${'a'.repeat(64)}`,
54
- claimLevel: 'self-reported',
55
- issuer: 'fixture',
56
- selfAssessed: true,
57
- }
58
- }
59
-
60
- test('level-audit: status-only BOOST rows are not measured without evidence cards', () => {
61
- const rows = Object.fromEntries(BOOST_IDS.map(id => [id, { status: 'measured', evidence: 'looks fine' }]))
62
- const fixture = writeBoostEvidence(rows)
63
- try {
64
- const run = spawnSync(process.execPath, [SCRIPT, GOOD, '--format', 'json', '--boost-evidence', fixture.file], { encoding: 'utf8', timeout: 30_000 })
65
- assert.equal(run.status, 0, run.stderr)
66
- const result = JSON.parse(run.stdout)
67
- assert.equal(result.levels.boost.state, 'not-measured')
68
- assert.equal(result.levels.boost.practices['LEV-1'].card.valid, false)
69
- } finally {
70
- fixture.cleanup()
71
- }
72
- })
73
-
74
- test('level-audit: BOOST is measured only with eight valid evidence cards', () => {
75
- const rows = Object.fromEntries(BOOST_IDS.map(id => [id, { status: 'measured', evidenceCard: validCard() }]))
76
- const fixture = writeBoostEvidence(rows)
77
- try {
78
- const run = spawnSync(process.execPath, [SCRIPT, GOOD, '--format', 'json', '--boost-evidence', fixture.file], { encoding: 'utf8', timeout: 30_000 })
79
- assert.equal(run.status, 0, run.stderr)
80
- const result = JSON.parse(run.stdout)
81
- assert.equal(result.levels.boost.state, 'measured')
82
- assert.equal(result.levels.boost.validCards, 8)
83
- } finally {
84
- fixture.cleanup()
85
- }
86
- })
87
-
88
- test('level-audit: expired BOOST cards are stale, not measured', () => {
89
- const rows = Object.fromEntries(BOOST_IDS.map(id => [id, { status: 'measured', evidenceCard: validCard('2020-01-01', '2019-01-01') }]))
90
- const fixture = writeBoostEvidence(rows)
91
- try {
92
- const run = spawnSync(process.execPath, [SCRIPT, GOOD, '--format', 'json', '--boost-evidence', fixture.file], { encoding: 'utf8', timeout: 30_000 })
93
- assert.equal(run.status, 0, run.stderr)
94
- const result = JSON.parse(run.stdout)
95
- assert.equal(result.levels.boost.state, 'stale')
96
- } finally {
97
- fixture.cleanup()
98
- }
99
- })
@@ -1,31 +0,0 @@
1
- import { test } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { readFileSync } from 'node:fs'
4
- import { fileURLToPath } from 'node:url'
5
- import path from 'node:path'
6
-
7
- const HERE = path.dirname(fileURLToPath(import.meta.url))
8
- const STD = path.resolve(HERE, '..', '..')
9
- const levels = JSON.parse(readFileSync(path.join(STD, 'spec', 'levels.json'), 'utf8'))
10
-
11
- test('levels: exactly three levels with the expected ids and analogies', () => {
12
- assert.equal(levels.schema, 'dsh-levels/1')
13
- assert.equal(levels.nonOfficial, true)
14
- assert.deepEqual(levels.levels.map(level => level.id), ['entry', 'flow', 'boost'])
15
- assert.deepEqual(levels.levels.map(level => level.analogy), ['GB', 'GB/T', 'GB/Z'])
16
- })
17
-
18
- test('levels: ENTRY has G0-G5, FLOW has J1-J8, BOOST has LEV-1..LEV-8', () => {
19
- const gates = Object.fromEntries(levels.levels.map(level => [level.id, level.gates.map(gate => gate.id)]))
20
- assert.deepEqual(gates.entry, ['G0', 'G1', 'G2', 'G3', 'G4', 'G5'])
21
- assert.deepEqual(gates.flow, ['J1', 'J2', 'J3', 'J4', 'J5', 'J6', 'J7', 'J8'])
22
- assert.deepEqual(gates.boost, ['LEV-1', 'LEV-2', 'LEV-3', 'LEV-4', 'LEV-5', 'LEV-6', 'LEV-7', 'LEV-8'])
23
- })
24
-
25
- test('levels: evidence states include not-run and never imply a pass', () => {
26
- const all = new Set(levels.levels.flatMap(level => level.evidenceStates))
27
- assert.equal(all.has('not-run'), true)
28
- assert.equal(all.has('runtime'), true)
29
- assert.equal(all.has('measured'), true)
30
- assert.equal(levels.axes.rules.some(rule => rule.includes('skip is not a pass')), true)
31
- })
@@ -1,117 +0,0 @@
1
- import test from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import fs from 'node:fs'
4
- import os from 'node:os'
5
- import path from 'node:path'
6
- import { fileURLToPath } from 'node:url'
7
- import { validateClauseMap } from '../scripts/clause-map.mjs'
8
- import { effective } from '../scripts/stale-check.mjs'
9
- import { appendRecord, verifyChain } from '../scripts/evidence-chain.mjs'
10
- import { buildDashboard } from '../scripts/dashboard.mjs'
11
- import { inspect } from '../scripts/surveillance.mjs'
12
-
13
- const HERE = path.dirname(fileURLToPath(import.meta.url))
14
- const ROOT = path.resolve(HERE, '..', '..')
15
- const read = (rel) => JSON.parse(fs.readFileSync(path.join(ROOT, rel), 'utf8'))
16
-
17
- test('clause map: the checked-in map covers every gate', () => {
18
- const result = validateClauseMap({ levels: read('spec/levels.json'), clauses: read('spec/clauses.json') })
19
- assert.deepEqual(result.errors, [])
20
- assert.deepEqual(result.covered.entry.sort(), ['G0', 'G1', 'G2', 'G3', 'G4', 'G5'])
21
- })
22
-
23
- test('clause map: a missing gate is an error', () => {
24
- const levels = read('spec/levels.json')
25
- const clauses = read('spec/clauses.json')
26
- clauses.clauses = clauses.clauses.filter((c) => !c.gates.includes('G4'))
27
- const result = validateClauseMap({ levels, clauses })
28
- assert.ok(result.errors.some((e) => /G4/.test(e)))
29
- })
30
-
31
- test('stale: an active expired record is expired', () => {
32
- const r = effective({ status: 'active', expiresAt: '2020-01-01' }, new Date('2026-11-01T00:00:00Z'))
33
- assert.equal(r.status, 'expired')
34
- assert.equal(r.stale, true)
35
- })
36
-
37
- test('stale: a draft is not valid but not “stale”', () => {
38
- const r = effective({ status: 'draft', expiresAt: '2030-01-01' }, new Date('2026-11-01T00:00:00Z'))
39
- assert.equal(r.status, 'draft')
40
- assert.equal(r.stale, false)
41
- })
42
-
43
- test('evidence chain: append then verify', () => {
44
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dsh-chain-'))
45
- const record = path.join(dir, 'record.json')
46
- fs.writeFileSync(record, '{"ok":true}')
47
- const chain = path.join(dir, 'chain.jsonl')
48
- appendRecord({ chain, record, ts: '2026-11-02T00:00:00Z' })
49
- appendRecord({ chain, record, ts: '2026-11-02T00:00:01Z' })
50
- const result = verifyChain(fs.readFileSync(chain, 'utf8').split('\n'))
51
- assert.deepEqual(result.errors, [])
52
- assert.equal(result.entries, 2)
53
- fs.rmSync(dir, { recursive: true, force: true })
54
- })
55
-
56
- test('evidence chain: tampering is detected', () => {
57
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dsh-chain-'))
58
- const record = path.join(dir, 'record.json')
59
- fs.writeFileSync(record, '{"ok":true}')
60
- const chain = path.join(dir, 'chain.jsonl')
61
- appendRecord({ chain, record, ts: '2026-11-02T00:00:00Z' })
62
- const lines = fs.readFileSync(chain, 'utf8').split('\n')
63
- lines[0] = lines[0].replace(/("recordSha256":")([0-9a-f])/, (m, a, b) => a + (b === '0' ? '1' : '0'))
64
- const result = verifyChain(lines)
65
- assert.ok(result.errors.length > 0)
66
- fs.rmSync(dir, { recursive: true, force: true })
67
- })
68
-
69
- test('dashboard: never claims official/security/production', () => {
70
- const d = buildDashboard(ROOT)
71
- assert.equal(d.schema, 'dsh-dashboard/1')
72
- assert.ok(d.notClaimed.includes('security audit'))
73
- assert.equal(d.flow.packages, 10)
74
- })
75
-
76
- test('surveillance: expired record becomes an issue', () => {
77
- const report = inspect({ attestations: [{ attestationId: 'DSH-ATT-2020-001', status: 'active', expiresAt: '2020-01-01', verifyUrl: 'https://example.com', claimLevel: 'self-reported' }] }, new Date('2026-11-01T00:00:00Z'))
78
- assert.ok(report.issues.some((i) => i.issues.includes('expired')))
79
- })
80
-
81
- test('round-robin: independent matching runs are comparable', async () => {
82
- const { compare } = await import('../scripts/round-robin.mjs')
83
- const expected = { good: { expected: 'warn' }, bad: { expected: 'fail' } }
84
- const r = compare({
85
- a: { runner: 'runner-a', results: { good: { status: 'warn' }, bad: { status: 'fail' } } },
86
- b: { runner: 'runner-b', results: { good: { status: 'warn' }, bad: { status: 'fail' } } },
87
- expected,
88
- })
89
- assert.equal(r.comparable, true)
90
- })
91
-
92
- test('round-robin: two not-run results are not comparable', async () => {
93
- const { compare } = await import('../scripts/round-robin.mjs')
94
- const r = compare({
95
- a: { runner: 'runner-a', results: { good: { status: 'not-run' } } },
96
- b: { runner: 'runner-b', results: { good: { status: 'not-run' } } },
97
- expected: { good: { expected: 'warn' } },
98
- })
99
- assert.equal(r.comparable, false)
100
- })
101
-
102
- test('flow index: aggregates result files without upgrading a verdict', async () => {
103
- const { buildFlowIndex } = await import('../scripts/flow-index.mjs')
104
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dsh-flow-index-'))
105
- const row = (id, pass, fail) => ({
106
- schema: 'dsh-flow-real/1', id, package: id, version: '1.0.0', source: 'fixture', why: 'test',
107
- verdicts: { J1: pass ? 'pass' : 'fail', J2: 'not-run', J4: 'not-run', J6: 'not-run' },
108
- classification: 'clean', conclusion: 'partial', caveats: [], summary: { checks: { pass, fail, blocked: 0, notRun: 6 }, verdicts: { pass: pass ? 1 : 0, fail: fail, blocked: 0, partial: 0, notRun: 3 } },
109
- })
110
- fs.writeFileSync(path.join(dir, 'a.json'), JSON.stringify(row('a', 1, 0)))
111
- fs.writeFileSync(path.join(dir, 'b.json'), JSON.stringify(row('b', 0, 1)))
112
- const index = buildFlowIndex(dir)
113
- assert.equal(index.totals.packages, 2)
114
- assert.equal(index.totals.checks.fail, 1)
115
- assert.equal(index.totals.verdicts.fail, 1)
116
- fs.rmSync(dir, { recursive: true, force: true })
117
- })
@@ -1,40 +0,0 @@
1
- import test from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { checkCredentials } from '../src/declarations.mjs'
4
- import { validateCommitments, validateDisclosure, COMMITMENT_IDS } from '../src/permissions.mjs'
5
-
6
- const manifest = {
7
- dsh: {
8
- commitmentsVersion: 1,
9
- commitments: Object.fromEntries(COMMITMENT_IDS.map(id => [id, true])),
10
- disclosure: {
11
- endpoints: ['https://api.example.com'],
12
- retention: 'none',
13
- systemPrompt: 'unchanged',
14
- },
15
- capabilities: ['network:egress'],
16
- },
17
- }
18
-
19
- test('all 9 commitments validate as booleans', () => {
20
- const details = validateCommitments(manifest)
21
- assert.equal(details.length, 1)
22
- assert.equal(details[0].result, 'PASS')
23
- })
24
-
25
- test('missing commitment is a warning, not a failure', () => {
26
- const details = validateCommitments({ dsh: { commitments: { 'no-session-upload': true } } })
27
- assert.ok(details.some(item => item.result === 'WARN' && item.reason.includes('missing commitment')))
28
- })
29
-
30
- test('disclosure endpoints must be https URLs', () => {
31
- const details = validateDisclosure({ dsh: { disclosure: { endpoints: ['http://insecure.example.com'] } } })
32
- assert.ok(details.some(item => item.result === 'WARN'))
33
- })
34
-
35
- test('checkCredentials reads both declaration fields', () => {
36
- const check = checkCredentials(manifest, [])
37
- assert.equal(check.result, 'PASS')
38
- assert.ok(check.details.some(item => item.item === 'C-12.6' && item.result === 'PASS'))
39
- assert.ok(check.details.some(item => item.item === 'C-12.7' && item.result === 'PASS'))
40
- })
@@ -1,300 +0,0 @@
1
- import test from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import fs from 'node:fs'
4
- import path from 'node:path'
5
- import { fileURLToPath } from 'node:url'
6
- import remediationSpec from '../../spec/remediation.json' with { type: 'json' }
7
- import packageJson from '../../package.json' with { type: 'json' }
8
- import { DOCS_BASE, joinAdvice } from '../src/remediation.mjs'
9
- import { buildReport } from '../src/report.mjs'
10
- import { runManifestChecks } from '../src/manifest.mjs'
11
- import { checkCapabilities, checkCredentials, checkImportTimeAdvisory, checkLabels } from '../src/declarations.mjs'
12
- import { readSources } from '../src/util.mjs'
13
-
14
- const here = path.dirname(fileURLToPath(import.meta.url))
15
- const srcDir = path.join(here, '..', 'src')
16
- const fixturesDir = path.join(here, '..', 'fixtures')
17
- const guideDocs = process.env.DSH_GUIDE_DOCS
18
- ? path.resolve(process.env.DSH_GUIDE_DOCS)
19
- : path.resolve(here, '..', '..', '..', 'dsh-plugin-guide', 'docs')
20
- const RULES = remediationSpec
21
-
22
- function slugify(text) {
23
- return String(text)
24
- .trim()
25
- .toLowerCase()
26
- .replace(/[^\p{L}\p{N}\s_-]/gu, '')
27
- .replace(/\s+/g, '-')
28
- }
29
-
30
- function headingSlugs(text) {
31
- const slugs = new Set()
32
- let fence = null
33
- for (const line of text.split(/\r?\n/)) {
34
- const fenceMatch = /^\s*(```+|~~~+)/.exec(line)
35
- if (fenceMatch) {
36
- if (fence === null) fence = fenceMatch[1][0]
37
- else if (fence === fenceMatch[1][0]) fence = null
38
- continue
39
- }
40
- if (fence !== null) continue
41
- // Explicit HTML anchors are valid targets too (older docs predate heading-only slugs).
42
- const explicit = /<a\s+id="([^"]+)"/.exec(line)
43
- if (explicit) slugs.add(explicit[1])
44
- const match = /^(#{1,6})\s+(.+?)\s*#*\s*$/.exec(line)
45
- if (match) slugs.add(slugify(match[2]))
46
- }
47
- return slugs
48
- }
49
-
50
- /** The key set the checker can emit: C-x[.y] literals, clause values, G0-G4. */
51
- function checkerUniverse() {
52
- const keys = new Set()
53
- for (const file of fs.readdirSync(srcDir).filter(name => name.endsWith('.mjs'))) {
54
- const text = fs.readFileSync(path.join(srcDir, file), 'utf8')
55
- for (const match of text.matchAll(/'((?:C-\d+)(?:\.\d+)*)'/g)) keys.add(match[1])
56
- for (const match of text.matchAll(/item:\s*'(G[0-4])'/g)) keys.add(match[1])
57
- }
58
- for (const gate of ['G0', 'G1', 'G2', 'G3', 'G4']) keys.add(gate)
59
- return keys
60
- }
61
-
62
- function runtimeNotRun() {
63
- const meta = {
64
- 'DSC-04': ['C-4', 'L1'], 'DSC-05': ['C-5', 'L1'], 'DSC-06': ['C-6', 'L1+L2'],
65
- 'DSC-07': ['C-7', 'L2'], 'DSC-08': ['C-8', 'L3'], 'DSC-09': ['C-9', 'L1'],
66
- }
67
- return Object.entries(meta).map(([id, [clause, layer]]) => ({
68
- id, clause, layer, method: 'M', advisory: false,
69
- result: 'ERROR', coverage: 'not-run', reason: 'test: runtime not run', details: [], evidence: [],
70
- }))
71
- }
72
-
73
- function staticReportFor(dir) {
74
- const result = runManifestChecks(dir)
75
- const sources = readSources(dir)
76
- const declaration = result.manifest
77
- ? [
78
- checkCapabilities(result.manifest, sources, result.patch),
79
- checkCredentials(result.manifest, sources),
80
- checkLabels(result.manifest, sources),
81
- ].map(item => (item.result === 'WARN' && ['DSC-11', 'DSC-13'].includes(item.id) ? { ...item, advisory: true } : item))
82
- : [
83
- { id: 'DSC-11', clause: 'C-11', layer: 'L0', method: 'M', advisory: false, result: 'ERROR', coverage: 'not-run', reason: 'manifest unavailable', details: [], evidence: [] },
84
- { id: 'DSC-12', clause: 'C-12', layer: 'L0', method: 'M', advisory: false, result: 'ERROR', coverage: 'not-run', reason: 'manifest unavailable', details: [], evidence: [] },
85
- { id: 'DSC-13', clause: 'C-13', layer: 'L0', method: 'M', advisory: false, result: 'ERROR', coverage: 'not-run', reason: 'manifest unavailable', details: [], evidence: [] },
86
- ]
87
- const checks = [
88
- ...result.checks,
89
- ...runtimeNotRun(),
90
- { id: 'DSC-14', clause: 'C-14', layer: 'L1', method: 'M', advisory: false, result: 'ERROR', coverage: 'not-run', reason: 'test: runtime not run', details: [], evidence: [] },
91
- checkImportTimeAdvisory(sources, result.entry?.relative),
92
- ...declaration,
93
- ]
94
- if (result.g4?.result === 'FAIL') {
95
- const dsc01 = checks.find(item => item.id === 'DSC-01')
96
- if (dsc01 && dsc01.result !== 'FAIL') {
97
- dsc01.result = 'FAIL'
98
- dsc01.reason = dsc01.reason ? `${dsc01.reason}; ${result.g4.reason}` : result.g4.reason
99
- dsc01.details = [...(dsc01.details ?? []), { item: 'G4', result: 'FAIL', reason: result.g4.reason }]
100
- }
101
- }
102
- return buildReport({
103
- subject: {
104
- mode: 'package', profile: 'package', kind: result.kind, kindInferred: true,
105
- name: result.manifest?.name ?? null, version: result.manifest?.version ?? null, path: dir,
106
- },
107
- anchor: { repo: 'test', commit: '0'.repeat(40), scanner: 'test', dsh: '0.2.2-alpha.2', cordis: '4.0.6-alpha.1', runtime: null },
108
- checks,
109
- g4: result.g4,
110
- scope: 'ds-c',
111
- profile: 'package',
112
- kind: result.kind,
113
- })
114
- }
115
-
116
- test('remediation table covers every checker key exactly', () => {
117
- const universe = checkerUniverse()
118
- const keys = new Set(Object.keys(RULES))
119
- const missing = [...universe].filter(key => !keys.has(key)).sort()
120
- const extra = [...keys].filter(key => !universe.has(key)).sort()
121
- assert.deepEqual(missing, [], `missing remediation rules: ${missing.join(', ')}`)
122
- assert.deepEqual(extra, [], `stale remediation rules: ${extra.join(', ')}`)
123
- assert.ok(universe.size > 0)
124
- })
125
-
126
- test('remediation rules have the required shape', () => {
127
- assert.equal(DOCS_BASE, 'https://github.com/PerryLink/dsh-plugin-guide/blob/v0.4.4/')
128
- assert.equal(packageJson.version, '1.2.0')
129
- assert.ok(Object.keys(RULES).length > 0)
130
- for (const [key, rule] of Object.entries(RULES)) {
131
- assert.match(key, /^(?:C-\d+(?:\.\d+)*|G[0-4])$/)
132
- for (const field of ['title', 'why', 'fix']) {
133
- assert.equal(typeof rule[field], 'string', `${key}.${field}`)
134
- assert.ok(rule[field].trim().length > 0, `${key}.${field} is empty`)
135
- }
136
- assert.match(rule.docs, /^docs\/[a-z]+\.md#[a-z0-9-]+$/, `${key}.docs`)
137
- assert.ok(['fail', 'warn', 'advisory'].includes(rule.severityHint), `${key}.severityHint`)
138
- }
139
- })
140
-
141
- test('advice on every fixture has a rule (no ruleMissing)', () => {
142
- const dirs = fs.readdirSync(fixturesDir, { withFileTypes: true })
143
- .filter(entry => entry.isDirectory())
144
- .map(entry => path.join(fixturesDir, entry.name))
145
- .filter(dir => fs.existsSync(path.join(dir, 'package.json')))
146
- assert.ok(dirs.length >= 20, `expected fixture packages, got ${dirs.length}`)
147
- let total = 0
148
- for (const dir of dirs) {
149
- const report = staticReportFor(dir)
150
- assert.ok(Array.isArray(report.advice), `${path.basename(dir)}: advice is not an array`)
151
- const missing = report.advice.filter(item => item.ruleMissing)
152
- assert.deepEqual(missing.map(item => `${item.source}:${item.key}`), [], `${path.basename(dir)}: ruleMissing`)
153
- for (const item of report.advice) {
154
- assert.ok(item.docsUrl, `${path.basename(dir)}: ${item.key} has no docsUrl`)
155
- assert.equal(item.docsUrl, `${DOCS_BASE}${RULES[item.key].docs}`, `${path.basename(dir)}: ${item.key} docsUrl`)
156
- assert.equal(item.version, packageJson.version)
157
- assert.ok(['FAIL', 'WARN'].includes(item.severity))
158
- }
159
- total += report.advice.length
160
- }
161
- assert.ok(total > 0, 'fixtures produced no advice; the test would be vacuous')
162
- })
163
-
164
- test('runtime FAIL without details falls back to check.clause', () => {
165
- const checks = [
166
- {
167
- id: 'DSC-04', clause: 'C-4', layer: 'L1', result: 'FAIL', coverage: 'full',
168
- reason: 'load failed: boom', details: [], evidence: ['index.mjs:1'], method: 'M', advisory: false,
169
- },
170
- {
171
- id: 'DSC-14', clause: 'C-14', layer: 'L1', result: 'FAIL', coverage: 'full',
172
- reason: 'runtime failed', details: [], evidence: ['index.mjs:1'], method: 'M', advisory: false,
173
- },
174
- ]
175
- const report = buildReport({
176
- subject: { mode: 'package', profile: 'package', kind: 'plugin', kindInferred: false, name: 'x', version: '1.0.0', path: here },
177
- anchor: { repo: 'x', commit: '0'.repeat(40), scanner: 'x', dsh: '0.2.2-alpha.2', cordis: '4.0.6-alpha.1', runtime: null },
178
- checks,
179
- scope: 'ds-c',
180
- profile: 'package',
181
- kind: 'plugin',
182
- })
183
- const c4 = report.advice.find(item => item.source === 'DSC-04')
184
- const c14 = report.advice.find(item => item.source === 'DSC-14')
185
- assert.equal(c4.key, 'C-4')
186
- assert.equal(c4.severity, 'FAIL')
187
- assert.equal(c4.version, '1.2.0')
188
- assert.deepEqual(c4.evidence, ['index.mjs:1'])
189
- assert.equal(c4.docsUrl, `${DOCS_BASE}docs/levels.md#entry`)
190
- assert.equal(c14.key, 'C-14')
191
- assert.equal(c14.docsUrl, `${DOCS_BASE}docs/entry.md#the-two-claims`)
192
- })
193
-
194
- test('advice uses detail severity even when the check was adjusted to WARN', () => {
195
- const checks = [
196
- { id: 'DSC-01', clause: 'C-1', layer: 'L0', result: 'PASS', coverage: 'full', reason: '', details: [], evidence: [] },
197
- {
198
- id: 'DSC-02', clause: 'C-2', layer: 'L0', result: 'FAIL', coverage: 'full', reason: '',
199
- details: [{ item: 'C-2.1', result: 'FAIL', reason: 'no engines', evidence: ['package.json#engines'] }],
200
- evidence: [], advisory: false,
201
- },
202
- { id: 'DSC-11', clause: 'C-11', layer: 'L0', result: 'PASS', coverage: 'full', reason: '', details: [], evidence: [] },
203
- { id: 'DSC-12', clause: 'C-12', layer: 'L0', result: 'PASS', coverage: 'full', reason: '', details: [], evidence: [] },
204
- { id: 'DSC-13', clause: 'C-13', layer: 'L0', result: 'PASS', coverage: 'full', reason: '', details: [], evidence: [] },
205
- { id: 'DSC-14', clause: 'C-14', layer: 'L1', result: 'PASS', coverage: 'full', reason: '', details: [], evidence: [] },
206
- ]
207
- const report = buildReport({
208
- subject: { mode: 'package', profile: 'package', kind: 'plugin', kindInferred: false, name: 'x', version: '1.0.0', path: here },
209
- anchor: { repo: 'x', commit: '0'.repeat(40), scanner: 'x', dsh: '0.2.2-alpha.2', cordis: '4.0.6-alpha.1', runtime: null },
210
- checks,
211
- scope: 'entry',
212
- profile: 'package',
213
- kind: 'plugin',
214
- })
215
- assert.equal(report.checks.find(item => item.id === 'DSC-02').result, 'WARN')
216
- const item = report.advice.find(entry => entry.key === 'C-2.1')
217
- assert.equal(item.severity, 'FAIL')
218
- assert.equal(item.source, 'DSC-02')
219
- assert.match(item.reason, /no engines/)
220
- })
221
-
222
- test('duplicate details merge evidence and count once per check and key', () => {
223
- const checks = [{
224
- id: 'DSC-11', clause: 'C-11', layer: 'L0', result: 'WARN', coverage: 'full', reason: '',
225
- details: [
226
- { item: 'C-11.2', result: 'WARN', reason: 'trigger tools', evidence: ['a.mjs:1'] },
227
- { item: 'C-11.1', result: 'WARN', reason: 'missing capabilities', evidence: ['package.json#dsh.capabilities'] },
228
- { item: 'C-11.2', result: 'WARN', reason: 'trigger web', evidence: ['b.mjs:2'] },
229
- ],
230
- evidence: [], advisory: true,
231
- }]
232
- const report = buildReport({
233
- subject: { mode: 'package', profile: 'package', kind: 'plugin', kindInferred: false, name: 'x', version: '1.0.0', path: here },
234
- anchor: { repo: 'x', commit: '0'.repeat(40), scanner: 'x', dsh: '0.2.2-alpha.2', cordis: '4.0.6-alpha.1', runtime: null },
235
- checks,
236
- scope: 'ds-c',
237
- profile: 'package',
238
- kind: 'plugin',
239
- })
240
- const merged = report.advice.find(item => item.key === 'C-11.2')
241
- assert.ok(merged)
242
- assert.equal(merged.occurrences, 2)
243
- assert.deepEqual(merged.evidence, ['a.mjs:1', 'b.mjs:2'])
244
- assert.equal(merged.advisory, true)
245
- assert.equal(report.advice.filter(item => item.source === 'DSC-11').length, 2)
246
- })
247
-
248
- test('buildReport includes advice by default and omits it on advice:false', () => {
249
- const base = {
250
- subject: { mode: 'package', profile: 'package', kind: 'plugin', kindInferred: false, name: 'x', version: '1.0.0', path: here },
251
- anchor: { repo: 'x', commit: '0'.repeat(40), scanner: 'x', dsh: '0.2.2-alpha.2', cordis: '4.0.6-alpha.1', runtime: null },
252
- checks: [],
253
- scope: 'ds-c',
254
- profile: 'package',
255
- kind: 'plugin',
256
- }
257
- const on = buildReport(base)
258
- const off = buildReport({ ...base, advice: false })
259
- assert.ok(Array.isArray(on.advice))
260
- assert.equal(on.advice.length, 0)
261
- assert.equal(Object.hasOwn(off, 'advice'), false)
262
- })
263
-
264
- test('every rule docs anchor exists in the local guide repository', t => {
265
- if (!fs.existsSync(guideDocs)) {
266
- t.skip(`guide docs not checked out at ${guideDocs}`)
267
- return
268
- }
269
- const cache = new Map()
270
- for (const [key, rule] of Object.entries(RULES)) {
271
- const hash = rule.docs.indexOf('#')
272
- const file = rule.docs.slice(0, hash)
273
- const anchor = rule.docs.slice(hash + 1)
274
- assert.ok(hash > 0 && !anchor.includes('#'), `${key}: docs must be <file>#<anchor>`)
275
- const relative = file.replace(/^docs\//, '')
276
- const absolute = path.join(guideDocs, relative)
277
- assert.ok(fs.existsSync(absolute), `${key}: missing guide file ${file}`)
278
- if (!cache.has(file)) cache.set(file, headingSlugs(fs.readFileSync(absolute, 'utf8')))
279
- assert.ok(cache.get(file).has(anchor), `${key}: missing anchor ${rule.docs}`)
280
- }
281
- })
282
-
283
- test('joinAdvice is pure and leaves the report untouched', () => {
284
- const checks = [{
285
- id: 'DSC-04', clause: 'C-4', layer: 'L1', result: 'FAIL', coverage: 'full',
286
- reason: 'load failed', details: [], evidence: ['index.mjs'], method: 'M', advisory: false,
287
- }]
288
- const report = {
289
- checks,
290
- standard: { id: 'DS-C', version: '1.0.0' },
291
- }
292
- const snapshot = JSON.stringify(report)
293
- const advice = joinAdvice(report, remediationSpec, DOCS_BASE)
294
- assert.equal(JSON.stringify(report), snapshot)
295
- assert.equal(advice.length, 1)
296
- assert.equal(advice[0].key, 'C-4')
297
- // A flat rule map (no version metadata) still gets the package version.
298
- const bare = joinAdvice(report, RULES, DOCS_BASE)
299
- assert.equal(bare[0].version, packageJson.version)
300
- })
@@ -1,32 +0,0 @@
1
- import test from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { compareVersions, parseVersion, satisfies, validRange } from '../src/semver.mjs'
4
-
5
- test('parseVersion accepts exact semver and prerelease', () => {
6
- assert.deepEqual(parseVersion('0.2.2-alpha.2'), { major: 0, minor: 2, patch: 2, prerelease: ['alpha', '2'] })
7
- assert.equal(parseVersion('^0.2.2'), null)
8
- })
9
-
10
- test('compareVersions orders release above prerelease', () => {
11
- assert.equal(compareVersions(parseVersion('0.2.2'), parseVersion('0.2.2-alpha.2')), 1)
12
- assert.equal(compareVersions(parseVersion('0.2.2-alpha.2'), parseVersion('0.2.2-alpha.1')), 1)
13
- assert.equal(compareVersions(parseVersion('0.2.0-rc.2'), parseVersion('0.2.0-0')), 1)
14
- })
15
-
16
- test('satisfies handles the ranges used by real plugins', () => {
17
- assert.equal(satisfies('0.2.2-alpha.2', '>=0.2.2-alpha.2 <0.3.0'), true)
18
- assert.equal(satisfies('0.2.2-alpha.2', '>=0.1.5-rc.1 <0.2.0-0 || >=0.2.0-rc.2 <0.3.0-0'), true)
19
- assert.equal(satisfies('0.2.2-alpha.2', '^0.2.2-alpha.2'), true)
20
- assert.equal(satisfies('0.2.2-alpha.2', '~0.2.2'), false)
21
- assert.equal(satisfies('0.2.2-alpha.2', '^0.2.2'), false)
22
- assert.equal(satisfies('0.2.2-alpha.2', '>=0.3.0'), false)
23
- assert.equal(satisfies('0.2.2-alpha.2', 'workspace:^'), false)
24
- assert.equal(satisfies('24.0.0', '^22.19.0 || >=24.0.0'), true)
25
- })
26
-
27
- test('validRange rejects empty and malformed ranges', () => {
28
- assert.equal(validRange('>=0.2.2 <0.3.0'), true)
29
- assert.equal(validRange(''), false)
30
- assert.equal(validRange('workspace:^'), false)
31
- assert.equal(validRange('not-a-range'), false)
32
- })