agentic-workflow-manager 6.5.2 → 8.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/dist/scripts/sensor-support-matrix.js +103 -0
- package/dist/scripts/support-matrix.js +40 -17
- package/dist/src/commands/init.js +1 -3
- package/dist/src/commands/ledger/index.js +6 -0
- package/dist/src/commands/preflight/checks.js +4 -4
- package/dist/src/commands/preflight/index.js +2 -2
- package/dist/src/commands/registry/add.js +2 -2
- package/dist/src/commands/registry/index.js +5 -0
- package/dist/src/commands/registry/status.js +7 -0
- package/dist/src/commands/sensors/compatibility/contract.js +382 -0
- package/dist/src/commands/sensors/compatibility/discovery.js +207 -0
- package/dist/src/commands/sensors/compatibility/live.js +92 -0
- package/dist/src/commands/sensors/compatibility/manifest.js +207 -0
- package/dist/src/commands/sensors/compatibility/materialize.js +134 -0
- package/dist/src/commands/sensors/compatibility/pack-source.js +78 -0
- package/dist/src/commands/sensors/compatibility/probe.js +47 -0
- package/dist/src/commands/sensors/compatibility/resolve.js +99 -0
- package/dist/src/commands/sensors/compatibility/types.js +2 -0
- package/dist/src/commands/sensors/coverage/contract.js +0 -50
- package/dist/src/commands/sensors/coverage/empirical.js +131 -0
- package/dist/src/commands/sensors/coverage/evaluate.js +85 -7
- package/dist/src/commands/sensors/coverage/evidence.js +5 -1
- package/dist/src/commands/sensors/coverage/index.js +44 -7
- package/dist/src/commands/sensors/coverage/render.js +214 -14
- package/dist/src/commands/sensors/coverage/resolve.js +24 -38
- package/dist/src/commands/sensors/exec.js +244 -9
- package/dist/src/commands/sensors/index.js +18 -6
- package/dist/src/commands/sensors/init.js +95 -6
- package/dist/src/commands/sensors/run.js +71 -0
- package/dist/src/commands/sensors/status.js +48 -4
- package/dist/src/commands/sync.js +7 -0
- package/dist/src/core/bundles.js +38 -3
- package/dist/src/core/init/steps.js +7 -4
- package/dist/src/core/ledger/scan.js +228 -0
- package/dist/src/core/ledger/store.js +89 -25
- package/dist/src/core/ledger/types.js +54 -0
- package/dist/src/core/paths.js +10 -9
- package/dist/src/core/registries.js +134 -14
- package/dist/src/index.js +24 -1
- package/dist/tests/commands/ledger/index.test.js +11 -0
- package/dist/tests/commands/preflight/preflight.test.js +64 -64
- package/dist/tests/commands/registry/add.test.js +69 -2
- package/dist/tests/commands/registry/status.test.js +14 -0
- package/dist/tests/commands/sensors/compatibility/contract.test.js +193 -0
- package/dist/tests/commands/sensors/compatibility/discovery.test.js +119 -0
- package/dist/tests/commands/sensors/compatibility/live.test.js +124 -0
- package/dist/tests/commands/sensors/compatibility/manifest.test.js +77 -0
- package/dist/tests/commands/sensors/compatibility/materialize.test.js +72 -0
- package/dist/tests/commands/sensors/compatibility/pack-source.test.js +55 -0
- package/dist/tests/commands/sensors/compatibility/probe.test.js +37 -0
- package/dist/tests/commands/sensors/compatibility/python-venv-fixture.js +29 -0
- package/dist/tests/commands/sensors/compatibility/resolve.test.js +83 -0
- package/dist/tests/commands/sensors/coverage/contract.test.js +8 -22
- package/dist/tests/commands/sensors/coverage/empirical.test.js +111 -0
- package/dist/tests/commands/sensors/coverage/evaluate.test.js +29 -0
- package/dist/tests/commands/sensors/coverage/evidence.test.js +9 -0
- package/dist/tests/commands/sensors/coverage/index.test.js +40 -13
- package/dist/tests/commands/sensors/coverage/render.test.js +187 -12
- package/dist/tests/commands/sensors/coverage/resolve.test.js +25 -4
- package/dist/tests/commands/sensors/exec-windows.test.js +140 -2
- package/dist/tests/commands/sensors/exec.test.js +115 -5
- package/dist/tests/commands/sensors/index.test.js +8 -0
- package/dist/tests/commands/sensors/init-pack-unavailable.test.js +12 -12
- package/dist/tests/commands/sensors/init.test.js +204 -48
- package/dist/tests/commands/sensors/run-inconclusive.test.js +9 -2
- package/dist/tests/commands/sensors/run-is-read-only.test.js +0 -0
- package/dist/tests/commands/sensors/run.test.js +66 -3
- package/dist/tests/commands/sensors/status-windows.test.js +7 -7
- package/dist/tests/commands/sensors/status.test.js +74 -23
- package/dist/tests/commands/update.test.js +1 -1
- package/dist/tests/core/bundles.test.js +37 -0
- package/dist/tests/core/discovery.test.js +4 -0
- package/dist/tests/core/init/orchestrator.test.js +5 -1
- package/dist/tests/core/init/steps.test.js +20 -4
- package/dist/tests/core/ledger/scan.test.js +217 -0
- package/dist/tests/core/ledger/store.test.js +108 -0
- package/dist/tests/core/profile-pins.test.js +2 -1
- package/dist/tests/core/project-skill-links.test.js +22 -0
- package/dist/tests/core/registries-capability.test.js +10 -0
- package/dist/tests/core/registries-sync.test.js +16 -0
- package/dist/tests/core/registries.test.js +129 -1
- package/dist/tests/core/registry-manifest.test.js +7 -0
- package/dist/tests/core/same-existing-path.test.js +35 -0
- package/dist/tests/core/sync-gates.test.js +2 -1
- package/dist/tests/integration/copilot-init-isolated.test.js +18 -0
- package/dist/tests/integration/sensor-compatibility.e2e.test.js +141 -0
- package/dist/tests/integration/sensor-coverage.e2e.test.js +35 -6
- package/dist/tests/integration/sensor-structured-run.e2e.test.js +127 -0
- package/dist/tests/structural/active-documentation.test.js +128 -0
- package/dist/tests/structural/async-entrypoint.test.js +15 -0
- package/dist/tests/structural/jest-environment-is-isolated.test.js +44 -0
- package/dist/tests/structural/r3-cli-major-version.test.js +37 -0
- package/dist/tests/structural/sensor-documentation-contract.test.js +77 -0
- package/dist/tests/structural/support-matrix-is-current.test.js +69 -2
- package/package.json +8 -2
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const contract_1 = require("../../../../src/commands/sensors/compatibility/contract");
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const coverage = {
|
|
11
|
+
schemaVersion: 1,
|
|
12
|
+
classes: {
|
|
13
|
+
formatting: {
|
|
14
|
+
description: 'Formatting is checked.',
|
|
15
|
+
detectors: [{ sensor: 'lint' }],
|
|
16
|
+
remedy: { summary: 'Add a formatter.', command: 'npm install prettier' },
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
function validPack() {
|
|
21
|
+
return {
|
|
22
|
+
schemaVersion: 2,
|
|
23
|
+
name: 'js-ts', description: 'fixture', detects: ['package.json'],
|
|
24
|
+
sensors: {
|
|
25
|
+
lint: {
|
|
26
|
+
applicability: { allFiles: ['package.json'] },
|
|
27
|
+
variants: [{
|
|
28
|
+
id: 'eslint-9',
|
|
29
|
+
priority: 10,
|
|
30
|
+
requirements: { tool: 'eslint', toolRange: '>=9.0.0 <10.0.0', runtime: 'node', runtimeRange: '>=20.0.0' },
|
|
31
|
+
certifiedRange: '>=9.0.0 <10.0.0',
|
|
32
|
+
assets: ['eslint.config.awm.mjs'], formatter: 'eslint-llm', probe: { kind: 'eslint-print-config' },
|
|
33
|
+
command: {
|
|
34
|
+
executable: 'eslint',
|
|
35
|
+
resolution: 'node-modules-bin',
|
|
36
|
+
args: ['--format', 'json', '{files}'],
|
|
37
|
+
fileInput: { placeholder: '{files}', extensions: ['.ts', '.tsx'] },
|
|
38
|
+
},
|
|
39
|
+
}],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
coverage,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
describe('sensor pack v2 contract', () => {
|
|
46
|
+
it('derives Semgrep compatibility from a contained shared policy reference', () => {
|
|
47
|
+
const sensorPacks = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-semgrep-policy-'));
|
|
48
|
+
const packDir = path_1.default.join(sensorPacks, 'python');
|
|
49
|
+
try {
|
|
50
|
+
fs_1.default.mkdirSync(path_1.default.join(sensorPacks, 'shared'), { recursive: true });
|
|
51
|
+
fs_1.default.mkdirSync(packDir);
|
|
52
|
+
fs_1.default.writeFileSync(path_1.default.join(sensorPacks, 'shared', 'semgrep-policy.json'), JSON.stringify({
|
|
53
|
+
tool: 'semgrep', toolRange: '>=1.0.0', runtime: 'python', runtimeRange: '>=3.9.0', probe: 'semgrep-validate',
|
|
54
|
+
}));
|
|
55
|
+
const pack = validPack();
|
|
56
|
+
pack.sensors.lint.variants[0] = {
|
|
57
|
+
id: 'semgrep-python', priority: 10, certifiedRange: '>=1.0.0', policyRef: 'shared/semgrep-policy.json',
|
|
58
|
+
command: { executable: 'semgrep', resolution: 'path', args: ['--config', '.semgrep.awm.yml', '--json', '.'] },
|
|
59
|
+
assets: ['.semgrep.awm.yml'], formatter: 'semgrep',
|
|
60
|
+
};
|
|
61
|
+
const parsed = (0, contract_1.parseSensorPack)(pack, path_1.default.join(packDir, 'pack.json'));
|
|
62
|
+
expect(parsed).toMatchObject({ kind: 'v2', pack: { sensors: { lint: { variants: [{
|
|
63
|
+
policyRef: 'shared/semgrep-policy.json', requirements: { tool: 'semgrep', runtime: 'python' }, probe: { kind: 'semgrep-validate' },
|
|
64
|
+
}] } } } });
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
fs_1.default.rmSync(sensorPacks, { recursive: true, force: true });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
it('fails closed when a policy reference is not the AWM-owned shared Semgrep policy', () => {
|
|
71
|
+
const pack = validPack();
|
|
72
|
+
pack.sensors.lint.variants[0] = {
|
|
73
|
+
id: 'semgrep-python', priority: 10, certifiedRange: '>=1.0.0', policyRef: '../secret.json',
|
|
74
|
+
command: { executable: 'semgrep', resolution: 'path', args: ['--config', '.semgrep.awm.yml', '--json', '.'] },
|
|
75
|
+
assets: ['.semgrep.awm.yml'], formatter: 'semgrep',
|
|
76
|
+
};
|
|
77
|
+
expect(() => (0, contract_1.parseSensorPack)(pack, '/tmp/sensor-packs/python/pack.json')).toThrow('policyRef');
|
|
78
|
+
});
|
|
79
|
+
it('parses a valid versioned pack', () => {
|
|
80
|
+
expect((0, contract_1.parseSensorPack)(validPack(), 'pack.json')).toMatchObject({ kind: 'v2', pack: validPack() });
|
|
81
|
+
});
|
|
82
|
+
it('accepts an opt-in hardening asset while variants may require no assets', () => {
|
|
83
|
+
const pack = {
|
|
84
|
+
...validPack(),
|
|
85
|
+
hardening: { 'typescript-strict': { assets: ['tsconfig.awm.json'] } },
|
|
86
|
+
sensors: {
|
|
87
|
+
lint: {
|
|
88
|
+
...validPack().sensors.lint,
|
|
89
|
+
variants: [{ ...validPack().sensors.lint.variants[0], assets: [] }],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
expect((0, contract_1.parseSensorPack)(pack, 'pack.json')).toMatchObject({
|
|
94
|
+
kind: 'v2',
|
|
95
|
+
pack: { hardening: { 'typescript-strict': { assets: ['tsconfig.awm.json'] } }, sensors: { lint: { variants: [{ assets: [] }] } } },
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
it.each(['true', 'false'])('accepts the exact ESLINT_USE_FLAT_CONFIG=%s environment mapping', flatConfig => {
|
|
99
|
+
const command = {
|
|
100
|
+
executable: 'npm',
|
|
101
|
+
resolution: 'path',
|
|
102
|
+
args: ['run', 'lint'],
|
|
103
|
+
packageManager: 'npm',
|
|
104
|
+
environment: { ESLINT_USE_FLAT_CONFIG: flatConfig },
|
|
105
|
+
};
|
|
106
|
+
expect((0, contract_1.parseSensorPack)({ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...validPack().sensors.lint.variants[0], command }] } } }, 'pack.json'))
|
|
107
|
+
.toMatchObject({ kind: 'v2', pack: { sensors: { lint: { variants: [{ command }] } } } });
|
|
108
|
+
});
|
|
109
|
+
it.each([
|
|
110
|
+
{ ESLINT_USE_FLAT_CONFIG: 'yes' },
|
|
111
|
+
{ ESLINT_USE_FLAT_CONFIG: 'true', OTHER: 'value' },
|
|
112
|
+
])('rejects an unknown or expanded ESLint environment mapping', environment => {
|
|
113
|
+
const command = { executable: 'npm', resolution: 'path', args: ['run', 'lint'], packageManager: 'npm', environment };
|
|
114
|
+
expect(() => (0, contract_1.parseSensorPack)({ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...validPack().sensors.lint.variants[0], command }] } } }, 'pack.json'))
|
|
115
|
+
.toThrow('command.environment');
|
|
116
|
+
});
|
|
117
|
+
it('normalizes package-manager executable spelling before enforcing its explicit selection', () => {
|
|
118
|
+
const variant = validPack().sensors.lint.variants[0];
|
|
119
|
+
const withExecutable = (executable, packageManager) => ({
|
|
120
|
+
...validPack(),
|
|
121
|
+
sensors: { lint: { ...validPack().sensors.lint, variants: [{
|
|
122
|
+
...variant,
|
|
123
|
+
command: { executable, resolution: 'path', args: ['run', 'lint'], ...(packageManager === undefined ? {} : { packageManager }) },
|
|
124
|
+
}] } },
|
|
125
|
+
});
|
|
126
|
+
expect(() => (0, contract_1.parseSensorPack)(withExecutable('NPM'), 'pack.json')).toThrow('packageManager');
|
|
127
|
+
expect((0, contract_1.parseSensorPack)(withExecutable('npm.exe', 'NPM'), 'pack.json')).toMatchObject({
|
|
128
|
+
kind: 'v2', pack: { sensors: { lint: { variants: [{ command: { executable: 'npm.exe', packageManager: 'npm' } }] } } },
|
|
129
|
+
});
|
|
130
|
+
expect(() => (0, contract_1.parseSensorPack)(withExecutable('NPM', 'pnpm'), 'pack.json')).toThrow('match executable');
|
|
131
|
+
});
|
|
132
|
+
it('keeps an unversioned pack on the legacy compatibility path', () => {
|
|
133
|
+
const legacy = { name: 'legacy', sensors: {} };
|
|
134
|
+
expect((0, contract_1.parseSensorPack)(legacy, 'pack.json')).toMatchObject({ kind: 'legacy', pack: { ...legacy, compatibility: { state: 'compatible-unverified' } } });
|
|
135
|
+
});
|
|
136
|
+
test.each([
|
|
137
|
+
[{ name: 'legacy', detects: [null], sensors: {} }, 'detects[0]'],
|
|
138
|
+
[{ name: 'legacy', detects: ['package.json'], sensors: { lint: { unknown: true } } }, 'unknown field'],
|
|
139
|
+
[{ name: 'legacy', detects: ['package.json'], sensors: { lint: { defaultCmd: 'eslint\u0000 .' } } }, 'defaultCmd'],
|
|
140
|
+
[{ name: 'legacy', detects: ['package.json'], sensors: { lint: { changedExtensions: ['.ts', 3] } } }, 'changedExtensions[1]'],
|
|
141
|
+
[{ name: 'legacy', detects: ['package.json'], sensors: { 'bad/name': {} } }, 'sensor id'],
|
|
142
|
+
])('rejects recursively malformed legacy content %j', (input, message) => {
|
|
143
|
+
expect(() => (0, contract_1.parseSensorPack)(input, 'legacy-pack.json')).toThrow(message);
|
|
144
|
+
});
|
|
145
|
+
it('preserves legacy config file fallbacks', () => {
|
|
146
|
+
expect((0, contract_1.parseSensorPack)({ name: 'legacy', sensors: { lint: { configFile: 'eslint.config.js', configFileFallback: '.eslintrc.json' } } }, 'legacy')).toMatchObject({ kind: 'legacy', pack: { sensors: { lint: { configFile: 'eslint.config.js', configFileFallback: '.eslintrc.json' } } } });
|
|
147
|
+
});
|
|
148
|
+
it('rejects malformed applicability and probe extensions', () => {
|
|
149
|
+
const variant = validPack().sensors.lint.variants[0];
|
|
150
|
+
expect(() => (0, contract_1.parseSensorPack)({ ...validPack(), sensors: { lint: { applicability: { allFiles: [3] }, variants: [variant] } } }, 'pack')).toThrow('allFiles[0]');
|
|
151
|
+
expect(() => (0, contract_1.parseSensorPack)({ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...variant, probe: { kind: 'version', extra: true } }] } } }, 'pack')).toThrow('unknown field');
|
|
152
|
+
});
|
|
153
|
+
it('preserves configFiles and rejects incomplete public overlap input', () => {
|
|
154
|
+
const variant = { ...validPack().sensors.lint.variants[0], requirements: { ...validPack().sensors.lint.variants[0].requirements, configFiles: ['eslint.config.js'] } };
|
|
155
|
+
expect((0, contract_1.parseSensorPack)({ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [variant] } } }, 'pack')).toMatchObject({ kind: 'v2', pack: { sensors: { lint: { variants: [{ requirements: { configFiles: ['eslint.config.js'] } }] } } } });
|
|
156
|
+
const complete = validPack().sensors.lint.variants[0];
|
|
157
|
+
expect(() => (0, contract_1.assertNoEqualPriorityOverlap)([{ ...complete, id: '../bad' }])).toThrow('stable lowercase id');
|
|
158
|
+
expect(() => (0, contract_1.assertNoEqualPriorityOverlap)([{ ...complete, command: undefined }])).toThrow('command');
|
|
159
|
+
});
|
|
160
|
+
it('rejects equal-priority variants with overlapping operational tool ranges', () => {
|
|
161
|
+
const first = validPack().sensors.lint.variants[0];
|
|
162
|
+
const second = { ...first, id: 'eslint-9-alt', certifiedRange: '>=10 <11', requirements: { ...first.requirements, toolRange: '>=9.5 <10.5' } };
|
|
163
|
+
expect(() => (0, contract_1.assertNoEqualPriorityOverlap)([first, second])).toThrow('overlap');
|
|
164
|
+
});
|
|
165
|
+
it('accepts overlapping tool ranges when runtime ranges are disjoint', () => {
|
|
166
|
+
const first = validPack().sensors.lint.variants[0];
|
|
167
|
+
const second = { ...first, id: 'eslint-9-node-old', requirements: { ...first.requirements, toolRange: '>=9.5 <10.5', runtimeRange: '>=18 <20' } };
|
|
168
|
+
expect(() => (0, contract_1.assertNoEqualPriorityOverlap)([first, second])).not.toThrow();
|
|
169
|
+
});
|
|
170
|
+
test.each([
|
|
171
|
+
[{ ...validPack(), schemaVersion: 3 }, 'supported: legacy, 2'],
|
|
172
|
+
[{ ...validPack(), sensors: { lint: { variants: [] } } }, 'variants'],
|
|
173
|
+
[{ ...validPack(), name: '../escape' }, 'name'],
|
|
174
|
+
[{ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...validPack().sensors.lint.variants[0], assets: ['../secret'] }] } } }, 'asset'],
|
|
175
|
+
[{ ...validPack(), sensors: { lint: { variants: [{ ...validPack().sensors.lint.variants[0], command: { executable: 'sh', resolution: 'path', args: ['-c', 'echo nope'] } }] } } }, 'executable'],
|
|
176
|
+
[{ ...validPack(), coverage: { ...coverage, schemaVersion: 2 } }, 'coverage.schemaVersion'],
|
|
177
|
+
[{ ...validPack(), sensors: { lint: { variants: [{ ...validPack().sensors.lint.variants[0], certifiedRange: 'not-a-range' }] } } }, 'certifiedRange'],
|
|
178
|
+
[{ ...validPack(), sensors: { lint: validPack().sensors.lint, lint2: validPack().sensors.lint } }, 'variant id'],
|
|
179
|
+
[{ ...validPack(), sensors: { lint: { variants: [validPack().sensors.lint.variants[0], { ...validPack().sensors.lint.variants[0], id: 'eslint-9-next', certifiedRange: '>=9.1.0 <10.0.0' }] } } }, 'overlap'],
|
|
180
|
+
[{ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...validPack().sensors.lint.variants[0], assets: ['C:/secret'] }] } } }, 'asset'],
|
|
181
|
+
[{ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...validPack().sensors.lint.variants[0], command: { executable: 'cmd.exe', resolution: 'path', args: ['x'] } }] } } }, 'executable'],
|
|
182
|
+
[{ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...validPack().sensors.lint.variants[0], command: { executable: 'npm', resolution: 'path', args: ['run', 'lint'] } }] } } }, 'packageManager'],
|
|
183
|
+
[{ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...validPack().sensors.lint.variants[0], command: { executable: 'npm', resolution: 'path', args: ['run', 'lint'], packageManager: 'pnpm' } }] } } }, 'match executable'],
|
|
184
|
+
[{ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...validPack().sensors.lint.variants[0], command: { ...validPack().sensors.lint.variants[0].command, environment: { NODE_OPTIONS: '--require unsafe' } } }] } } }, 'environment'],
|
|
185
|
+
[{ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...validPack().sensors.lint.variants[0], command: { ...validPack().sensors.lint.variants[0].command, args: ['prefix{files}'] } }] } } }, 'embed'],
|
|
186
|
+
[{ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...validPack().sensors.lint.variants[0], formatter: 'bad\nformat' }] } } }, 'formatter'],
|
|
187
|
+
[{ ...validPack(), hardening: { 'typescript-strict': { assets: ['../tsconfig.awm.json'] } } }, 'asset'],
|
|
188
|
+
[{ ...validPack(), hardening: { 'typescript-strict': { assets: [] } } }, 'hardening'],
|
|
189
|
+
[{ ...validPack(), hardening: { 'typescript-strict': { assets: ['tsconfig.awm.json'], command: {} } } }, 'unknown field'],
|
|
190
|
+
])('rejects malformed pack %j', (input, message) => {
|
|
191
|
+
expect(() => (0, contract_1.parseSensorPack)(input, 'pack.json')).toThrow(message);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const discovery_1 = require("../../../../src/commands/sensors/compatibility/discovery");
|
|
10
|
+
describe('discoverProjectEvidence', () => {
|
|
11
|
+
it('returns only local, relative project evidence and detects conflicting lockfiles', () => {
|
|
12
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-discovery-'));
|
|
13
|
+
try {
|
|
14
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'package.json'), JSON.stringify({ scripts: { lint: 'eslint .' }, devDependencies: { eslint: '10.0.0' } }));
|
|
15
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'package-lock.json'), '{}');
|
|
16
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'pnpm-lock.yaml'), 'lockfileVersion: 9');
|
|
17
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'eslint.config.js'), 'export default []');
|
|
18
|
+
fs_1.default.mkdirSync(path_1.default.join(root, 'node_modules', 'eslint'), { recursive: true });
|
|
19
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ name: 'eslint', version: '10.4.1' }));
|
|
20
|
+
const evidence = (0, discovery_1.discoverProjectEvidence)(root, { schemaVersion: 2, name: 'js-ts', detects: ['package.json'], sensors: { lint: { applicability: { allFiles: ['package.json'] }, variants: [{ requirements: { tool: 'eslint', configFiles: [] } }] } } }, { platform: () => 'darwin' });
|
|
21
|
+
expect(evidence.packageManagerConflict).toBe(true);
|
|
22
|
+
expect(evidence.os).toBe('darwin');
|
|
23
|
+
expect(evidence.declaredToolRanges.eslint).toBe('10.0.0');
|
|
24
|
+
expect(evidence.toolVersions.eslint).toBe('10.4.1');
|
|
25
|
+
expect(evidence.scripts).toContain('lint');
|
|
26
|
+
expect(evidence.configFiles).toContain('eslint.config.js');
|
|
27
|
+
expect(evidence.paths.every((item) => !path_1.default.isAbsolute(item) && !item.includes('..'))).toBe(true);
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
it('includes pack and applicability markers when deciding whether a sensor applies', () => {
|
|
34
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-discovery-markers-'));
|
|
35
|
+
try {
|
|
36
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'pack-marker'), 'present');
|
|
37
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'sensor-marker'), 'present');
|
|
38
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'one-of-these'), 'present');
|
|
39
|
+
const evidence = (0, discovery_1.discoverProjectEvidence)(root, {
|
|
40
|
+
schemaVersion: 2,
|
|
41
|
+
name: 'custom',
|
|
42
|
+
detects: ['pack-marker'],
|
|
43
|
+
sensors: {
|
|
44
|
+
lint: {
|
|
45
|
+
applicability: { allFiles: ['sensor-marker'], anyFiles: ['one-of-these', 'missing-marker'] },
|
|
46
|
+
variants: [{ requirements: { tool: 'eslint', configFiles: [] } }],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
expect(evidence.paths).toEqual(expect.arrayContaining(['pack-marker', 'sensor-marker', 'one-of-these']));
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
it('does not report package.json as evidence when the project has none', () => {
|
|
57
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-discovery-no-package-'));
|
|
58
|
+
try {
|
|
59
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'pyproject.toml'), '[project]\nname = "sample"');
|
|
60
|
+
const evidence = (0, discovery_1.discoverProjectEvidence)(root, {
|
|
61
|
+
schemaVersion: 2,
|
|
62
|
+
name: 'python',
|
|
63
|
+
detects: ['pyproject.toml'],
|
|
64
|
+
sensors: {
|
|
65
|
+
lint: { applicability: { allFiles: ['package.json'] }, variants: [{ requirements: { tool: 'ruff', configFiles: [] } }] },
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
expect(evidence.paths).not.toContain('package.json');
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
test.each([
|
|
75
|
+
['linux', ['.venv', 'lib', 'python3.12', 'site-packages']],
|
|
76
|
+
['win32', ['.venv', 'Lib', 'site-packages']],
|
|
77
|
+
])('discovers exact local Python runtime and tool metadata on %s', (targetPlatform, sitePackages) => {
|
|
78
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-python-discovery-'));
|
|
79
|
+
try {
|
|
80
|
+
fs_1.default.mkdirSync(path_1.default.join(root, ...sitePackages), { recursive: true });
|
|
81
|
+
fs_1.default.writeFileSync(path_1.default.join(root, '.venv', 'pyvenv.cfg'), 'version = 3.12.4\n');
|
|
82
|
+
for (const [tool, version] of Object.entries({ mypy: '1.11.2', ruff: '0.6.9', pytest: '8.3.3', semgrep: '1.91.0' })) {
|
|
83
|
+
const info = path_1.default.join(root, ...sitePackages, `${tool}-${version}.dist-info`);
|
|
84
|
+
fs_1.default.mkdirSync(info);
|
|
85
|
+
fs_1.default.writeFileSync(path_1.default.join(info, 'METADATA'), `Metadata-Version: 2.3\nName: ${tool}\nVersion: ${version}\n`);
|
|
86
|
+
}
|
|
87
|
+
const pack = { schemaVersion: 2, name: 'python', detects: ['pyproject.toml'], sensors: {
|
|
88
|
+
quality: { applicability: { allFiles: ['pyproject.toml'] }, variants: Object.keys({ mypy: 0, ruff: 0, pytest: 0, semgrep: 0 }).map(tool => ({ requirements: { tool, runtime: 'python', configFiles: [] } })) },
|
|
89
|
+
} };
|
|
90
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'pyproject.toml'), '[project]\nname = "sample"');
|
|
91
|
+
const evidence = (0, discovery_1.discoverProjectEvidence)(root, pack, { platform: () => targetPlatform });
|
|
92
|
+
expect(evidence.runtimeVersions.python).toBe('3.12.4');
|
|
93
|
+
expect(evidence.toolVersions).toMatchObject({ mypy: '1.11.2', ruff: '0.6.9', pytest: '8.3.3', semgrep: '1.91.0' });
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
it('does not trust missing or escaping Python environment metadata', () => {
|
|
100
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-python-discovery-untrusted-'));
|
|
101
|
+
const outside = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-python-discovery-outside-'));
|
|
102
|
+
try {
|
|
103
|
+
fs_1.default.mkdirSync(path_1.default.join(root, '.venv', 'lib', 'python3.12', 'site-packages'), { recursive: true });
|
|
104
|
+
fs_1.default.writeFileSync(path_1.default.join(root, '.venv', 'pyvenv.cfg'), 'version = not-a-version\n');
|
|
105
|
+
const escaped = path_1.default.join(root, '.venv', 'lib', 'python3.12', 'site-packages', 'ruff-0.6.9.dist-info');
|
|
106
|
+
fs_1.default.mkdirSync(path_1.default.join(outside, 'ruff-0.6.9.dist-info'));
|
|
107
|
+
fs_1.default.writeFileSync(path_1.default.join(outside, 'ruff-0.6.9.dist-info', 'METADATA'), 'Name: ruff\nVersion: 0.6.9\n');
|
|
108
|
+
fs_1.default.symlinkSync(path_1.default.join(outside, 'ruff-0.6.9.dist-info'), escaped, 'dir');
|
|
109
|
+
const pack = { schemaVersion: 2, name: 'python', detects: [], sensors: { lint: { applicability: {}, variants: [{ requirements: { tool: 'ruff', runtime: 'python', configFiles: [] } }] } } };
|
|
110
|
+
const evidence = (0, discovery_1.discoverProjectEvidence)(root, pack, { platform: () => 'linux' });
|
|
111
|
+
expect(evidence.runtimeVersions.python).toBeNull();
|
|
112
|
+
expect(evidence.toolVersions.ruff).toBeNull();
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
116
|
+
fs_1.default.rmSync(outside, { recursive: true, force: true });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const live_1 = require("../../../../src/commands/sensors/compatibility/live");
|
|
10
|
+
const exec_1 = require("../../../../src/commands/sensors/exec");
|
|
11
|
+
const python_venv_fixture_1 = require("./python-venv-fixture");
|
|
12
|
+
const itPosix = process.platform !== 'win32' ? it : it.skip;
|
|
13
|
+
describe('resolveParsedPackCompatibility — contained Python commands', () => {
|
|
14
|
+
test.each([
|
|
15
|
+
['linux', ['.venv', 'bin', 'semgrep']],
|
|
16
|
+
['win32', ['.venv', 'Scripts', 'semgrep.exe']],
|
|
17
|
+
])('creates a local Semgrep executable in the %s virtual-environment layout', (targetPlatform, executableParts) => {
|
|
18
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-semgrep-venv-layout-'));
|
|
19
|
+
try {
|
|
20
|
+
(0, python_venv_fixture_1.createSemgrepVenvFixture)(root, targetPlatform);
|
|
21
|
+
expect(fs_1.default.lstatSync(path_1.default.join(root, ...executableParts)).isFile()).toBe(true);
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
it('resolves the Windows virtual-environment executable from Scripts instead of PATH', async () => {
|
|
28
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-semgrep-windows-resolution-'));
|
|
29
|
+
const global = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-semgrep-windows-global-'));
|
|
30
|
+
const originalPlatform = process.platform;
|
|
31
|
+
const savedPath = process.env.PATH;
|
|
32
|
+
const savedPathExt = process.env.PATHEXT;
|
|
33
|
+
try {
|
|
34
|
+
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });
|
|
35
|
+
process.env.PATH = global;
|
|
36
|
+
process.env.PATHEXT = '.EXE';
|
|
37
|
+
(0, python_venv_fixture_1.createSemgrepVenvFixture)(root, 'win32');
|
|
38
|
+
fs_1.default.writeFileSync(path_1.default.join(global, 'semgrep.exe'), 'must-not-run');
|
|
39
|
+
await expect((0, exec_1.runStructuredCommand)({
|
|
40
|
+
executable: 'semgrep', resolution: 'python-environment', pythonEnvironmentRoot: '.venv', args: ['--version'],
|
|
41
|
+
}, { cwd: root, timeout: 5_000 })).resolves.toMatchObject({
|
|
42
|
+
code: 0,
|
|
43
|
+
stdout: expect.stringContaining(process.version),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true });
|
|
48
|
+
if (savedPath === undefined)
|
|
49
|
+
delete process.env.PATH;
|
|
50
|
+
else
|
|
51
|
+
process.env.PATH = savedPath;
|
|
52
|
+
if (savedPathExt === undefined)
|
|
53
|
+
delete process.env.PATHEXT;
|
|
54
|
+
else
|
|
55
|
+
process.env.PATHEXT = savedPathExt;
|
|
56
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
57
|
+
fs_1.default.rmSync(global, { recursive: true, force: true });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
itPosix('binds Semgrep discovery, validation probe, and execution to the same local virtual-environment executable', async () => {
|
|
61
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-semgrep-contained-'));
|
|
62
|
+
const global = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-semgrep-global-'));
|
|
63
|
+
const savedPath = process.env.PATH;
|
|
64
|
+
try {
|
|
65
|
+
(0, python_venv_fixture_1.createSemgrepVenvFixture)(root);
|
|
66
|
+
fs_1.default.writeFileSync(path_1.default.join(root, '.venv', 'bin', 'semgrep'), '#!/bin/sh\nif [ "$1" = "--validate" ]; then echo local-semgrep; exit 0; fi\nexit 1\n', { mode: 0o755 });
|
|
67
|
+
fs_1.default.writeFileSync(path_1.default.join(global, 'semgrep'), '#!/bin/sh\necho global-semgrep\n', { mode: 0o755 });
|
|
68
|
+
process.env.PATH = global;
|
|
69
|
+
const pack = {
|
|
70
|
+
schemaVersion: 2,
|
|
71
|
+
name: 'python',
|
|
72
|
+
description: 'test',
|
|
73
|
+
detects: ['pyproject.toml'],
|
|
74
|
+
coverage: {},
|
|
75
|
+
sensors: {
|
|
76
|
+
security: {
|
|
77
|
+
applicability: { allFiles: ['pyproject.toml'] },
|
|
78
|
+
variants: [{
|
|
79
|
+
id: 'semgrep-1', priority: 1, certifiedRange: '>=1 <2',
|
|
80
|
+
requirements: { tool: 'semgrep', toolRange: '>=1 <2', runtime: 'python', runtimeRange: '>=3.12 <4' },
|
|
81
|
+
assets: [], formatter: 'semgrep', probe: { kind: 'semgrep-validate' },
|
|
82
|
+
command: { executable: 'semgrep', resolution: 'path', args: ['--validate'] },
|
|
83
|
+
}],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'pyproject.toml'), '[project]\nname = "sample"\n');
|
|
88
|
+
const live = await (0, live_1.resolveParsedPackCompatibility)(root, pack);
|
|
89
|
+
const command = live.pack.sensors.security.variants[0].command;
|
|
90
|
+
expect(live.sensors.security).toMatchObject({ state: 'certified', toolVersion: '1.91.0' });
|
|
91
|
+
expect(command).toMatchObject({ executable: 'semgrep', resolution: 'python-environment' });
|
|
92
|
+
await expect((0, exec_1.runStructuredCommand)(command, { cwd: root, timeout: 5_000 })).resolves.toMatchObject({ code: 0, stdout: 'local-semgrep\n' });
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
process.env.PATH = savedPath;
|
|
96
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
97
|
+
fs_1.default.rmSync(global, { recursive: true, force: true });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
it('never falls back from the discovered .venv to a sibling venv executable', async () => {
|
|
101
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-semgrep-environment-identity-'));
|
|
102
|
+
try {
|
|
103
|
+
(0, python_venv_fixture_1.createSemgrepVenvFixture)(root, process.platform, '.venv', { executable: false });
|
|
104
|
+
(0, python_venv_fixture_1.createSemgrepVenvFixture)(root, process.platform, 'venv');
|
|
105
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'pyproject.toml'), '[project]\nname = "sample"\n');
|
|
106
|
+
const pack = {
|
|
107
|
+
schemaVersion: 2, name: 'python', description: 'test', detects: ['pyproject.toml'], coverage: {},
|
|
108
|
+
sensors: { security: { applicability: { allFiles: ['pyproject.toml'] }, variants: [{
|
|
109
|
+
id: 'semgrep-1', priority: 1, certifiedRange: '>=1 <2',
|
|
110
|
+
requirements: { tool: 'semgrep', toolRange: '>=1 <2', runtime: 'python', runtimeRange: '>=3.12 <4' },
|
|
111
|
+
assets: [], formatter: 'semgrep', probe: { kind: 'semgrep-validate' },
|
|
112
|
+
command: { executable: 'semgrep', resolution: 'path', args: ['--validate'] },
|
|
113
|
+
}] } },
|
|
114
|
+
};
|
|
115
|
+
const live = await (0, live_1.resolveParsedPackCompatibility)(root, pack);
|
|
116
|
+
expect(live.sensors.security).toMatchObject({ state: 'unverifiable', reason: 'probe-inconclusive', toolVersion: '1.91.0' });
|
|
117
|
+
expect(() => (0, exec_1.runStructuredCommand)(live.pack.sensors.security.variants[0].command, { cwd: root, timeout: 5_000 }))
|
|
118
|
+
.toThrow('python environment executable is not a contained local regular file');
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const manifest_1 = require("../../../../src/commands/sensors/compatibility/manifest");
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
describe('sensor manifest contract', () => {
|
|
10
|
+
it('keeps compatibility contracts on an acyclic import boundary', () => {
|
|
11
|
+
const source = (relative) => fs_1.default.readFileSync(path_1.default.join(__dirname, '../../../../src/commands/sensors', relative), 'utf8');
|
|
12
|
+
expect(source('types.ts')).not.toContain('./compatibility/');
|
|
13
|
+
expect(source('compatibility/types.ts')).not.toContain('../coverage/');
|
|
14
|
+
expect(source('coverage/contract.ts')).not.toContain('../compatibility/manifest');
|
|
15
|
+
});
|
|
16
|
+
it('normalizes a legacy string command with compatible-unverified evidence', () => {
|
|
17
|
+
expect((0, manifest_1.parseSensorManifest)({ pack: 'js-ts', sensors: { lint: 'npm run lint' } }, 'sensors.json')).toMatchObject({ kind: 'legacy', pack: {
|
|
18
|
+
pack: 'js-ts',
|
|
19
|
+
sensors: { lint: { cmd: 'npm run lint' } },
|
|
20
|
+
compatibility: (0, manifest_1.legacyCompatibility)(),
|
|
21
|
+
} });
|
|
22
|
+
});
|
|
23
|
+
it('accepts a v2 selected variant and structured command', () => {
|
|
24
|
+
const manifest = {
|
|
25
|
+
schemaVersion: 2,
|
|
26
|
+
pack: 'js-ts',
|
|
27
|
+
sensors: {
|
|
28
|
+
lint: {
|
|
29
|
+
enabled: true, variantId: 'eslint-9',
|
|
30
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.', '--format', 'json'] },
|
|
31
|
+
initializedCompatibility: { state: 'certified', reason: 'range-and-probe', variantId: 'eslint-9', toolVersion: '9.0.0', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
expect((0, manifest_1.parseSensorManifest)(manifest, 'sensors.json')).toMatchObject({ kind: 'v2', pack: manifest });
|
|
36
|
+
expect(JSON.parse((0, manifest_1.serializeManifestV2)(manifest))).toEqual(manifest);
|
|
37
|
+
});
|
|
38
|
+
it('persists only an explicit v2 pack selection as applicability provenance', () => {
|
|
39
|
+
const manifest = {
|
|
40
|
+
schemaVersion: 2, pack: 'generic', packSelection: 'explicit',
|
|
41
|
+
sensors: {},
|
|
42
|
+
};
|
|
43
|
+
expect((0, manifest_1.parseSensorManifest)(manifest, 'sensors.json')).toMatchObject({ kind: 'v2', pack: { packSelection: 'explicit' } });
|
|
44
|
+
expect(JSON.parse((0, manifest_1.serializeManifestV2)(manifest))).toEqual(manifest);
|
|
45
|
+
expect(() => (0, manifest_1.parseSensorManifest)({ ...manifest, packSelection: 'detected' }, 'sensors.json')).toThrow('packSelection');
|
|
46
|
+
});
|
|
47
|
+
it('accepts optional contained assets and rejects traversal', () => {
|
|
48
|
+
const sensor = { enabled: true, variantId: 'eslint-9', assets: ['eslint.config.awm.mjs'], command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, initializedCompatibility: { state: 'certified', reason: 'ok', variantId: 'eslint-9', toolVersion: '9.0.0', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] } };
|
|
49
|
+
expect((0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: sensor } }, 'sensors.json')).toMatchObject({ kind: 'v2' });
|
|
50
|
+
expect(() => (0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: { ...sensor, assets: ['C:/secret'] } } }, 'sensors.json')).toThrow('asset');
|
|
51
|
+
});
|
|
52
|
+
it('rejects mismatched compatibility variant and hostile source labels', () => {
|
|
53
|
+
const sensor = { enabled: true, variantId: 'eslint-9', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, initializedCompatibility: { state: 'certified', reason: 'ok', variantId: 'other', toolVersion: '9.0.0', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] } };
|
|
54
|
+
expect(() => (0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: sensor } }, { path: 'bad' })).toThrow('<unknown source>');
|
|
55
|
+
expect(() => (0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: sensor } }, 'source\nleak')).toThrow('<unknown source>');
|
|
56
|
+
});
|
|
57
|
+
it('rejects invalid semver evidence independently', () => {
|
|
58
|
+
const sensor = { enabled: true, variantId: 'eslint-9', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, initializedCompatibility: { state: 'certified', reason: 'ok', variantId: 'eslint-9', toolVersion: 'bad', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] } };
|
|
59
|
+
expect(() => (0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: sensor } }, 'source')).toThrow('toolVersion');
|
|
60
|
+
});
|
|
61
|
+
it('rejects certified evidence without a selected variant', () => {
|
|
62
|
+
const sensor = { enabled: true, variantId: 'eslint-9', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, initializedCompatibility: { state: 'certified', reason: 'ok', variantId: null, toolVersion: '9.0.0', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] } };
|
|
63
|
+
expect(() => (0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: sensor } }, 'source')).toThrow('variantId');
|
|
64
|
+
});
|
|
65
|
+
it('rejects a certified tool version outside its certified range', () => {
|
|
66
|
+
const sensor = { enabled: true, variantId: 'eslint-9', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, initializedCompatibility: { state: 'certified', reason: 'ok', variantId: 'eslint-9', toolVersion: '10.0.0', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] } };
|
|
67
|
+
expect(() => (0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: sensor } }, 'source')).toThrow('certifiedRange');
|
|
68
|
+
});
|
|
69
|
+
test.each([
|
|
70
|
+
[null, 'object'],
|
|
71
|
+
[{}, 'pack'],
|
|
72
|
+
[{ schemaVersion: 3, pack: 'js-ts', sensors: {} }, 'schemaVersion'],
|
|
73
|
+
[{ schemaVersion: 2, pack: 'js-ts', sensors: { lint: { variantId: 'eslint-9', command: { executable: 'eslint', resolution: 'path', args: [] } } } }, 'enabled'],
|
|
74
|
+
])('rejects malformed manifest %j', (input, message) => {
|
|
75
|
+
expect(() => (0, manifest_1.parseSensorManifest)(input, 'sensors.json')).toThrow(message);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const materialize_1 = require("../../../../src/commands/sensors/compatibility/materialize");
|
|
10
|
+
const evidence = { state: 'certified', reason: 'range-and-probe', variantId: 'eslint-10', toolVersion: '10.0.0', runtimeVersion: '22.0.0', certifiedRange: '>=10 <11', evidence: [] };
|
|
11
|
+
describe('materializeResolvedSensors', () => {
|
|
12
|
+
let projectRoot;
|
|
13
|
+
let packRoot;
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
projectRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-project-'));
|
|
16
|
+
packRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-pack-'));
|
|
17
|
+
fs_1.default.writeFileSync(path_1.default.join(packRoot, 'eslint.config.awm.mjs'), 'export default [];');
|
|
18
|
+
fs_1.default.writeFileSync(path_1.default.join(packRoot, 'tsconfig.awm.json'), '{}');
|
|
19
|
+
});
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
fs_1.default.rmSync(projectRoot, { recursive: true, force: true });
|
|
22
|
+
fs_1.default.rmSync(packRoot, { recursive: true, force: true });
|
|
23
|
+
});
|
|
24
|
+
it('copies only selected assets and writes a v2 manifest atomically', () => {
|
|
25
|
+
const result = (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
|
|
26
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
27
|
+
} });
|
|
28
|
+
expect(result.configured).toEqual(['eslint.config.awm.mjs']);
|
|
29
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'tsconfig.awm.json'))).toBe(false);
|
|
30
|
+
expect(JSON.parse(fs_1.default.readFileSync(path_1.default.join(projectRoot, '.awm', 'sensors.json'), 'utf8'))).toMatchObject({ schemaVersion: 2, sensors: { lint: { variantId: 'eslint-10' } } });
|
|
31
|
+
expect(fs_1.default.readdirSync(path_1.default.join(projectRoot, '.awm'))).not.toContain('sensors.json.tmp');
|
|
32
|
+
});
|
|
33
|
+
it('preserves a destination that already exists', () => {
|
|
34
|
+
fs_1.default.writeFileSync(path_1.default.join(projectRoot, 'eslint.config.awm.mjs'), 'owner content');
|
|
35
|
+
const result = (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
|
|
36
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
37
|
+
} });
|
|
38
|
+
expect(result.preserved).toEqual(['eslint.config.awm.mjs']);
|
|
39
|
+
expect(fs_1.default.readFileSync(path_1.default.join(projectRoot, 'eslint.config.awm.mjs'), 'utf8')).toBe('owner content');
|
|
40
|
+
});
|
|
41
|
+
it('revalidates a shared Semgrep policy reference without materializing the policy itself', () => {
|
|
42
|
+
const sensorPacks = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-policy-'));
|
|
43
|
+
const semgrepPack = path_1.default.join(sensorPacks, 'python');
|
|
44
|
+
try {
|
|
45
|
+
fs_1.default.mkdirSync(path_1.default.join(sensorPacks, 'shared'), { recursive: true });
|
|
46
|
+
fs_1.default.mkdirSync(semgrepPack);
|
|
47
|
+
fs_1.default.writeFileSync(path_1.default.join(semgrepPack, 'pack.json'), '{}');
|
|
48
|
+
fs_1.default.writeFileSync(path_1.default.join(semgrepPack, '.semgrep.awm.yml'), 'rules: []\n');
|
|
49
|
+
fs_1.default.writeFileSync(path_1.default.join(sensorPacks, 'shared', 'semgrep-policy.json'), JSON.stringify({ tool: 'semgrep', toolRange: '>=1.0.0', runtime: 'python', runtimeRange: '>=3.9.0', probe: 'semgrep-validate' }));
|
|
50
|
+
const result = (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot: semgrepPack, pack: 'python', sensors: {
|
|
51
|
+
security: { enabled: true, variantId: 'semgrep-python', command: { executable: 'semgrep', resolution: 'path', args: ['--config', '.semgrep.awm.yml', '--json', '.'] }, assets: ['.semgrep.awm.yml'], policyRef: 'shared/semgrep-policy.json', initializedCompatibility: { ...evidence, variantId: 'semgrep-python' } },
|
|
52
|
+
} });
|
|
53
|
+
expect(result.configured).toEqual(['.semgrep.awm.yml']);
|
|
54
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'shared', 'semgrep-policy.json'))).toBe(false);
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
fs_1.default.rmSync(sensorPacks, { recursive: true, force: true });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
it('reports previous AWM assets as orphaned and never deletes them', () => {
|
|
61
|
+
fs_1.default.mkdirSync(path_1.default.join(projectRoot, '.awm'));
|
|
62
|
+
fs_1.default.writeFileSync(path_1.default.join(projectRoot, '.awm', 'sensors.json'), JSON.stringify({ schemaVersion: 2, pack: 'js-ts', sensors: {
|
|
63
|
+
lint: { enabled: true, variantId: 'old', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.cjs'], initializedCompatibility: { ...evidence, variantId: 'old' } },
|
|
64
|
+
} }));
|
|
65
|
+
fs_1.default.writeFileSync(path_1.default.join(projectRoot, 'eslint.config.awm.cjs'), 'old');
|
|
66
|
+
const result = (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
|
|
67
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
68
|
+
} });
|
|
69
|
+
expect(result.orphaned).toEqual(['eslint.config.awm.cjs']);
|
|
70
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'eslint.config.awm.cjs'))).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
});
|