@baselineos/cli 0.1.0
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.
- package/.baseline/govern/audit-trail.json +61 -0
- package/.turbo/turbo-build.log +23 -0
- package/.turbo/turbo-test.log +80 -0
- package/LICENSE +17 -0
- package/README.md +19 -0
- package/dist/chunk-CCIHFLKI.js +1792 -0
- package/dist/cli.cjs +2290 -0
- package/dist/cli.d.cts +8 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +470 -0
- package/dist/index.cjs +3488 -0
- package/dist/index.d.cts +616 -0
- package/dist/index.d.ts +616 -0
- package/dist/index.js +1664 -0
- package/in/.baseline/govern/audit-trail.json +61 -0
- package/package.json +45 -0
- package/src/__tests__/badge.test.ts +93 -0
- package/src/__tests__/cli.test.ts +22 -0
- package/src/__tests__/commands.test.ts +178 -0
- package/src/__tests__/schema.test.ts +114 -0
- package/src/__tests__/smoke.test.ts +15 -0
- package/src/__tests__/verify.test.ts +135 -0
- package/src/badge.ts +88 -0
- package/src/cli.ts +314 -0
- package/src/commands.ts +2228 -0
- package/src/config/megagem-config.json +266 -0
- package/src/index.ts +19 -0
- package/src/megagem.ts +1800 -0
- package/src/verify.ts +254 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "audit-1775397759527-609",
|
|
4
|
+
"type": "policy_created",
|
|
5
|
+
"actor": "system",
|
|
6
|
+
"policyId": "policy-1775397759527",
|
|
7
|
+
"details": {
|
|
8
|
+
"policyId": "policy-1775397759527",
|
|
9
|
+
"name": "baseline-default"
|
|
10
|
+
},
|
|
11
|
+
"timestamp": "2026-04-05T14:02:39.527Z"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"id": "audit-1775397759528-689",
|
|
15
|
+
"type": "policy_approved",
|
|
16
|
+
"actor": "baseline-cli",
|
|
17
|
+
"policyId": "policy-1775397759527",
|
|
18
|
+
"details": {
|
|
19
|
+
"policyId": "policy-1775397759527"
|
|
20
|
+
},
|
|
21
|
+
"timestamp": "2026-04-05T14:02:39.528Z"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "audit-1775397759528-276",
|
|
25
|
+
"type": "policy_evaluated",
|
|
26
|
+
"actor": "system",
|
|
27
|
+
"policyId": "policy-1775397759527",
|
|
28
|
+
"details": {
|
|
29
|
+
"policyId": "policy-1775397759527",
|
|
30
|
+
"passed": true,
|
|
31
|
+
"enforcementAction": "allow",
|
|
32
|
+
"violationCount": 0
|
|
33
|
+
},
|
|
34
|
+
"timestamp": "2026-04-05T14:02:39.528Z"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "audit-1775397759528-276",
|
|
38
|
+
"type": "policy_enforced",
|
|
39
|
+
"actor": "system",
|
|
40
|
+
"policyId": "policy-1775397759527",
|
|
41
|
+
"resourceId": "./in",
|
|
42
|
+
"details": {
|
|
43
|
+
"policyId": "policy-1775397759527",
|
|
44
|
+
"resourceId": "./in",
|
|
45
|
+
"enforcementAction": "allow"
|
|
46
|
+
},
|
|
47
|
+
"timestamp": "2026-04-05T14:02:39.528Z"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "audit-1775397759530-271",
|
|
51
|
+
"type": "compliance_checked",
|
|
52
|
+
"actor": "system",
|
|
53
|
+
"details": {
|
|
54
|
+
"complianceId": "compliance-1775397759530",
|
|
55
|
+
"standard": "ISO27001",
|
|
56
|
+
"compliant": true,
|
|
57
|
+
"violationCount": 0
|
|
58
|
+
},
|
|
59
|
+
"timestamp": "2026-04-05T14:02:39.530Z"
|
|
60
|
+
}
|
|
61
|
+
]
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@baselineos/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Baseline Protocol - Command Line Interface",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"baseline": "dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@baselineos/lang": "0.1.0",
|
|
19
|
+
"@baselineos/protocol-core": "1.0.0",
|
|
20
|
+
"@baselineos/frame": "0.1.0",
|
|
21
|
+
"@baselineos/studio": "0.1.0",
|
|
22
|
+
"@baselineos/experience": "0.1.0",
|
|
23
|
+
"@baselineos/govern": "1.0.0",
|
|
24
|
+
"@baselineos/autonomy": "0.1.0",
|
|
25
|
+
"@baselineos/persona": "0.1.0",
|
|
26
|
+
"baselineos": "0.2.0-beta.1"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"tsup": "^8.0.0",
|
|
30
|
+
"typescript": "^5.7.0",
|
|
31
|
+
"vitest": "^2.1.0"
|
|
32
|
+
},
|
|
33
|
+
"license": "Apache-2.0",
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup src/index.ts src/cli.ts --format esm,cjs --dts",
|
|
39
|
+
"dev": "tsup src/index.ts src/cli.ts --format esm,cjs --dts --watch",
|
|
40
|
+
"test": "vitest run",
|
|
41
|
+
"lint": "eslint src/",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"clean": "rm -rf dist"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Baseline-governed trust signal badge
|
|
3
|
+
* Sprint 40
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { generateBadge, validateBadge, formatBadgeText } from '../badge.js';
|
|
7
|
+
|
|
8
|
+
describe('Badge Generation', () => {
|
|
9
|
+
it('generates a valid badge', () => {
|
|
10
|
+
const badge = generateBadge({ subject: 'my-app' });
|
|
11
|
+
expect(badge.governedBy).toBe('baseline-protocol');
|
|
12
|
+
expect(badge.subject).toBe('my-app');
|
|
13
|
+
expect(badge.version).toBe('1.0.0');
|
|
14
|
+
expect(badge.verificationHash.length).toBeGreaterThanOrEqual(16);
|
|
15
|
+
expect(badge.issuedAt).toBeDefined();
|
|
16
|
+
expect(badge.expiresAt).toBeDefined();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('includes specified standards', () => {
|
|
20
|
+
const badge = generateBadge({
|
|
21
|
+
subject: 'trade-platform',
|
|
22
|
+
standards: ['AFCFTA-ROO', 'COCOBOD-GRADING'],
|
|
23
|
+
});
|
|
24
|
+
expect(badge.standards).toEqual(['AFCFTA-ROO', 'COCOBOD-GRADING']);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('uses custom issuer', () => {
|
|
28
|
+
const badge = generateBadge({ subject: 'app', issuer: 'gtcx-authority' });
|
|
29
|
+
expect(badge.issuer).toBe('gtcx-authority');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('defaults to 90-day validity', () => {
|
|
33
|
+
const badge = generateBadge({ subject: 'app' });
|
|
34
|
+
const issued = new Date(badge.issuedAt);
|
|
35
|
+
const expires = new Date(badge.expiresAt);
|
|
36
|
+
const days = (expires.getTime() - issued.getTime()) / 86_400_000;
|
|
37
|
+
expect(Math.round(days)).toBe(90);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('accepts custom validity period', () => {
|
|
41
|
+
const badge = generateBadge({ subject: 'app', validityDays: 30 });
|
|
42
|
+
const issued = new Date(badge.issuedAt);
|
|
43
|
+
const expires = new Date(badge.expiresAt);
|
|
44
|
+
const days = (expires.getTime() - issued.getTime()) / 86_400_000;
|
|
45
|
+
expect(Math.round(days)).toBe(30);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('Badge Validation', () => {
|
|
50
|
+
it('validates a good badge', () => {
|
|
51
|
+
const badge = generateBadge({ subject: 'app' });
|
|
52
|
+
expect(validateBadge(badge).valid).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('rejects non-baseline badge', () => {
|
|
56
|
+
const badge = generateBadge({ subject: 'app' });
|
|
57
|
+
(badge as any).governedBy = 'other';
|
|
58
|
+
expect(validateBadge(badge).valid).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('rejects expired badge', () => {
|
|
62
|
+
const badge = generateBadge({ subject: 'app', validityDays: 0 });
|
|
63
|
+
badge.expiresAt = new Date(Date.now() - 1000).toISOString();
|
|
64
|
+
const result = validateBadge(badge);
|
|
65
|
+
expect(result.valid).toBe(false);
|
|
66
|
+
expect(result.reason).toContain('expired');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('rejects missing hash', () => {
|
|
70
|
+
const badge = generateBadge({ subject: 'app' });
|
|
71
|
+
badge.verificationHash = '';
|
|
72
|
+
expect(validateBadge(badge).valid).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('rejects missing subject', () => {
|
|
76
|
+
const badge = generateBadge({ subject: 'app' });
|
|
77
|
+
badge.subject = '';
|
|
78
|
+
expect(validateBadge(badge).valid).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('Badge Formatting', () => {
|
|
83
|
+
it('formats badge as text', () => {
|
|
84
|
+
const badge = generateBadge({
|
|
85
|
+
subject: 'cocoa-trade-platform',
|
|
86
|
+
standards: ['AFCFTA-ROO'],
|
|
87
|
+
});
|
|
88
|
+
const text = formatBadgeText(badge);
|
|
89
|
+
expect(text).toContain('Baseline-Governed');
|
|
90
|
+
expect(text).toContain('cocoa-trade-platform');
|
|
91
|
+
expect(text).toContain('AFCFTA-ROO');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { buildCommandLine } from '../cli.js';
|
|
3
|
+
|
|
4
|
+
describe('cli buildCommandLine', () => {
|
|
5
|
+
it('returns empty string for no args', () => {
|
|
6
|
+
expect(buildCommandLine([])).toBe('');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('prefixes bare commands with --', () => {
|
|
10
|
+
expect(buildCommandLine(['status'])).toBe('--status');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('preserves full argument line for option forwarding', () => {
|
|
14
|
+
const args = ['baseline', '--context', './proj', '--mode', 'production'];
|
|
15
|
+
expect(buildCommandLine(args)).toBe('--baseline --context ./proj --mode production');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('does not double-prefix when command already starts with --', () => {
|
|
19
|
+
const args = ['--baseline', '--context', './proj'];
|
|
20
|
+
expect(buildCommandLine(args)).toBe('--baseline --context ./proj');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { BaselineCommandSystem } from '../commands.js';
|
|
3
|
+
import { mkdtemp, rm, mkdir, writeFile, readFile } from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
describe('BaselineCommandSystem option parsing', () => {
|
|
8
|
+
it('forwards options into the workflow layers', async () => {
|
|
9
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
10
|
+
|
|
11
|
+
const system = new BaselineCommandSystem();
|
|
12
|
+
const result = await system.executeCommand(
|
|
13
|
+
'--baseline --context /tmp/baseline --mode production --input ./in --output ./out'
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
logSpy.mockRestore();
|
|
17
|
+
|
|
18
|
+
expect(result.success).toBe(true);
|
|
19
|
+
|
|
20
|
+
if ('results' in result && result.results) {
|
|
21
|
+
const results = result.results as {
|
|
22
|
+
init: { contextPath: string; mode: string };
|
|
23
|
+
lang: { inputPath: string; outputPath: string };
|
|
24
|
+
};
|
|
25
|
+
expect(results.init.contextPath).toBe('/tmp/baseline');
|
|
26
|
+
expect(results.init.mode).toBe('production');
|
|
27
|
+
expect(results.lang.inputPath).toBe('./in');
|
|
28
|
+
expect(results.lang.outputPath).toBe('./out');
|
|
29
|
+
} else {
|
|
30
|
+
throw new Error('Expected workflow results');
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('runs the onboarding flow and returns ids', async () => {
|
|
35
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
36
|
+
|
|
37
|
+
const system = new BaselineCommandSystem();
|
|
38
|
+
const result = await system.executeCommand(
|
|
39
|
+
'--onboard --name Amani --role Engineer --company GTCX --team Acme --project Demo --language en --learning-style visual --pace fast --focus practical --learning-path standard --context /tmp/baseline --skip-workflow'
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
logSpy.mockRestore();
|
|
43
|
+
|
|
44
|
+
expect(result.success).toBe(true);
|
|
45
|
+
|
|
46
|
+
const onboarding = (result as {
|
|
47
|
+
onboarding?: {
|
|
48
|
+
user?: { name?: string; role?: string };
|
|
49
|
+
preferences?: { language?: string; learningStyle?: string; pace?: string };
|
|
50
|
+
team?: { teamId?: string };
|
|
51
|
+
project?: { projectId?: string };
|
|
52
|
+
};
|
|
53
|
+
}).onboarding;
|
|
54
|
+
expect(onboarding?.team?.teamId).toBeDefined();
|
|
55
|
+
expect(onboarding?.project?.projectId).toBeDefined();
|
|
56
|
+
expect(onboarding?.user?.name).toBe('Amani');
|
|
57
|
+
expect(onboarding?.user?.role).toBe('Engineer');
|
|
58
|
+
expect(onboarding?.preferences?.language).toBe('en');
|
|
59
|
+
expect(onboarding?.preferences?.learningStyle).toBe('visual');
|
|
60
|
+
expect(onboarding?.preferences?.pace).toBe('fast');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('runs experience flow and records completion', async () => {
|
|
64
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
65
|
+
const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'baseline-exp-'));
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const system = new BaselineCommandSystem();
|
|
69
|
+
const result = await system.executeCommand(
|
|
70
|
+
`--experience --learning-path standard --context ${tmpDir} --fresh`
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
logSpy.mockRestore();
|
|
74
|
+
|
|
75
|
+
expect(result.success).toBe(true);
|
|
76
|
+
const experience = (result as {
|
|
77
|
+
experience?: { path?: string; experiences?: Array<{ id?: string }> };
|
|
78
|
+
}).experience;
|
|
79
|
+
expect(experience?.path).toBe('standard');
|
|
80
|
+
expect(experience?.experiences?.length).toBeGreaterThan(0);
|
|
81
|
+
} finally {
|
|
82
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('updates config language and persists baseline config', async () => {
|
|
87
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
88
|
+
const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'baseline-config-'));
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const system = new BaselineCommandSystem();
|
|
92
|
+
const result = await system.executeCommand(
|
|
93
|
+
`--config language --set en --context ${tmpDir}`
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
logSpy.mockRestore();
|
|
97
|
+
|
|
98
|
+
expect(result.success).toBe(true);
|
|
99
|
+
const config = (result as { config?: { language?: string } }).config;
|
|
100
|
+
expect(config?.language).toBe('en');
|
|
101
|
+
|
|
102
|
+
const configPath = path.join(tmpDir, '.baseline', 'baseline.config.json');
|
|
103
|
+
const raw = await readFile(configPath, 'utf8');
|
|
104
|
+
const saved = JSON.parse(raw) as { language?: string };
|
|
105
|
+
expect(saved.language).toBe('en');
|
|
106
|
+
} finally {
|
|
107
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('indexes docs and writes index file', async () => {
|
|
112
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
113
|
+
const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'baseline-index-'));
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
await mkdir(path.join(tmpDir, 'docs'));
|
|
117
|
+
await writeFile(path.join(tmpDir, 'docs', 'README.md'), '# Doc', 'utf8');
|
|
118
|
+
|
|
119
|
+
const system = new BaselineCommandSystem();
|
|
120
|
+
const result = await system.executeCommand(
|
|
121
|
+
`--index --paths docs --context ${tmpDir}`
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
logSpy.mockRestore();
|
|
125
|
+
|
|
126
|
+
expect(result.success).toBe(true);
|
|
127
|
+
const indexPath = path.join(tmpDir, '.baseline', 'index.json');
|
|
128
|
+
const raw = await readFile(indexPath, 'utf8');
|
|
129
|
+
const saved = JSON.parse(raw) as { total?: number };
|
|
130
|
+
expect(saved.total).toBeGreaterThan(0);
|
|
131
|
+
} finally {
|
|
132
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('runs doctor diagnostics', async () => {
|
|
137
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
138
|
+
const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'baseline-doctor-'));
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const system = new BaselineCommandSystem();
|
|
142
|
+
const result = await system.executeCommand(`--doctor --context ${tmpDir}`);
|
|
143
|
+
|
|
144
|
+
logSpy.mockRestore();
|
|
145
|
+
|
|
146
|
+
expect(result.success).toBe(true);
|
|
147
|
+
const doctor = result as { score?: number; total?: number };
|
|
148
|
+
expect(doctor.total).toBeGreaterThan(0);
|
|
149
|
+
expect(doctor.score).toBeGreaterThanOrEqual(0);
|
|
150
|
+
} finally {
|
|
151
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('returns governance evidence for baseline-govern', async () => {
|
|
156
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
157
|
+
const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'baseline-govern-cli-'));
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const system = new BaselineCommandSystem();
|
|
161
|
+
const result = await system.executeCommand(
|
|
162
|
+
`--baseline-govern --context ${tmpDir}`
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
logSpy.mockRestore();
|
|
166
|
+
|
|
167
|
+
expect(result.success).toBe(true);
|
|
168
|
+
const typed = result as {
|
|
169
|
+
evidence?: { auditTrail?: unknown[]; latestEvaluation?: { enforcementAction?: string } };
|
|
170
|
+
};
|
|
171
|
+
expect(Array.isArray(typed.evidence?.auditTrail)).toBe(true);
|
|
172
|
+
expect((typed.evidence?.auditTrail?.length ?? 0)).toBeGreaterThan(0);
|
|
173
|
+
expect(typed.evidence?.latestEvaluation?.enforcementAction).toBeDefined();
|
|
174
|
+
} finally {
|
|
175
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for JSON Schema validity
|
|
3
|
+
* Sprint 44
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { readFileSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
|
|
9
|
+
const SCHEMAS_DIR = join(__dirname, '../../../../specs/schemas');
|
|
10
|
+
|
|
11
|
+
function loadSchema(name: string): Record<string, unknown> {
|
|
12
|
+
const content = readFileSync(join(SCHEMAS_DIR, name), 'utf-8');
|
|
13
|
+
return JSON.parse(content) as Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('JSON Schema Validity', () => {
|
|
17
|
+
const schemaFiles = [
|
|
18
|
+
'govern.schema.json',
|
|
19
|
+
'orchestrator.schema.json',
|
|
20
|
+
'evidence-bundle.schema.json',
|
|
21
|
+
'protocol-core.schema.json',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
for (const file of schemaFiles) {
|
|
25
|
+
describe(file, () => {
|
|
26
|
+
it('is valid JSON', () => {
|
|
27
|
+
expect(() => loadSchema(file)).not.toThrow();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('has $schema property', () => {
|
|
31
|
+
const schema = loadSchema(file);
|
|
32
|
+
expect(schema.$schema).toBeDefined();
|
|
33
|
+
expect(schema.$schema).toContain('json-schema.org');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('has $id property', () => {
|
|
37
|
+
const schema = loadSchema(file);
|
|
38
|
+
expect(schema.$id).toBeDefined();
|
|
39
|
+
expect(schema.$id).toContain('baselineprotocol.org');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('has title and description', () => {
|
|
43
|
+
const schema = loadSchema(file);
|
|
44
|
+
expect(schema.title).toBeDefined();
|
|
45
|
+
expect(schema.description).toBeDefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('has $defs with type definitions', () => {
|
|
49
|
+
const schema = loadSchema(file);
|
|
50
|
+
const defs = schema.$defs as Record<string, unknown>;
|
|
51
|
+
expect(defs).toBeDefined();
|
|
52
|
+
expect(Object.keys(defs).length).toBeGreaterThan(0);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('Protocol Core Schema — Structure', () => {
|
|
59
|
+
it('defines all three layers', () => {
|
|
60
|
+
const schema = loadSchema('protocol-core.schema.json');
|
|
61
|
+
const defs = schema.$defs as Record<string, unknown>;
|
|
62
|
+
expect(defs.LangBlock).toBeDefined();
|
|
63
|
+
expect(defs.FrameBlock).toBeDefined();
|
|
64
|
+
expect(defs.CoreBlock).toBeDefined();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('defines BaselineProgram as root', () => {
|
|
68
|
+
const schema = loadSchema('protocol-core.schema.json');
|
|
69
|
+
const defs = schema.$defs as Record<string, unknown>;
|
|
70
|
+
expect(defs.BaselineProgram).toBeDefined();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('defines knowledge graph types', () => {
|
|
74
|
+
const schema = loadSchema('protocol-core.schema.json');
|
|
75
|
+
const defs = schema.$defs as Record<string, unknown>;
|
|
76
|
+
expect(defs.KnowledgeNode).toBeDefined();
|
|
77
|
+
expect(defs.KnowledgeRelationship).toBeDefined();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('has at least 40 type definitions', () => {
|
|
81
|
+
const schema = loadSchema('protocol-core.schema.json');
|
|
82
|
+
const defs = schema.$defs as Record<string, unknown>;
|
|
83
|
+
expect(Object.keys(defs).length).toBeGreaterThanOrEqual(40);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('all $ref pointers resolve to defined types', () => {
|
|
87
|
+
const schema = loadSchema('protocol-core.schema.json');
|
|
88
|
+
const defs = schema.$defs as Record<string, Record<string, unknown>>;
|
|
89
|
+
const definedTypes = new Set(Object.keys(defs));
|
|
90
|
+
const content = JSON.stringify(schema);
|
|
91
|
+
|
|
92
|
+
// Extract all internal $ref pointers
|
|
93
|
+
const refs = content.match(/#\/\$defs\/\w+/g) ?? [];
|
|
94
|
+
for (const ref of refs) {
|
|
95
|
+
const typeName = ref.replace('#/$defs/', '');
|
|
96
|
+
expect(definedTypes.has(typeName), `$ref to undefined type: ${typeName}`).toBe(true);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('Govern Schema — Structure', () => {
|
|
102
|
+
it('defines Policy and ComplianceCheck', () => {
|
|
103
|
+
const schema = loadSchema('govern.schema.json');
|
|
104
|
+
const defs = schema.$defs as Record<string, unknown>;
|
|
105
|
+
expect(defs.Policy).toBeDefined();
|
|
106
|
+
expect(defs.ComplianceCheck).toBeDefined();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('defines audit event types', () => {
|
|
110
|
+
const schema = loadSchema('govern.schema.json');
|
|
111
|
+
const defs = schema.$defs as Record<string, unknown>;
|
|
112
|
+
expect(defs.GovernanceAuditEvent).toBeDefined();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { BaselineCommandSystem } from '../index.js';
|
|
3
|
+
|
|
4
|
+
describe('cli', () => {
|
|
5
|
+
it('should instantiate BaselineCommandSystem', () => {
|
|
6
|
+
const cmd = new BaselineCommandSystem();
|
|
7
|
+
expect(cmd).toBeDefined();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should report status', () => {
|
|
11
|
+
const cmd = new BaselineCommandSystem();
|
|
12
|
+
const status = cmd.getStatus();
|
|
13
|
+
expect(status).toBeDefined();
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for baseline verify command
|
|
3
|
+
* Sprint 39 — Commercial Boundary + Enterprise Package
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import { runVerify } from '../verify.js';
|
|
7
|
+
import { writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
|
|
10
|
+
const TMP_DIR = `/tmp/test-verify-${Date.now()}`;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
mkdirSync(TMP_DIR, { recursive: true });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
rmSync(TMP_DIR, { recursive: true, force: true });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('baseline verify', () => {
|
|
21
|
+
it('fails with no arguments', async () => {
|
|
22
|
+
const result = await runVerify({});
|
|
23
|
+
expect(result.success).toBe(false);
|
|
24
|
+
expect(result.failed).toBeGreaterThan(0);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('verifies a valid evidence bundle', async () => {
|
|
28
|
+
const evidencePath = join(TMP_DIR, 'evidence.json');
|
|
29
|
+
writeFileSync(evidencePath, JSON.stringify({
|
|
30
|
+
policy: { id: 'policy-1', name: 'Test Policy' },
|
|
31
|
+
latestEvaluation: { passed: true, score: 95 },
|
|
32
|
+
approvals: [{ status: 'approved' }],
|
|
33
|
+
auditTrail: [
|
|
34
|
+
{ type: 'policy_created', actor: 'admin', timestamp: new Date().toISOString() },
|
|
35
|
+
],
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
const result = await runVerify({ evidencePath });
|
|
39
|
+
expect(result.success).toBe(true);
|
|
40
|
+
expect(result.passed).toBeGreaterThan(0);
|
|
41
|
+
expect(result.hash).toBeDefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('fails on missing evidence file', async () => {
|
|
45
|
+
const result = await runVerify({ evidencePath: '/nonexistent/path.json' });
|
|
46
|
+
expect(result.success).toBe(false);
|
|
47
|
+
expect(result.checks.some(c => c.name === 'evidence.exists' && c.status === 'fail')).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('fails on malformed evidence', async () => {
|
|
51
|
+
const evidencePath = join(TMP_DIR, 'bad.json');
|
|
52
|
+
writeFileSync(evidencePath, 'not json');
|
|
53
|
+
|
|
54
|
+
const result = await runVerify({ evidencePath });
|
|
55
|
+
expect(result.success).toBe(false);
|
|
56
|
+
expect(result.checks.some(c => c.status === 'fail')).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('warns on evidence with no policy', async () => {
|
|
60
|
+
const evidencePath = join(TMP_DIR, 'no-policy.json');
|
|
61
|
+
writeFileSync(evidencePath, JSON.stringify({ auditTrail: [] }));
|
|
62
|
+
|
|
63
|
+
const result = await runVerify({ evidencePath });
|
|
64
|
+
expect(result.warnings).toBeGreaterThan(0);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('verifies a valid policy file', async () => {
|
|
68
|
+
const policyPath = join(TMP_DIR, 'policy.json');
|
|
69
|
+
writeFileSync(policyPath, JSON.stringify({
|
|
70
|
+
id: 'policy-1',
|
|
71
|
+
name: 'Test Policy',
|
|
72
|
+
version: '1.0.0',
|
|
73
|
+
status: 'approved',
|
|
74
|
+
rules: [{ id: 'r1', name: 'Rule 1' }],
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
const result = await runVerify({ policyPath });
|
|
78
|
+
expect(result.success).toBe(true);
|
|
79
|
+
expect(result.checks.some(c => c.name === 'policy.identity' && c.status === 'pass')).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('warns on draft policy', async () => {
|
|
83
|
+
const policyPath = join(TMP_DIR, 'draft.json');
|
|
84
|
+
writeFileSync(policyPath, JSON.stringify({
|
|
85
|
+
id: 'p1', name: 'Draft', version: '0.1.0', status: 'draft', rules: [],
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
const result = await runVerify({ policyPath });
|
|
89
|
+
expect(result.checks.some(c => c.name === 'policy.status' && c.status === 'warn')).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('verifies a valid audit trail', async () => {
|
|
93
|
+
const auditPath = join(TMP_DIR, 'audit.json');
|
|
94
|
+
writeFileSync(auditPath, JSON.stringify([
|
|
95
|
+
{ type: 'policy_created', actor: 'admin', timestamp: '2026-01-01T00:00:00Z' },
|
|
96
|
+
{ type: 'policy_approved', actor: 'admin', timestamp: '2026-01-01T01:00:00Z' },
|
|
97
|
+
]));
|
|
98
|
+
|
|
99
|
+
const result = await runVerify({ auditTrailPath: auditPath });
|
|
100
|
+
expect(result.success).toBe(true);
|
|
101
|
+
expect(result.checks.some(c => c.name === 'audit.order' && c.status === 'pass')).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('warns on unordered audit trail', async () => {
|
|
105
|
+
const auditPath = join(TMP_DIR, 'unordered.json');
|
|
106
|
+
writeFileSync(auditPath, JSON.stringify([
|
|
107
|
+
{ type: 'policy_approved', actor: 'admin', timestamp: '2026-01-02T00:00:00Z' },
|
|
108
|
+
{ type: 'policy_created', actor: 'admin', timestamp: '2026-01-01T00:00:00Z' },
|
|
109
|
+
]));
|
|
110
|
+
|
|
111
|
+
const result = await runVerify({ auditTrailPath: auditPath });
|
|
112
|
+
expect(result.checks.some(c => c.name === 'audit.order' && c.status === 'warn')).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('verifies all three inputs together', async () => {
|
|
116
|
+
const policyPath = join(TMP_DIR, 'policy.json');
|
|
117
|
+
const evidencePath = join(TMP_DIR, 'evidence.json');
|
|
118
|
+
const auditPath = join(TMP_DIR, 'audit.json');
|
|
119
|
+
|
|
120
|
+
writeFileSync(policyPath, JSON.stringify({ id: 'p1', name: 'P', version: '1.0', status: 'enforced', rules: [{ id: 'r1' }] }));
|
|
121
|
+
writeFileSync(evidencePath, JSON.stringify({ policy: { id: 'p1' }, auditTrail: [{ type: 'policy_created' }] }));
|
|
122
|
+
writeFileSync(auditPath, JSON.stringify([{ type: 'policy_created', timestamp: '2026-01-01' }]));
|
|
123
|
+
|
|
124
|
+
const result = await runVerify({ policyPath, evidencePath, auditTrailPath: auditPath });
|
|
125
|
+
expect(result.success).toBe(true);
|
|
126
|
+
expect(result.passed).toBeGreaterThanOrEqual(5);
|
|
127
|
+
expect(result.hash).toHaveLength(16);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('returns verification timestamp', async () => {
|
|
131
|
+
const result = await runVerify({});
|
|
132
|
+
expect(result.verifiedAt).toBeDefined();
|
|
133
|
+
expect(new Date(result.verifiedAt).getTime()).toBeGreaterThan(0);
|
|
134
|
+
});
|
|
135
|
+
});
|