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,172 +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
- import fs from 'node:fs'
7
- import os from 'node:os'
8
-
9
- const HERE = path.dirname(fileURLToPath(import.meta.url))
10
- const STD = path.resolve(HERE, '..', '..')
11
- const SCRIPT = path.join(STD, '40-checker', 'scripts', 'entry-validate.mjs')
12
- const FIXTURES = path.join(STD, 'adoption', 'entry', 'fixtures')
13
-
14
- function run(name, withMeta = true) {
15
- const dir = path.join(FIXTURES, name)
16
- const args = [SCRIPT, dir, '--json']
17
- const meta = path.join(dir, 'meta.json')
18
- if (withMeta && fs.existsSync(meta)) args.push('--meta', meta)
19
- return spawnSync(process.execPath, args, { encoding: 'utf8', timeout: 30_000 })
20
- }
21
-
22
- function result(name, withMeta = true) {
23
- const out = run(name, withMeta)
24
- assert.ok(out.stdout, out.stderr)
25
- return { out, json: JSON.parse(out.stdout) }
26
- }
27
-
28
- test('entry-validate: the good fixture passes C1-C8', () => {
29
- const { out, json } = result('good')
30
- assert.equal(out.status, 0, out.stderr)
31
- assert.equal(json.conclusion.status, 'pass')
32
- assert.deepEqual(json.conclusion.failed, [])
33
- assert.deepEqual(json.conclusion.notRun, [])
34
- })
35
-
36
- test('entry-validate: missing meta is incomplete, never a pass', () => {
37
- const { out, json } = result('no-meta', false)
38
- assert.equal(out.status, 3)
39
- assert.equal(json.conclusion.status, 'incomplete')
40
- assert.ok(json.conclusion.notRun.includes('C1'))
41
- assert.ok(json.conclusion.notRun.includes('C7'))
42
- })
43
-
44
- const failures = [
45
- ['client-only', 'C2'],
46
- ['missing-patch', 'C2'],
47
- ['engines-star', 'C4'],
48
- ['client-no-export', 'C5'],
49
- ['no-metadata', 'C6'],
50
- ['no-version', 'C7'],
51
- ['forbidden-label', 'C8'],
52
- ]
53
-
54
- for (const [name, id] of failures) {
55
- test(`entry-validate: ${name} fails ${id}`, () => {
56
- const { out, json } = result(name)
57
- assert.equal(out.status, 2, out.stderr)
58
- assert.equal(json.conclusion.status, 'fail')
59
- assert.ok(json.conclusion.failed.includes(id), JSON.stringify(json.conclusion))
60
- })
61
- }
62
-
63
- test('entry-validate: a peer range is an accepted C4 source', () => {
64
- const { out, json } = result('peer-ok')
65
- assert.equal(out.status, 0, out.stderr)
66
- assert.equal(json.checks.find(row => row.id === 'C4').status, 'pass')
67
- })
68
-
69
- test('entry-validate: a declared but uncommitted build output is a warning, not a fail', () => {
70
- const { out, json } = result('build-output')
71
- assert.equal(out.status, 0, out.stderr)
72
- assert.equal(json.conclusion.status, 'pass-with-warnings')
73
- assert.ok(json.conclusion.warnings.includes('C5'))
74
- assert.equal(json.checks.find(row => row.id === 'C6').status, 'pass')
75
- assert.deepEqual(json.conclusion.failed, [])
76
- })
77
-
78
- test('entry-validate: an ordered dsh.bundle.patch list is accepted', () => {
79
- const { out, json } = result('patch-list')
80
- assert.equal(out.status, 0, out.stderr)
81
- assert.equal(json.checks.find(row => row.id === 'C2').status, 'pass')
82
- assert.equal(json.conclusion.status, 'pass')
83
- })
84
-
85
- function hostEvidence(status) {
86
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dsh-entry-host-'))
87
- const file = path.join(dir, 'host.json')
88
- fs.writeFileSync(file, JSON.stringify({
89
- status,
90
- command: 'dsh plugin --profile test add @dsh-fixtures/entry-good',
91
- host: 'dsh 0.2.2-alpha.2 / cordis 4.0.6-alpha.1',
92
- evidence: 'https://example.invalid/host-install/1',
93
- generatedAt: '2026-11-02',
94
- }))
95
- return { dir, file, cleanup: () => fs.rmSync(dir, { recursive: true, force: true }) }
96
- }
97
-
98
- test('entry-validate: a passed host install upgrades C3 to pass', () => {
99
- const h = hostEvidence('pass')
100
- try {
101
- const out = spawnSync(process.execPath, [SCRIPT, path.join(FIXTURES, 'good'), '--json', '--meta', path.join(FIXTURES, 'good', 'meta.json'), '--host-evidence', h.file], { encoding: 'utf8', timeout: 30_000 })
102
- assert.equal(out.status, 0, out.stderr)
103
- const json = JSON.parse(out.stdout)
104
- assert.equal(json.checks.find(row => row.id === 'C3').status, 'pass')
105
- assert.equal(json.conclusion.status, 'pass')
106
- } finally { h.cleanup() }
107
- })
108
-
109
- test('entry-validate: a failed host install fails C3', () => {
110
- const h = hostEvidence('fail')
111
- try {
112
- const out = spawnSync(process.execPath, [SCRIPT, path.join(FIXTURES, 'good'), '--json', '--meta', path.join(FIXTURES, 'good', 'meta.json'), '--host-evidence', h.file], { encoding: 'utf8', timeout: 30_000 })
113
- assert.equal(out.status, 2)
114
- const json = JSON.parse(out.stdout)
115
- assert.equal(json.checks.find(row => row.id === 'C3').status, 'fail')
116
- assert.ok(json.conclusion.failed.includes('C3'))
117
- } finally { h.cleanup() }
118
- })
119
-
120
- test('entry-validate: a blocked host install is incomplete, not a pass', () => {
121
- const h = hostEvidence('blocked')
122
- try {
123
- const out = spawnSync(process.execPath, [SCRIPT, path.join(FIXTURES, 'good'), '--json', '--meta', path.join(FIXTURES, 'good', 'meta.json'), '--host-evidence', h.file], { encoding: 'utf8', timeout: 30_000 })
124
- assert.equal(out.status, 3)
125
- const json = JSON.parse(out.stdout)
126
- assert.equal(json.checks.find(row => row.id === 'C3').status, 'blocked')
127
- assert.ok(json.conclusion.blocked.includes('C3'))
128
- assert.equal(json.conclusion.status, 'incomplete')
129
- } finally { h.cleanup() }
130
- })
131
-
132
- test('entry-validate: C3 is flow-only when no host evidence is supplied', () => {
133
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dsh-c3-flow-'))
134
- try {
135
- const meta = JSON.parse(fs.readFileSync(path.join(FIXTURES, 'good', 'meta.json'), 'utf8'))
136
- delete meta.installCommand
137
- const metaFile = path.join(dir, 'meta.json')
138
- fs.writeFileSync(metaFile, JSON.stringify(meta))
139
- const out = spawnSync(process.execPath, [SCRIPT, path.join(FIXTURES, 'good'), '--json', '--meta', metaFile], { encoding: 'utf8', timeout: 30_000 })
140
- assert.equal(out.status, 0, out.stderr)
141
- const json = JSON.parse(out.stdout)
142
- assert.equal(json.checks.find(row => row.id === 'C3').status, 'not-applicable')
143
- assert.equal(json.conclusion.status, 'pass')
144
- } finally { fs.rmSync(dir, { recursive: true, force: true }) }
145
- })
146
-
147
- test('entry-validate: host evidence can mark C3 not-applicable for a host/directory sample', () => {
148
- const h = hostEvidence('not-applicable')
149
- try {
150
- const out = spawnSync(process.execPath, [SCRIPT, path.join(FIXTURES, 'good'), '--json', '--meta', path.join(FIXTURES, 'good', 'meta.json'), '--host-evidence', h.file], { encoding: 'utf8', timeout: 30_000 })
151
- assert.equal(out.status, 0, out.stderr)
152
- const json = JSON.parse(out.stdout)
153
- assert.equal(json.checks.find(row => row.id === 'C3').status, 'not-applicable')
154
- assert.equal(json.conclusion.status, 'pass')
155
- } finally { h.cleanup() }
156
- })
157
-
158
- test('entry-validate: a missing license is A7 advisory, not a C6 fail', () => {
159
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dsh-c6-license-'))
160
- try {
161
- const src = path.join(FIXTURES, 'good')
162
- for (const name of ['index.js', 'client.js', 'cordis.patch.yml', 'meta.json']) fs.copyFileSync(path.join(src, name), path.join(dir, name))
163
- const pkg = JSON.parse(fs.readFileSync(path.join(src, 'package.json'), 'utf8'))
164
- delete pkg.license
165
- fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify(pkg))
166
- const out = spawnSync(process.execPath, [SCRIPT, dir, '--json', '--meta', path.join(dir, 'meta.json')], { encoding: 'utf8', timeout: 30_000 })
167
- assert.equal(out.status, 0, out.stderr)
168
- const json = JSON.parse(out.stdout)
169
- assert.equal(json.checks.find(row => row.id === 'C6').status, 'pass')
170
- assert.ok(json.advisories.some(row => row.id === 'A7'))
171
- } finally { fs.rmSync(dir, { recursive: true, force: true }) }
172
- })
@@ -1,295 +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 { spawnSync } from 'node:child_process'
7
- import { fileURLToPath } from 'node:url'
8
- import {
9
- FLOW_IDS, evidenceHash, finalizeEvidence, journeyAggregate, treeSha256, validateFlowEvidence,
10
- } from '../src/flow-evidence.mjs'
11
- import { isSecretEnvName, sanitizedEnv } from '../src/sandbox-env.mjs'
12
-
13
- const HERE = path.dirname(fileURLToPath(import.meta.url))
14
- const STD = path.resolve(HERE, '..', '..')
15
- const AUDIT = path.join(STD, '40-checker', 'scripts', 'level-audit.mjs')
16
- const COLLECT = path.join(STD, '40-checker', 'bin', 'dsh-flow-collect.mjs')
17
- const GOOD = path.join(STD, '40-checker', 'fixtures', 'good')
18
- const TEMPLATE = path.join(STD, 'examples', 'plugin-template')
19
- const ISO = '2026-11-06T12:00:00.000Z'
20
- const HASH = `sha256:${'a'.repeat(64)}`
21
-
22
- function check(id, overrides = {}) {
23
- return {
24
- id,
25
- kind: 'observed',
26
- command: ['node', '-e', 'process.exit(0)'],
27
- exitCode: 0,
28
- startedAt: ISO,
29
- endedAt: ISO,
30
- stdout: { path: `logs/${id}.out.log`, sha256: HASH },
31
- assertions: [{ claim: `${id} observed`, observed: true }],
32
- ...overrides,
33
- }
34
- }
35
-
36
- function evidenceDocument({ journeys = {}, subject, ...rest } = {}) {
37
- const base = {
38
- schema: 'dsh-flow-evidence/2',
39
- generatedAt: ISO,
40
- producer: { command: ['node', '40-checker/bin/dsh-flow-collect.mjs'], at: ISO, kind: 'local' },
41
- subject: subject ?? {
42
- name: '@dsh-fixtures/good-plugin',
43
- version: '1.0.0',
44
- integrity: null,
45
- tarballSha256: HASH,
46
- commit: null,
47
- treeSha256: HASH,
48
- },
49
- host: { dsh: '0.2.1-rc.5', node: 'v24.22.1', platform: 'linux-x64' },
50
- journeys: Object.fromEntries(FLOW_IDS.map((id) => [id, { status: 'pass', checks: [check(`${id}.run`)] }])),
51
- claims: { claimLevel: 'self-attested', notClaimed: ['certification'] },
52
- conclusion: 'flow-pass',
53
- ...rest,
54
- }
55
- for (const [id, override] of Object.entries(journeys)) base.journeys[id] = override
56
- return finalizeEvidence(base)
57
- }
58
-
59
- function writeTemp(name, value) {
60
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dsh-flow-evidence-test-'))
61
- const file = path.join(dir, name)
62
- fs.writeFileSync(file, typeof value === 'string' ? value : `${JSON.stringify(value, null, 2)}\n`)
63
- return { dir, file, cleanup: () => fs.rmSync(dir, { recursive: true, force: true }) }
64
- }
65
-
66
- function runAudit(flowFile) {
67
- const run = spawnSync(process.execPath, [AUDIT, GOOD, '--format', 'json', '--flow-evidence', flowFile], {
68
- encoding: 'utf8', timeout: 60_000,
69
- })
70
- assert.equal(run.status, 0, run.stderr)
71
- return JSON.parse(run.stdout)
72
- }
73
-
74
- test('sandbox-env: inherited secrets are dropped and only allowlisted names survive', () => {
75
- process.env.DSH_TEST_TOKEN = 'do-not-leak'
76
- process.env.DSH_TEST_API_KEY = 'do-not-leak'
77
- try {
78
- const env = sanitizedEnv()
79
- assert.equal(env.DSH_TEST_TOKEN, undefined)
80
- assert.equal(env.DSH_TEST_API_KEY, undefined)
81
- assert.equal(env.DSH_HOME, undefined)
82
- assert.equal(env.PATH, process.env.PATH)
83
- assert.equal(isSecretEnvName('GH_TOKEN'), true)
84
- assert.equal(isSecretEnvName('DEEPSEEK_API_KEY'), true)
85
- assert.equal(isSecretEnvName('PATH'), false)
86
- assert.equal(sanitizedEnv({ DSH_HOME: '/tmp/isolated' }).DSH_HOME, '/tmp/isolated')
87
- } finally {
88
- delete process.env.DSH_TEST_TOKEN
89
- delete process.env.DSH_TEST_API_KEY
90
- }
91
- })
92
-
93
- test('flow-evidence: a well-formed v2 document validates and hashes consistently', () => {
94
- const doc = evidenceDocument()
95
- const verdict = validateFlowEvidence(doc)
96
- assert.equal(verdict.kind, 'v2')
97
- assert.equal(verdict.valid, true, verdict.errors.join('; '))
98
- assert.equal(verdict.declaredHash, evidenceHash(doc))
99
- assert.equal(verdict.journeys.J1.derived, 'pass')
100
- })
101
-
102
- test('level-audit: v1 journey/runtime schemas are deprecated and never a pass', () => {
103
- const docs = [
104
- { schema: 'dsh-flow-journey/1', package: '@dsh-fixtures/good-plugin', checks: [] },
105
- { schema: 'dsh-flow-runtime/1', environment: {}, checks: [] },
106
- ]
107
- for (const doc of docs) {
108
- const temp = writeTemp('v1.json', doc)
109
- try {
110
- const result = runAudit(temp.file)
111
- assert.equal(result.levels.flow.state, 'deprecated')
112
- assert.equal(result.levels.flow.deprecated, true)
113
- assert.equal(result.levels.flow.passed, 0)
114
- assert.notEqual(result.levels.flow.state, 'runtime')
115
- } finally {
116
- temp.cleanup()
117
- }
118
- }
119
- })
120
-
121
- test('level-audit: bare {Jx:{status:pass}} rows are deprecated and never a pass', () => {
122
- const temp = writeTemp('bare.json', Object.fromEntries(FLOW_IDS.map(id => [id, { status: 'pass' }])))
123
- try {
124
- const result = runAudit(temp.file)
125
- assert.equal(result.levels.flow.state, 'deprecated')
126
- assert.equal(result.levels.flow.passed, 0)
127
- assert.notEqual(result.levels.flow.state, 'runtime')
128
- } finally {
129
- temp.cleanup()
130
- }
131
- })
132
-
133
- test('level-audit: v2 with all eight journeys passing is runtime', () => {
134
- const temp = writeTemp('v2-pass.json', evidenceDocument())
135
- try {
136
- const result = runAudit(temp.file)
137
- assert.equal(result.levels.flow.state, 'runtime')
138
- assert.equal(result.levels.flow.passed, 8)
139
- assert.equal(result.levels.flow.evidenceHash.ok, true)
140
- } finally {
141
- temp.cleanup()
142
- }
143
- })
144
-
145
- test('level-audit: a forged v2 pass with a non-zero exitCode is not a pass', () => {
146
- // Declared pass on every journey, but J4 carries exitCode 1: the aggregate
147
- // contradicts the declaration, so the document must fail validation.
148
- const doc = evidenceDocument({
149
- journeys: { J4: { status: 'pass', checks: [check('J4.run', { exitCode: 1 })] } },
150
- })
151
- const temp = writeTemp('forged.json', doc)
152
- try {
153
- const result = runAudit(temp.file)
154
- assert.notEqual(result.levels.flow.state, 'runtime')
155
- assert.equal(result.levels.flow.passed, 0)
156
- assert.equal(result.levels.flow.invalid, true)
157
- assert.match(result.levels.flow.reason, /aggregate|exitCode|declares pass/i)
158
- } finally {
159
- temp.cleanup()
160
- }
161
- })
162
-
163
- test('level-audit: an honest v2 with one failed journey is failed, not runtime', () => {
164
- const doc = evidenceDocument({
165
- journeys: { J4: { status: 'fail', checks: [check('J4.run', { exitCode: 1 })] } },
166
- conclusion: 'flow-fail',
167
- })
168
- const temp = writeTemp('failed.json', doc)
169
- try {
170
- const result = runAudit(temp.file)
171
- assert.equal(result.levels.flow.state, 'failed')
172
- assert.equal(result.levels.flow.passed, 7)
173
- assert.equal(result.levels.flow.journeys.J4.status, 'fail')
174
- } finally {
175
- temp.cleanup()
176
- }
177
- })
178
-
179
- test('level-audit: a v2 journey without checks does not count as a pass', () => {
180
- const doc = evidenceDocument({ journeys: { J5: { status: 'not-run', checks: [] } } })
181
- const temp = writeTemp('empty.json', doc)
182
- try {
183
- const result = runAudit(temp.file)
184
- assert.notEqual(result.levels.flow.state, 'runtime')
185
- assert.equal(result.levels.flow.passed, 7)
186
- } finally {
187
- temp.cleanup()
188
- }
189
- })
190
-
191
- test('level-audit: a tampered evidence hash is invalid, not runtime', () => {
192
- const doc = evidenceDocument()
193
- doc.chain.evidenceHash = HASH
194
- const temp = writeTemp('tampered.json', doc)
195
- try {
196
- const result = runAudit(temp.file)
197
- assert.equal(result.levels.flow.state, 'invalid')
198
- assert.equal(result.levels.flow.passed, 0)
199
- assert.match(result.levels.flow.reason, /evidenceHash/)
200
- } finally {
201
- temp.cleanup()
202
- }
203
- })
204
-
205
- test('level-audit: a declared blocked journey is blocked, not failed', () => {
206
- const doc = evidenceDocument({
207
- journeys: { J6: { status: 'blocked', reason: 'no --previous package supplied', checks: [check('J6.upgrade', { exitCode: 1 })] } },
208
- conclusion: 'flow-blocked',
209
- })
210
- const temp = writeTemp('blocked.json', doc)
211
- try {
212
- const result = runAudit(temp.file)
213
- assert.equal(result.levels.flow.state, 'blocked')
214
- assert.equal(result.levels.flow.passed, 7)
215
- } finally {
216
- temp.cleanup()
217
- }
218
- })
219
-
220
- test('flow-collect: static output is v2, validates, and the audit can read it', () => {
221
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dsh-flow-collect-test-'))
222
- try {
223
- const copy = path.join(dir, 'plugin')
224
- fs.cpSync(TEMPLATE, copy, { recursive: true })
225
- const out = path.join(dir, 'flow.json')
226
- const run = spawnSync(process.execPath, [COLLECT, '--package', copy, '--static', '--out', out], {
227
- encoding: 'utf8', timeout: 120_000,
228
- })
229
- assert.equal(run.status, 0, `${run.stderr}\n${run.stdout}`)
230
- const doc = JSON.parse(fs.readFileSync(out, 'utf8'))
231
- assert.equal(doc.schema, 'dsh-flow-evidence/2')
232
- const verdict = validateFlowEvidence(doc)
233
- assert.equal(verdict.valid, true, verdict.errors.join('; '))
234
- assert.deepEqual(Object.keys(doc.journeys), FLOW_IDS)
235
- assert.equal(doc.subject.treeStable, true)
236
- assert.equal(doc.subject.treeSha256, treeSha256(TEMPLATE).hash)
237
- assert.equal(doc.chain.evidenceHash, evidenceHash(doc))
238
- assert.equal(journeyAggregate(doc.journeys.J1.checks), 'static')
239
-
240
- // The audit consumes the collector output without upgrading it to runtime.
241
- const audit = spawnSync(process.execPath, [AUDIT, copy, '--format', 'json', '--flow-evidence', out], {
242
- encoding: 'utf8', timeout: 60_000,
243
- })
244
- assert.equal(audit.status, 0, audit.stderr)
245
- const result = JSON.parse(audit.stdout)
246
- assert.equal(result.levels.flow.schema, 'dsh-flow-evidence/2')
247
- assert.notEqual(result.levels.flow.state, 'runtime')
248
- assert.notEqual(result.levels.flow.state, 'invalid')
249
- assert.notEqual(result.levels.flow.state, 'deprecated')
250
- assert.equal(result.levels.flow.passed, 0)
251
- assert.equal(result.levels.flow.evidenceHash.ok, true)
252
- } finally {
253
- fs.rmSync(dir, { recursive: true, force: true })
254
- }
255
- })
256
-
257
- test('flow-collect: a failed journey exits 1 and prints the five-element hint', () => {
258
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dsh-flow-collect-fail-'))
259
- try {
260
- const plugin = path.join(dir, 'plugin')
261
- fs.mkdirSync(plugin)
262
- fs.writeFileSync(path.join(plugin, 'package.json'), `${JSON.stringify({
263
- name: 'dsh-flow-bad-fixture', version: '1.0.0', type: 'module', main: './index.mjs',
264
- files: ['index.mjs'], dependencies: { 'workspace-only': 'workspace:*' },
265
- }, null, 2)}\n`)
266
- fs.writeFileSync(path.join(plugin, 'index.mjs'), 'export function apply() {}\n')
267
- const out = path.join(dir, 'flow.json')
268
- const run = spawnSync(process.execPath, [COLLECT, '--package', plugin, '--static', '--out', out], {
269
- encoding: 'utf8', timeout: 120_000,
270
- })
271
- assert.equal(run.status, 1, `${run.stderr}\n${run.stdout}`)
272
- for (const marker of ['FAIL J1.install.preflight', '1) 子检查:', '2) 命令:', '3) exitCode:', '4) stderr(末20行):', '5) 日志:', '条款锚: 32#R1']) {
273
- assert.ok(run.stdout.includes(marker), `missing hint marker ${marker}\n${run.stdout}`)
274
- }
275
- const doc = JSON.parse(fs.readFileSync(out, 'utf8'))
276
- assert.equal(doc.journeys.J1.status, 'fail')
277
- assert.equal(doc.conclusion, 'flow-fail')
278
- assert.equal(validateFlowEvidence(doc).valid, true)
279
- } finally {
280
- fs.rmSync(dir, { recursive: true, force: true })
281
- }
282
- })
283
-
284
- test('flow-evidence: the published schema declares the validator contract', () => {
285
- const schema = JSON.parse(fs.readFileSync(path.join(STD, 'spec', 'flow-evidence.schema.json'), 'utf8'))
286
- assert.equal(schema.properties.schema.const, 'dsh-flow-evidence/2')
287
- for (const key of ['schema', 'generatedAt', 'producer', 'subject', 'host', 'journeys', 'claims', 'chain']) {
288
- assert.ok(schema.required.includes(key), `schema.required is missing ${key}`)
289
- }
290
- for (const id of FLOW_IDS) assert.ok(schema.properties.journeys.required.includes(id), `journeys.required is missing ${id}`)
291
- for (const key of ['id', 'command', 'exitCode', 'startedAt', 'endedAt', 'stdout', 'assertions']) {
292
- assert.ok(schema.$defs.check.required.includes(key), `check.required is missing ${key}`)
293
- }
294
- assert.ok(schema.$defs.journey.properties.checks)
295
- })
@@ -1,76 +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 { validateGovernance } from '../scripts/governance-check.mjs'
7
-
8
- const HERE = path.dirname(fileURLToPath(import.meta.url))
9
- const ROOT = path.resolve(HERE, '..', '..')
10
- const read = (rel) => JSON.parse(fs.readFileSync(path.join(ROOT, rel), 'utf8'))
11
-
12
- function fixture() {
13
- return {
14
- roles: read('governance/roles.json'),
15
- contributors: read('governance/contributors.json'),
16
- reviewers: read('governance/reviewers.json'),
17
- coi: read('governance/coi.json'),
18
- }
19
- }
20
-
21
- test('governance: the checked-in files pass', () => {
22
- const result = validateGovernance(fixture())
23
- assert.deepEqual(result.errors, [])
24
- })
25
-
26
- test('governance: exactly one human organizer is required', () => {
27
- const data = fixture()
28
- data.roles.roles.organizer.bot = true
29
- data.roles.roles.organizer.humanOnly = false
30
- const result = validateGovernance(data)
31
- assert.ok(result.errors.some((e) => /organizer must be humanOnly/.test(e)))
32
- })
33
-
34
- test('governance: AI must not decide spec semantics', () => {
35
- const data = fixture()
36
- data.roles.matrix['ai-steward']['spec.decide'] = 'allow'
37
- const result = validateGovernance(data)
38
- assert.ok(result.errors.some((e) => /must not hold spec\.decide/.test(e)))
39
- })
40
-
41
- test('governance: AI merge must be allowlist-only', () => {
42
- const data = fixture()
43
- data.roles.matrix['ai-steward']['repo.merge.main'] = 'allow'
44
- const result = validateGovernance(data)
45
- assert.ok(result.errors.some((e) => /allow-with-allowlist/.test(e)))
46
- })
47
-
48
- test('governance: class A approver must be human', () => {
49
- const data = fixture()
50
- data.roles.decisions.A.approver = ['ai-steward']
51
- const result = validateGovernance(data)
52
- assert.ok(result.errors.some((e) => /decision A approver/.test(e)))
53
- })
54
-
55
- test('governance: AI merge denylist protects spec and governance', () => {
56
- const data = fixture()
57
- data.roles.ai.mergeDenylist = ['spec/']
58
- const result = validateGovernance(data)
59
- assert.ok(result.errors.some((e) => /governance\//.test(e)))
60
- })
61
-
62
- test('governance: a contributor needs DCO and the current terms', () => {
63
- const data = fixture()
64
- data.contributors.contributors[0].signedOff = false
65
- data.contributors.contributors[0].terms = 'dsh-ct/0'
66
- const result = validateGovernance(data)
67
- assert.ok(result.errors.some((e) => /no DCO sign-off/.test(e)))
68
- assert.ok(result.errors.some((e) => /terms mismatch/.test(e)))
69
- })
70
-
71
- test('governance: reviewer must reference a human role', () => {
72
- const data = fixture()
73
- data.reviewers.reviewers[0].role = 'ai-steward'
74
- const result = validateGovernance(data)
75
- assert.ok(result.errors.some((e) => /human role/.test(e)))
76
- })
@@ -1,97 +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
- import fs from 'node:fs'
7
- import os from 'node:os'
8
-
9
- const HERE = path.dirname(fileURLToPath(import.meta.url))
10
- const STD = path.resolve(HERE, '..', '..')
11
- const SYNC = path.join(STD, '40-checker', 'scripts', 'label-sync.mjs')
12
- const DRIFT = path.join(STD, '40-checker', 'scripts', 'label-drift.mjs')
13
- const VERIFY = path.join(STD, '40-checker', 'scripts', 'label-verify.mjs')
14
- const SAMPLE = path.join(STD, 'adoption', 'market', 'sample-source.json')
15
-
16
- function tempDir() {
17
- return fs.mkdtempSync(path.join(os.tmpdir(), 'dsh-label-test-'))
18
- }
19
-
20
- function run(script, args) {
21
- return spawnSync(process.execPath, [script, ...args], { encoding: 'utf8', timeout: 30_000 })
22
- }
23
-
24
- test('label-sync: writes a valid index from the sample source', () => {
25
- const dir = tempDir()
26
- try {
27
- const out = path.join(dir, 'labels')
28
- const sync = run(SYNC, ['--source', SAMPLE, '--out', out])
29
- assert.equal(sync.status, 0, sync.stderr)
30
- const index = JSON.parse(fs.readFileSync(path.join(out, 'index.json'), 'utf8'))
31
- assert.equal(index.schema, 'dsh-label-index/1')
32
- assert.equal(index.records.length, 1)
33
- assert.match(index.records[0].recordHash, /^sha256:[0-9a-f]{64}$/)
34
- const drift = run(DRIFT, ['--index', path.join(out, 'index.json'), '--now', '2026-11-02'])
35
- assert.equal(drift.status, 0, drift.stdout + drift.stderr)
36
- const verify = run(VERIFY, ['--index', path.join(out, 'index.json'), '--hash', index.records[0].recordHash, '--json'])
37
- assert.equal(verify.status, 0, verify.stderr)
38
- const view = JSON.parse(verify.stdout)
39
- assert.equal(view.ok, true)
40
- assert.equal(view.claimLevel, 'reference')
41
- assert.equal(Object.prototype.hasOwnProperty.call(view, 'grade'), false)
42
- } finally {
43
- fs.rmSync(dir, { recursive: true, force: true })
44
- }
45
- })
46
-
47
- test('label-drift: an expired label is stale and fails', () => {
48
- const dir = tempDir()
49
- try {
50
- const out = path.join(dir, 'labels')
51
- assert.equal(run(SYNC, ['--source', SAMPLE, '--out', out]).status, 0)
52
- const drift = run(DRIFT, ['--index', path.join(out, 'index.json'), '--now', '2028-01-01', '--json'])
53
- assert.equal(drift.status, 1)
54
- const result = JSON.parse(drift.stdout)
55
- assert.equal(result.stale, 1)
56
- assert.equal(result.ok, false)
57
- } finally {
58
- fs.rmSync(dir, { recursive: true, force: true })
59
- }
60
- })
61
-
62
- test('label-sync: a grade or a wrong hash is rejected', () => {
63
- const dir = tempDir()
64
- try {
65
- const source = JSON.parse(fs.readFileSync(SAMPLE, 'utf8'))
66
- const badGrade = structuredClone(source)
67
- badGrade[0].grade = 'A'
68
- const gradeFile = path.join(dir, 'bad-grade.json')
69
- fs.writeFileSync(gradeFile, JSON.stringify(badGrade))
70
- const grade = run(SYNC, ['--source', gradeFile, '--check'])
71
- assert.equal(grade.status, 1)
72
- assert.match(grade.stderr, /forbidden field: grade/)
73
-
74
- const badHash = structuredClone(source)
75
- badHash[0].recordHash = `sha256:${'0'.repeat(64)}`
76
- const hashFile = path.join(dir, 'bad-hash.json')
77
- fs.writeFileSync(hashFile, JSON.stringify(badHash))
78
- const hash = run(SYNC, ['--source', hashFile, '--check'])
79
- assert.equal(hash.status, 1)
80
- assert.match(hash.stderr, /recordHash does not match/)
81
- } finally {
82
- fs.rmSync(dir, { recursive: true, force: true })
83
- }
84
- })
85
-
86
- test('label-verify: an unknown hash is a 404, not a fake pass', () => {
87
- const dir = tempDir()
88
- try {
89
- const out = path.join(dir, 'labels')
90
- assert.equal(run(SYNC, ['--source', SAMPLE, '--out', out]).status, 0)
91
- const verify = run(VERIFY, ['--index', path.join(out, 'index.json'), '--hash', `sha256:${'f'.repeat(64)}`])
92
- assert.equal(verify.status, 2)
93
- assert.match(verify.stderr, /not found/)
94
- } finally {
95
- fs.rmSync(dir, { recursive: true, force: true })
96
- }
97
- })