agentic-workflow-manager 9.16.1 → 9.18.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/src/commands/env/index.js +192 -0
- package/dist/src/commands/preflight/checks.js +63 -13
- package/dist/src/commands/registry/doctor.js +428 -0
- package/dist/src/commands/registry/index.js +52 -0
- package/dist/src/commands/registry/scaffold.js +140 -0
- package/dist/src/commands/sensors/bootstrap.js +2 -1
- package/dist/src/commands/sensors/compatibility/contract.js +30 -26
- package/dist/src/commands/sensors/compatibility/discovery.js +21 -0
- package/dist/src/commands/sensors/compatibility/manifest.js +169 -17
- package/dist/src/commands/sensors/compatibility/materialize.js +30 -5
- package/dist/src/commands/sensors/compatibility/source.js +3 -2
- package/dist/src/commands/sensors/coverage/render.js +8 -2
- package/dist/src/commands/sensors/coverage/resolve.js +7 -0
- package/dist/src/commands/sensors/init.js +15 -2
- package/dist/src/commands/sensors/migrate.js +13 -10
- package/dist/src/commands/sensors/prepare.js +53 -6
- package/dist/src/commands/sensors/project.js +60 -6
- package/dist/src/commands/sensors/result.js +18 -3
- package/dist/src/commands/sensors/run.js +124 -16
- package/dist/src/commands/sensors/status.js +163 -18
- package/dist/src/commands/watch/index.js +3 -0
- package/dist/src/core/environments/resolve.js +122 -0
- package/dist/src/core/environments/store.js +323 -0
- package/dist/src/core/facts/catalog.js +138 -0
- package/dist/src/core/facts/needs.js +57 -0
- package/dist/src/core/facts/types.js +21 -0
- package/dist/src/core/init/steps.js +5 -1
- package/dist/src/index.js +2 -0
- package/dist/tests/commands/env/index.test.js +261 -0
- package/dist/tests/commands/preflight/preflight.test.js +121 -0
- package/dist/tests/commands/registry/doctor.test.js +283 -0
- package/dist/tests/commands/registry/scaffold.test.js +186 -0
- package/dist/tests/commands/sensors/bootstrap.test.js +10 -0
- package/dist/tests/commands/sensors/compatibility/manifest.test.js +204 -0
- package/dist/tests/commands/sensors/init.test.js +107 -0
- package/dist/tests/commands/sensors/migrate.test.js +4 -3
- package/dist/tests/commands/sensors/partial-pack-bootstrap.test.js +1 -1
- package/dist/tests/commands/sensors/prepare.test.js +62 -0
- package/dist/tests/commands/sensors/result.test.js +66 -0
- package/dist/tests/commands/sensors/run.test.js +331 -0
- package/dist/tests/commands/sensors/status.test.js +204 -0
- package/dist/tests/commands/watch/reopened-196.test.js +9 -2
- package/dist/tests/core/environments/resolve.test.js +347 -0
- package/dist/tests/core/environments/store.test.js +248 -0
- package/dist/tests/core/facts/catalog.test.js +179 -0
- package/dist/tests/core/facts/needs.test.js +88 -0
- package/dist/tests/structural/native-release-artifacts.test.js +4 -2
- package/dist/tests/structural/operator-environments-docs-contract.test.js +70 -0
- package/dist/tests/structural/project-declared-sensors-docs-contract.test.js +104 -0
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
|
@@ -19,6 +19,26 @@ function validV2Manifest() {
|
|
|
19
19
|
},
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
|
+
function projectSensor(overrides = {}) {
|
|
23
|
+
return {
|
|
24
|
+
source: 'project',
|
|
25
|
+
enabled: true,
|
|
26
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
27
|
+
...overrides,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function v3WithPackAndProject(projectOverrides = {}) {
|
|
31
|
+
return {
|
|
32
|
+
schemaVersion: 3,
|
|
33
|
+
mode: 'project-sensors',
|
|
34
|
+
pack: 'js-ts',
|
|
35
|
+
source: { registry: 'baseline' },
|
|
36
|
+
sensors: {
|
|
37
|
+
lint: validV2Manifest().sensors.lint,
|
|
38
|
+
custom: projectSensor(projectOverrides),
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
22
42
|
describe('sensor manifest contract', () => {
|
|
23
43
|
it('keeps compatibility contracts on an acyclic import boundary', () => {
|
|
24
44
|
const source = (relative) => fs_1.default.readFileSync(path_1.default.join(__dirname, '../../../../src/commands/sensors', relative), 'utf8');
|
|
@@ -192,4 +212,188 @@ describe('sensor manifest contract', () => {
|
|
|
192
212
|
])('rejects malformed manifest %j', (input, message) => {
|
|
193
213
|
expect(() => (0, manifest_1.parseSensorManifest)(input, 'sensors.json')).toThrow(message);
|
|
194
214
|
});
|
|
215
|
+
describe('project-declared sensors (S1)', () => {
|
|
216
|
+
it('accepts a source:project entry alongside a pack-bound lint sensor', () => {
|
|
217
|
+
const manifest = v3WithPackAndProject();
|
|
218
|
+
const parsed = (0, manifest_1.parseSensorManifest)(manifest, 'sensors.json');
|
|
219
|
+
expect(parsed).toMatchObject({
|
|
220
|
+
kind: 'v3',
|
|
221
|
+
pack: {
|
|
222
|
+
mode: 'project-sensors',
|
|
223
|
+
pack: 'js-ts',
|
|
224
|
+
sensors: {
|
|
225
|
+
lint: { variantId: 'eslint-9', enabled: true },
|
|
226
|
+
custom: {
|
|
227
|
+
source: 'project',
|
|
228
|
+
enabled: true,
|
|
229
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
230
|
+
formatter: 'exit-code',
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
expect(parsed.kind === 'v3' && parsed.pack.mode === 'project-sensors' && 'variantId' in parsed.pack.sensors.custom).toBe(false);
|
|
236
|
+
expect(parsed.kind === 'v3' && parsed.pack.mode === 'project-sensors' && 'initializedCompatibility' in parsed.pack.sensors.custom).toBe(false);
|
|
237
|
+
});
|
|
238
|
+
it('accepts an only-project manifest with pack omitted or null', () => {
|
|
239
|
+
const base = {
|
|
240
|
+
schemaVersion: 3,
|
|
241
|
+
mode: 'project-sensors',
|
|
242
|
+
sensors: { custom: projectSensor() },
|
|
243
|
+
};
|
|
244
|
+
for (const manifest of [base, { ...base, pack: null }]) {
|
|
245
|
+
const parsed = (0, manifest_1.parseSensorManifest)(manifest, 'sensors.json');
|
|
246
|
+
expect(parsed).toMatchObject({
|
|
247
|
+
kind: 'v3',
|
|
248
|
+
pack: {
|
|
249
|
+
mode: 'project-sensors',
|
|
250
|
+
sensors: { custom: { source: 'project', formatter: 'exit-code' } },
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
if (parsed.kind === 'v3' && parsed.pack.mode === 'project-sensors') {
|
|
254
|
+
expect(parsed.pack.pack === undefined || parsed.pack.pack === null).toBe(true);
|
|
255
|
+
expect(parsed.pack.source).toBeUndefined();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
it('accepts an empty only-project sensors map', () => {
|
|
260
|
+
expect((0, manifest_1.parseSensorManifest)({
|
|
261
|
+
schemaVersion: 3,
|
|
262
|
+
mode: 'project-sensors',
|
|
263
|
+
sensors: {},
|
|
264
|
+
}, 'sensors.json')).toMatchObject({
|
|
265
|
+
kind: 'v3',
|
|
266
|
+
pack: { mode: 'project-sensors', sensors: {} },
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
it.each(['variantId', 'initializedCompatibility'])('rejects a project entry that declares forbidden pack field %s', field => {
|
|
270
|
+
const value = field === 'variantId'
|
|
271
|
+
? 'eslint-9'
|
|
272
|
+
: { state: 'certified', reason: 'ok', variantId: 'eslint-9', toolVersion: '9.0.0', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] };
|
|
273
|
+
const soft = (0, manifest_1.parseSensorManifestWithIssues)(v3WithPackAndProject({ [field]: value }), 'sensors.json');
|
|
274
|
+
expect(soft.invalidEntries).toEqual([
|
|
275
|
+
expect.objectContaining({
|
|
276
|
+
name: 'custom',
|
|
277
|
+
reason: expect.stringMatching(new RegExp(`sensors\\.custom\\.${field}`)),
|
|
278
|
+
}),
|
|
279
|
+
]);
|
|
280
|
+
expect(soft).toMatchObject({
|
|
281
|
+
kind: 'v3',
|
|
282
|
+
pack: { sensors: { lint: { variantId: 'eslint-9' } } },
|
|
283
|
+
});
|
|
284
|
+
expect('custom' in soft.pack.sensors).toBe(false);
|
|
285
|
+
});
|
|
286
|
+
it('soft-rejects an invalid project command with sensors.<name>.command in the reason (RF-1.12)', () => {
|
|
287
|
+
const soft = (0, manifest_1.parseSensorManifestWithIssues)(v3WithPackAndProject({
|
|
288
|
+
command: { executable: 'bash', resolution: 'path', args: ['-c', 'true'] },
|
|
289
|
+
}), 'sensors.json');
|
|
290
|
+
expect(soft.invalidEntries).toHaveLength(1);
|
|
291
|
+
expect(soft.invalidEntries[0]?.reason).toMatch(/sensors\.custom\.command/);
|
|
292
|
+
expect(soft).toMatchObject({
|
|
293
|
+
kind: 'v3',
|
|
294
|
+
pack: { sensors: { lint: { variantId: 'eslint-9' } } },
|
|
295
|
+
});
|
|
296
|
+
expect('custom' in soft.pack.sensors).toBe(false);
|
|
297
|
+
});
|
|
298
|
+
it('soft-rejects an unmarked custom command as missing source (RF-1.2)', () => {
|
|
299
|
+
const soft = (0, manifest_1.parseSensorManifestWithIssues)({
|
|
300
|
+
schemaVersion: 3,
|
|
301
|
+
mode: 'project-sensors',
|
|
302
|
+
pack: 'js-ts',
|
|
303
|
+
source: { registry: 'baseline' },
|
|
304
|
+
sensors: {
|
|
305
|
+
lint: validV2Manifest().sensors.lint,
|
|
306
|
+
custom: {
|
|
307
|
+
enabled: true,
|
|
308
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
}, 'sensors.json');
|
|
312
|
+
expect(soft.invalidEntries).toEqual([
|
|
313
|
+
expect.objectContaining({
|
|
314
|
+
name: 'custom',
|
|
315
|
+
reason: expect.stringMatching(/sensors\.custom\.source/),
|
|
316
|
+
}),
|
|
317
|
+
]);
|
|
318
|
+
expect(soft.invalidEntries[0]?.reason).toMatch(/source/);
|
|
319
|
+
expect(soft).toMatchObject({
|
|
320
|
+
kind: 'v3',
|
|
321
|
+
pack: { sensors: { lint: { variantId: 'eslint-9' } } },
|
|
322
|
+
});
|
|
323
|
+
// Soft-isolation: unmarked custom must be dropped, pack sibling kept.
|
|
324
|
+
expect('custom' in soft.pack.sensors).toBe(false);
|
|
325
|
+
});
|
|
326
|
+
it('rejects unknown formatter and defaults omitted formatter to exit-code', () => {
|
|
327
|
+
const soft = (0, manifest_1.parseSensorManifestWithIssues)(v3WithPackAndProject({ formatter: 'not-a-formatter' }), 'sensors.json');
|
|
328
|
+
expect(soft.invalidEntries).toEqual([
|
|
329
|
+
expect.objectContaining({
|
|
330
|
+
name: 'custom',
|
|
331
|
+
reason: expect.stringMatching(/sensors\.custom\.formatter/),
|
|
332
|
+
}),
|
|
333
|
+
]);
|
|
334
|
+
const ok = (0, manifest_1.parseSensorManifest)(v3WithPackAndProject(), 'sensors.json');
|
|
335
|
+
expect(ok).toMatchObject({
|
|
336
|
+
kind: 'v3',
|
|
337
|
+
pack: { sensors: { custom: { formatter: 'exit-code' } } },
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
it('treats omitted applicability as always applicable and rejects invalid shapes', () => {
|
|
341
|
+
const ok = (0, manifest_1.parseSensorManifest)(v3WithPackAndProject(), 'sensors.json');
|
|
342
|
+
expect(ok.kind === 'v3' && ok.pack.mode === 'project-sensors' && !('applicability' in ok.pack.sensors.custom)).toBe(true);
|
|
343
|
+
const withApplicability = (0, manifest_1.parseSensorManifest)(v3WithPackAndProject({ applicability: { allFiles: ['package.json'] } }), 'sensors.json');
|
|
344
|
+
expect(withApplicability).toMatchObject({
|
|
345
|
+
kind: 'v3',
|
|
346
|
+
pack: { sensors: { custom: { applicability: { allFiles: ['package.json'] } } } },
|
|
347
|
+
});
|
|
348
|
+
const soft = (0, manifest_1.parseSensorManifestWithIssues)(v3WithPackAndProject({ applicability: {} }), 'sensors.json');
|
|
349
|
+
expect(soft.invalidEntries).toEqual([
|
|
350
|
+
expect.objectContaining({
|
|
351
|
+
name: 'custom',
|
|
352
|
+
reason: expect.stringMatching(/sensors\.custom\.applicability/),
|
|
353
|
+
}),
|
|
354
|
+
]);
|
|
355
|
+
});
|
|
356
|
+
it('rejects schemaVersion 2 and legacy manifests that use source:project with migrate-to-v3', () => {
|
|
357
|
+
const v2 = {
|
|
358
|
+
schemaVersion: 2,
|
|
359
|
+
pack: 'js-ts',
|
|
360
|
+
sensors: {
|
|
361
|
+
custom: {
|
|
362
|
+
source: 'project',
|
|
363
|
+
enabled: true,
|
|
364
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
expect(() => (0, manifest_1.parseSensorManifest)(v2, 'sensors.json')).toThrow(/migrate-to-v3|migrate to v3/i);
|
|
369
|
+
const legacy = {
|
|
370
|
+
pack: 'js-ts',
|
|
371
|
+
sensors: {
|
|
372
|
+
custom: {
|
|
373
|
+
source: 'project',
|
|
374
|
+
enabled: true,
|
|
375
|
+
cmd: 'npm run lint',
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
};
|
|
379
|
+
expect(() => (0, manifest_1.parseSensorManifest)(legacy, 'sensors.json')).toThrow(/migrate-to-v3|migrate to v3/i);
|
|
380
|
+
});
|
|
381
|
+
it('soft-isolates one invalid project entry while keeping a valid pack sensor', () => {
|
|
382
|
+
const soft = (0, manifest_1.parseSensorManifestWithIssues)(v3WithPackAndProject({ formatter: 'not-a-formatter' }), 'sensors.json');
|
|
383
|
+
expect(soft.invalidEntries).toHaveLength(1);
|
|
384
|
+
expect(soft.invalidEntries[0]).toMatchObject({
|
|
385
|
+
name: 'custom',
|
|
386
|
+
reason: expect.stringMatching(/sensors\.custom\.formatter/),
|
|
387
|
+
});
|
|
388
|
+
expect(soft).toMatchObject({
|
|
389
|
+
kind: 'v3',
|
|
390
|
+
pack: {
|
|
391
|
+
mode: 'project-sensors',
|
|
392
|
+
sensors: { lint: { enabled: true, variantId: 'eslint-9' } },
|
|
393
|
+
},
|
|
394
|
+
});
|
|
395
|
+
expect('custom' in soft.pack.sensors).toBe(false);
|
|
396
|
+
expect(soft.invalidEntries.some(entry => /not valid JSON|valid JSON/i.test(entry.reason))).toBe(false);
|
|
397
|
+
});
|
|
398
|
+
});
|
|
195
399
|
});
|
|
@@ -95,3 +95,110 @@ describe('initSensors compatibility API', () => {
|
|
|
95
95
|
expect(bootstrap_1.applySensorBootstrap).not.toHaveBeenCalled();
|
|
96
96
|
});
|
|
97
97
|
});
|
|
98
|
+
describe('buildManifest project-declared preserve (S3)', () => {
|
|
99
|
+
let tmpDir;
|
|
100
|
+
let registryRoot;
|
|
101
|
+
beforeEach(() => {
|
|
102
|
+
tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-init-s3-'));
|
|
103
|
+
registryRoot = makeRegistry();
|
|
104
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
105
|
+
});
|
|
106
|
+
afterEach(() => {
|
|
107
|
+
fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
108
|
+
fs_1.default.rmSync(registryRoot, { recursive: true, force: true });
|
|
109
|
+
});
|
|
110
|
+
it('preserves an existing source:project entry byte-stable when regenerating pack sensors (RF-3.1)', () => {
|
|
111
|
+
const projectEntry = {
|
|
112
|
+
source: 'project',
|
|
113
|
+
enabled: true,
|
|
114
|
+
fast: true,
|
|
115
|
+
command: { executable: 'terraform', resolution: 'path', args: ['fmt', '-check'] },
|
|
116
|
+
formatter: 'exit-code',
|
|
117
|
+
description: 'keep-me',
|
|
118
|
+
};
|
|
119
|
+
// Collides with a pack default name: per-field merge would otherwise inject pack `cmd`.
|
|
120
|
+
const existing = {
|
|
121
|
+
pack: 'js-ts',
|
|
122
|
+
sensors: {
|
|
123
|
+
typecheck: projectEntry,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
const merged = (0, init_1.buildManifest)('js-ts', existing, registryRoot, tmpDir);
|
|
127
|
+
expect(merged.sensors.typecheck).toEqual(projectEntry);
|
|
128
|
+
expect(merged.sensors.typecheck).not.toHaveProperty('cmd');
|
|
129
|
+
});
|
|
130
|
+
it('writes an only-project manifest without forcing pack:"generic" (RF-3.2)', () => {
|
|
131
|
+
const projectEntry = {
|
|
132
|
+
source: 'project',
|
|
133
|
+
enabled: true,
|
|
134
|
+
command: { executable: 'terraform', resolution: 'path', args: ['fmt', '-check'] },
|
|
135
|
+
formatter: 'exit-code',
|
|
136
|
+
};
|
|
137
|
+
const onlyProject = (0, init_1.buildManifest)(null, {
|
|
138
|
+
sensors: { 'iac-format': projectEntry },
|
|
139
|
+
}, undefined, tmpDir);
|
|
140
|
+
expect(onlyProject).not.toMatchObject({ pack: 'generic' });
|
|
141
|
+
expect(Object.prototype.hasOwnProperty.call(onlyProject, 'pack')).toBe(false);
|
|
142
|
+
expect(onlyProject.sensors['iac-format']).toEqual(projectEntry);
|
|
143
|
+
expect(JSON.stringify(onlyProject)).not.toMatch(/"pack"\s*:/);
|
|
144
|
+
});
|
|
145
|
+
it('preserves source:project entries when materialize regenerates pack sensors (RF-3.1)', () => {
|
|
146
|
+
const { materializePortableSensors } = require('../../../src/commands/sensors/compatibility/materialize');
|
|
147
|
+
const home = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-init-mat-home-'));
|
|
148
|
+
const previousHome = process.env.AWM_HOME;
|
|
149
|
+
try {
|
|
150
|
+
process.env.AWM_HOME = home;
|
|
151
|
+
const packDir = path_1.default.join(home, 'registries', 'baseline', 'sensor-packs', 'js-ts');
|
|
152
|
+
fs_1.default.mkdirSync(packDir, { recursive: true });
|
|
153
|
+
fs_1.default.writeFileSync(path_1.default.join(packDir, 'eslint.config.awm.mjs'), 'export default []\n');
|
|
154
|
+
fs_1.default.writeFileSync(path_1.default.join(packDir, 'pack.json'), JSON.stringify({
|
|
155
|
+
schemaVersion: 2, name: 'js-ts', description: 'fixture', detects: ['package.json'],
|
|
156
|
+
coverage: { schemaVersion: 1, classes: {} },
|
|
157
|
+
sensors: { lint: { variants: [] } },
|
|
158
|
+
}));
|
|
159
|
+
const projectEntry = {
|
|
160
|
+
source: 'project',
|
|
161
|
+
enabled: true,
|
|
162
|
+
command: { executable: 'terraform', resolution: 'path', args: ['fmt', '-check'] },
|
|
163
|
+
formatter: 'exit-code',
|
|
164
|
+
description: 'preserve-me',
|
|
165
|
+
};
|
|
166
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
|
|
167
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({
|
|
168
|
+
schemaVersion: 3, mode: 'project-sensors', pack: 'js-ts', source: { registry: 'baseline' },
|
|
169
|
+
sensors: { 'iac-format': projectEntry },
|
|
170
|
+
}));
|
|
171
|
+
const evidence = {
|
|
172
|
+
state: 'certified', reason: 'range-and-probe', variantId: 'eslint-10',
|
|
173
|
+
toolVersion: '10.0.0', runtimeVersion: process.versions.node, certifiedRange: '>=10 <11', evidence: [],
|
|
174
|
+
};
|
|
175
|
+
const result = materializePortableSensors({
|
|
176
|
+
projectRoot: tmpDir,
|
|
177
|
+
pack: 'js-ts',
|
|
178
|
+
source: {
|
|
179
|
+
path: path_1.default.join(packDir, 'pack.json'),
|
|
180
|
+
content: fs_1.default.readFileSync(path_1.default.join(packDir, 'pack.json'), 'utf8'),
|
|
181
|
+
registry: { name: 'baseline', remote: 'https://example.test/baseline.git', contentRoot: path_1.default.join(home, 'registries', 'baseline') },
|
|
182
|
+
},
|
|
183
|
+
configure: false,
|
|
184
|
+
sensors: {
|
|
185
|
+
lint: {
|
|
186
|
+
enabled: true, variantId: 'eslint-10',
|
|
187
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
188
|
+
assets: ['eslint.config.awm.mjs'],
|
|
189
|
+
initializedCompatibility: evidence,
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
expect(result.manifest.sensors['iac-format']).toEqual(projectEntry);
|
|
194
|
+
expect(JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), 'utf8')).sensors['iac-format']).toEqual(projectEntry);
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
if (previousHome === undefined)
|
|
198
|
+
delete process.env.AWM_HOME;
|
|
199
|
+
else
|
|
200
|
+
process.env.AWM_HOME = previousHome;
|
|
201
|
+
fs_1.default.rmSync(home, { recursive: true, force: true });
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
});
|
|
@@ -154,11 +154,12 @@ describe('planV2Migration', () => {
|
|
|
154
154
|
try {
|
|
155
155
|
fs_1.default.writeFileSync(manifestPath, original);
|
|
156
156
|
const candidate = (0, migrate_1.planV2Migration)({ manifest: v2, source: resolvedSource() }).candidate;
|
|
157
|
-
const
|
|
157
|
+
const lint = candidate.sensors.lint;
|
|
158
|
+
const withoutPolicy = { ...lint };
|
|
158
159
|
delete withoutPolicy.policyRef;
|
|
159
160
|
for (const changed of [
|
|
160
|
-
{ ...
|
|
161
|
-
{ ...
|
|
161
|
+
{ ...lint, fast: false },
|
|
162
|
+
{ ...lint, variantId: 'eslint-8', initializedCompatibility: { ...lint.initializedCompatibility, variantId: 'eslint-8', toolVersion: '8.0.0', certifiedRange: '>=8 <9' } },
|
|
162
163
|
withoutPolicy,
|
|
163
164
|
]) {
|
|
164
165
|
expect(() => (0, migrate_1.replaceV2ManifestWithV3)(manifestPath, { ...candidate, sensors: { lint: changed } }, resolvedSource())).toThrow('semantic mismatch');
|
|
@@ -52,7 +52,7 @@ describe('project-sensors bootstrap with a partially resolvable pack', () => {
|
|
|
52
52
|
const manifest = created.manifest;
|
|
53
53
|
// The manifest contents are the assertion, not the exit code.
|
|
54
54
|
expect(Object.keys(manifest.sensors)).toEqual(['lint']);
|
|
55
|
-
expect(manifest.sensors.lint
|
|
55
|
+
expect(manifest.sensors.lint).toMatchObject({ variantId: 'eslint-10' });
|
|
56
56
|
expect(created.unresolved).toEqual([{ sensor: 'security', state: 'missing-tool', reason: 'tool-not-found' }]);
|
|
57
57
|
// Nothing is invented for the skipped sensor: no entry, and none of its assets.
|
|
58
58
|
expect(created.changes.map(change => change.path)).toEqual(['.awm/sensors.json']);
|
|
@@ -86,6 +86,68 @@ describe('validateRunOptions', () => {
|
|
|
86
86
|
.toThrow(/refusing to combine --changed with a baseline capture/);
|
|
87
87
|
});
|
|
88
88
|
});
|
|
89
|
+
describe('prepareV2Sensor: project-declared entries (S2)', () => {
|
|
90
|
+
const projectSensor = (overrides = {}) => ({
|
|
91
|
+
source: 'project',
|
|
92
|
+
enabled: true,
|
|
93
|
+
command: { executable: 'node', resolution: 'path', args: ['scripts/iac-format.js'] },
|
|
94
|
+
formatter: 'exit-code',
|
|
95
|
+
timeout: 45_000,
|
|
96
|
+
...overrides,
|
|
97
|
+
});
|
|
98
|
+
test('uses the manifest command, default exit-code formatter, project timeout, and project-declared provenance', () => {
|
|
99
|
+
const prepared = (0, prepare_1.prepareV2Sensor)({
|
|
100
|
+
name: 'iac-format',
|
|
101
|
+
sensor: projectSensor(),
|
|
102
|
+
requestedScope: 'full',
|
|
103
|
+
});
|
|
104
|
+
expect(prepared.command).toEqual({
|
|
105
|
+
kind: 'structured',
|
|
106
|
+
value: { executable: 'node', resolution: 'path', args: ['scripts/iac-format.js'] },
|
|
107
|
+
});
|
|
108
|
+
expect(prepared.formatter).toBe('exit-code');
|
|
109
|
+
expect(prepared.timeoutMs).toBe(45_000);
|
|
110
|
+
expect(prepared.timeoutSource).toBe('project');
|
|
111
|
+
expect(prepared.certification).toBe('project-declared');
|
|
112
|
+
expect(prepared.certification).not.toBe('certified');
|
|
113
|
+
expect(prepared.syntheticStatus).toBeUndefined();
|
|
114
|
+
});
|
|
115
|
+
test('skips live variant re-resolution for project entries', () => {
|
|
116
|
+
const prepared = (0, prepare_1.prepareV2Sensor)({
|
|
117
|
+
name: 'iac-format',
|
|
118
|
+
sensor: projectSensor(),
|
|
119
|
+
liveState: {
|
|
120
|
+
state: 'missing-tool',
|
|
121
|
+
reason: 'tool-not-found',
|
|
122
|
+
variantId: null,
|
|
123
|
+
toolVersion: null,
|
|
124
|
+
runtimeVersion: null,
|
|
125
|
+
certifiedRange: null,
|
|
126
|
+
evidence: [],
|
|
127
|
+
},
|
|
128
|
+
requestedScope: 'full',
|
|
129
|
+
});
|
|
130
|
+
expect(prepared.command).toEqual({
|
|
131
|
+
kind: 'structured',
|
|
132
|
+
value: { executable: 'node', resolution: 'path', args: ['scripts/iac-format.js'] },
|
|
133
|
+
});
|
|
134
|
+
expect(prepared.certification).toBe('project-declared');
|
|
135
|
+
});
|
|
136
|
+
test('returns synthetic not-applicable when project applicabilityPaths are not met', () => {
|
|
137
|
+
const prepared = (0, prepare_1.prepareV2Sensor)({
|
|
138
|
+
name: 'iac-format',
|
|
139
|
+
sensor: projectSensor({
|
|
140
|
+
applicability: { allFiles: ['terraform.tf'] },
|
|
141
|
+
}),
|
|
142
|
+
applicabilityPaths: ['README.md'],
|
|
143
|
+
requestedScope: 'full',
|
|
144
|
+
});
|
|
145
|
+
expect(prepared.command).toBeUndefined();
|
|
146
|
+
expect(prepared.syntheticStatus).toBe('inconclusive');
|
|
147
|
+
expect(prepared.syntheticReason).toBe('not-applicable: applicability-not-met');
|
|
148
|
+
expect(prepared.certification).toBe('project-declared');
|
|
149
|
+
});
|
|
150
|
+
});
|
|
89
151
|
// El bloqueo que esto arregla: `compatible-unverified` significa que la herramienta esta,
|
|
90
152
|
// cae en el rango operativo declarado por el pack y su probe coincidio — solo la version
|
|
91
153
|
// exacta no es la que el registry congelo. Saltearla dejaba el veredicto en
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const result_1 = require("../../../src/commands/sensors/result");
|
|
4
|
+
const exec_fixtures_1 = require("./exec-fixtures");
|
|
5
|
+
function prepared(overrides = {}) {
|
|
6
|
+
return {
|
|
7
|
+
name: 'iac-format',
|
|
8
|
+
formatter: 'exit-code',
|
|
9
|
+
timeoutMs: 30_000,
|
|
10
|
+
timeoutSource: 'project',
|
|
11
|
+
requestedScope: 'full',
|
|
12
|
+
effectiveScope: 'full',
|
|
13
|
+
certification: 'project-declared',
|
|
14
|
+
command: { kind: 'structured', value: { executable: 'node', resolution: 'path', args: ['check.js'] } },
|
|
15
|
+
...overrides,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
describe('interpretResult exit-code formatter (RF-2.4)', () => {
|
|
19
|
+
it('maps exit 0 to pass with no per-file findings', () => {
|
|
20
|
+
const result = (0, result_1.interpretResult)(prepared(), (0, exec_fixtures_1.ok)('all good\n'));
|
|
21
|
+
expect(result).toMatchObject({
|
|
22
|
+
name: 'iac-format',
|
|
23
|
+
status: 'pass',
|
|
24
|
+
errors: [],
|
|
25
|
+
certification: 'project-declared',
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
it('maps non-zero exit to fail with truncated combined stdout/stderr evidence and no invented per-file findings', () => {
|
|
29
|
+
const long = `${'x'.repeat(300)}\n`;
|
|
30
|
+
const result = (0, result_1.interpretResult)(prepared(), (0, exec_fixtures_1.exited)(1, long, 'stderr-tail\n'));
|
|
31
|
+
expect(result.status).toBe('fail');
|
|
32
|
+
expect(result.errors).toHaveLength(1);
|
|
33
|
+
expect(result.errors[0].file).toBeUndefined();
|
|
34
|
+
expect(result.errors[0].line).toBeUndefined();
|
|
35
|
+
expect(result.errors[0].rule).toBeUndefined();
|
|
36
|
+
// Combined = stdout+stderr; truncateEvidence caps at 200 — must be truncated
|
|
37
|
+
// stdout-first content, not a loose "exit 1" / stderr-only fallback.
|
|
38
|
+
expect(result.errors[0].message.length).toBeLessThanOrEqual(200);
|
|
39
|
+
expect(result.errors[0].message).toMatch(/^x{10,}/);
|
|
40
|
+
expect(result.errors[0].message).not.toMatch(/exit 1/);
|
|
41
|
+
expect(result.certification).toBe('project-declared');
|
|
42
|
+
});
|
|
43
|
+
it('maps non-zero exit with empty stdout/stderr to fail evidence that includes exit N', () => {
|
|
44
|
+
const result = (0, result_1.interpretResult)(prepared(), (0, exec_fixtures_1.exited)(7, '', ''));
|
|
45
|
+
expect(result.status).toBe('fail');
|
|
46
|
+
expect(result.errors).toHaveLength(1);
|
|
47
|
+
expect(result.errors[0].file).toBeUndefined();
|
|
48
|
+
expect(result.errors[0].message).toMatch(/exit 7/);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('executePrepared synthetic provenance (RF-2.2)', () => {
|
|
52
|
+
it('includes certification on synthetic/disabled results the same way interpretResult does', async () => {
|
|
53
|
+
const result = await (0, result_1.executePrepared)(prepared({
|
|
54
|
+
command: undefined,
|
|
55
|
+
syntheticStatus: 'skipped',
|
|
56
|
+
syntheticReason: 'disabled',
|
|
57
|
+
certification: 'project-declared',
|
|
58
|
+
}));
|
|
59
|
+
expect(result).toMatchObject({
|
|
60
|
+
name: 'iac-format',
|
|
61
|
+
status: 'skipped',
|
|
62
|
+
skipReason: 'disabled',
|
|
63
|
+
certification: 'project-declared',
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|