agentic-workflow-manager 9.4.1 → 9.6.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/preflight/checks.js +58 -27
- package/dist/src/commands/sensors/bootstrap.js +171 -0
- package/dist/src/commands/sensors/compatibility/contract.js +18 -15
- package/dist/src/commands/sensors/compatibility/manifest.js +75 -2
- package/dist/src/commands/sensors/compatibility/materialize.js +256 -54
- package/dist/src/commands/sensors/compatibility/pack-source.js +61 -17
- package/dist/src/commands/sensors/compatibility/safe-file.js +286 -0
- package/dist/src/commands/sensors/compatibility/source.js +75 -0
- package/dist/src/commands/sensors/coverage/index.js +2 -2
- package/dist/src/commands/sensors/coverage/resolve.js +35 -29
- package/dist/src/commands/sensors/detection.js +31 -0
- package/dist/src/commands/sensors/exec.js +116 -11
- package/dist/src/commands/sensors/index.js +51 -10
- package/dist/src/commands/sensors/init.js +17 -168
- package/dist/src/commands/sensors/migrate.js +209 -0
- package/dist/src/commands/sensors/project.js +107 -0
- package/dist/src/commands/sensors/run.js +66 -47
- package/dist/src/commands/sensors/status.js +97 -56
- package/dist/src/core/init/steps.js +24 -11
- package/dist/src/core/secure-fs/native-bridge.js +209 -0
- package/dist/tests/commands/preflight/preflight.test.js +31 -0
- package/dist/tests/commands/sensors/bootstrap-observational.test.js +24 -0
- package/dist/tests/commands/sensors/bootstrap.test.js +175 -0
- package/dist/tests/commands/sensors/compatibility/contract.test.js +38 -0
- package/dist/tests/commands/sensors/compatibility/manifest.test.js +90 -0
- package/dist/tests/commands/sensors/compatibility/materialize.test.js +410 -0
- package/dist/tests/commands/sensors/compatibility/pack-source.test.js +249 -0
- package/dist/tests/commands/sensors/compatibility/safe-file.test.js +52 -0
- package/dist/tests/commands/sensors/compatibility/source.test.js +121 -0
- package/dist/tests/commands/sensors/coverage/index.test.js +20 -0
- package/dist/tests/commands/sensors/coverage/resolve.test.js +36 -34
- package/dist/tests/commands/sensors/exec.test.js +86 -0
- package/dist/tests/commands/sensors/index.test.js +54 -0
- package/dist/tests/commands/sensors/init-pack-unavailable.test.js +21 -84
- package/dist/tests/commands/sensors/init.test.js +56 -490
- package/dist/tests/commands/sensors/migrate.test.js +513 -0
- package/dist/tests/commands/sensors/project.test.js +102 -0
- package/dist/tests/commands/sensors/run.test.js +107 -4
- package/dist/tests/commands/sensors/status.test.js +106 -2
- package/dist/tests/core/init/orchestrator.test.js +3 -1
- package/dist/tests/core/secure-fs/native-bridge.test.js +733 -0
- package/dist/tests/integration/preflight-json-pipe.e2e.test.js +5 -0
- package/dist/tests/integration/sensor-bootstrap.e2e.test.js +165 -0
- package/dist/tests/integration/sensor-compatibility.e2e.test.js +14 -23
- package/dist/tests/integration/sensor-coverage.e2e.test.js +15 -24
- package/dist/tests/integration/sensor-structured-run.e2e.test.js +3 -1
- package/dist/tests/structural/native-release-artifacts.test.js +22 -0
- package/dist/tests/structural/release-flags-are-reachable-from-ci.test.js +10 -0
- package/dist/tests/structural/release-registry-provenance.test.js +4 -0
- package/dist/tests/structural/sensor-portability-contract.test.js +44 -0
- package/package.json +6 -2
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/linux-arm64/secure_fs.node +0 -0
- package/prebuilds/linux-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
|
@@ -7,20 +7,37 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
7
7
|
const os_1 = __importDefault(require("os"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const materialize_1 = require("../../../../src/commands/sensors/compatibility/materialize");
|
|
10
|
+
const pack_source_1 = require("../../../../src/commands/sensors/compatibility/pack-source");
|
|
11
|
+
const safe_file_1 = require("../../../../src/commands/sensors/compatibility/safe-file");
|
|
12
|
+
const native_bridge_1 = require("../../../../src/core/secure-fs/native-bridge");
|
|
10
13
|
const evidence = { state: 'certified', reason: 'range-and-probe', variantId: 'eslint-10', toolVersion: '10.0.0', runtimeVersion: '22.0.0', certifiedRange: '>=10 <11', evidence: [] };
|
|
11
14
|
describe('materializeResolvedSensors', () => {
|
|
12
15
|
let projectRoot;
|
|
13
16
|
let packRoot;
|
|
17
|
+
let portableRegistryRoots;
|
|
14
18
|
beforeEach(() => {
|
|
15
19
|
projectRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-project-'));
|
|
16
20
|
packRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-pack-'));
|
|
21
|
+
portableRegistryRoots = [];
|
|
17
22
|
fs_1.default.writeFileSync(path_1.default.join(packRoot, 'eslint.config.awm.mjs'), 'export default [];');
|
|
18
23
|
fs_1.default.writeFileSync(path_1.default.join(packRoot, 'tsconfig.awm.json'), '{}');
|
|
19
24
|
});
|
|
20
25
|
afterEach(() => {
|
|
26
|
+
(0, safe_file_1.setSecureFsForTests)(undefined);
|
|
21
27
|
fs_1.default.rmSync(projectRoot, { recursive: true, force: true });
|
|
22
28
|
fs_1.default.rmSync(packRoot, { recursive: true, force: true });
|
|
29
|
+
for (const registryRoot of portableRegistryRoots)
|
|
30
|
+
fs_1.default.rmSync(registryRoot, { recursive: true, force: true });
|
|
23
31
|
});
|
|
32
|
+
function portableSource() {
|
|
33
|
+
const registryRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-registry-'));
|
|
34
|
+
portableRegistryRoots.push(registryRoot);
|
|
35
|
+
const selectedPackRoot = path_1.default.join(registryRoot, 'sensor-packs', 'js-ts');
|
|
36
|
+
fs_1.default.mkdirSync(selectedPackRoot, { recursive: true });
|
|
37
|
+
fs_1.default.writeFileSync(path_1.default.join(selectedPackRoot, 'pack.json'), '{}');
|
|
38
|
+
fs_1.default.writeFileSync(path_1.default.join(selectedPackRoot, 'eslint.config.awm.mjs'), 'export default [];');
|
|
39
|
+
return (0, pack_source_1.resolvePackSource)('js-ts', { registries: [{ name: 'baseline', remote: '', contentRoot: registryRoot }] });
|
|
40
|
+
}
|
|
24
41
|
it('copies only selected assets and writes a v2 manifest atomically', () => {
|
|
25
42
|
const result = (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
|
|
26
43
|
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
@@ -30,6 +47,186 @@ describe('materializeResolvedSensors', () => {
|
|
|
30
47
|
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
48
|
expect(fs_1.default.readdirSync(path_1.default.join(projectRoot, '.awm'))).not.toContain('sensors.json.tmp');
|
|
32
49
|
});
|
|
50
|
+
it('invokes the secure-fs bridge while applying selected registry content', () => {
|
|
51
|
+
let leaseHeld = false;
|
|
52
|
+
const bridge = {
|
|
53
|
+
readRegularFile: jest.fn((...args) => native_bridge_1.secureFs.readRegularFile(...args)),
|
|
54
|
+
withProjectLease: jest.fn((_projectRoot, operation) => {
|
|
55
|
+
expect(leaseHeld).toBe(false);
|
|
56
|
+
leaseHeld = true;
|
|
57
|
+
try {
|
|
58
|
+
return operation();
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
leaseHeld = false;
|
|
62
|
+
}
|
|
63
|
+
}),
|
|
64
|
+
writeProjectTransaction: jest.fn((...args) => {
|
|
65
|
+
expect(leaseHeld).toBe(true);
|
|
66
|
+
return native_bridge_1.secureFs.writeProjectTransaction(...args);
|
|
67
|
+
}),
|
|
68
|
+
};
|
|
69
|
+
(0, safe_file_1.setSecureFsForTests)(bridge);
|
|
70
|
+
(0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
|
|
71
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
72
|
+
} });
|
|
73
|
+
expect(bridge.readRegularFile).toHaveBeenCalledWith(path_1.default.join(packRoot, 'eslint.config.awm.mjs'), 1024 * 1024);
|
|
74
|
+
expect(bridge.withProjectLease).toHaveBeenCalledTimes(1);
|
|
75
|
+
expect(bridge.withProjectLease).toHaveBeenCalledWith(projectRoot, expect.any(Function));
|
|
76
|
+
expect(bridge.writeProjectTransaction).toHaveBeenCalledWith(projectRoot, 'eslint.config.awm.mjs', expect.any(Buffer), { mode: 'create', createParents: true });
|
|
77
|
+
(0, safe_file_1.setSecureFsForTests)(undefined);
|
|
78
|
+
expect(bridge.writeProjectTransaction).toHaveBeenCalledWith(projectRoot, '.awm/sensors.json', expect.any(Buffer), { mode: 'create', createParents: true });
|
|
79
|
+
});
|
|
80
|
+
it('carries the exact native read identity through the existing manifest replacement path', () => {
|
|
81
|
+
const manifestPath = path_1.default.join(projectRoot, '.awm', 'sensors.json');
|
|
82
|
+
fs_1.default.mkdirSync(path_1.default.dirname(manifestPath));
|
|
83
|
+
fs_1.default.writeFileSync(manifestPath, JSON.stringify({ schemaVersion: 2, pack: 'js-ts', sensors: {
|
|
84
|
+
lint: { enabled: true, variantId: 'eslint-9', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, initializedCompatibility: { ...evidence, variantId: 'eslint-9' } },
|
|
85
|
+
} }));
|
|
86
|
+
const bridge = {
|
|
87
|
+
readRegularFile: jest.fn((...args) => native_bridge_1.secureFs.readRegularFile(...args)),
|
|
88
|
+
withProjectLease: jest.fn((_projectRoot, operation) => operation()),
|
|
89
|
+
writeProjectTransaction: jest.fn((...args) => native_bridge_1.secureFs.writeProjectTransaction(...args)),
|
|
90
|
+
};
|
|
91
|
+
(0, safe_file_1.setSecureFsForTests)(bridge);
|
|
92
|
+
(0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', configure: false, sensors: {
|
|
93
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, initializedCompatibility: evidence },
|
|
94
|
+
} });
|
|
95
|
+
let readCall = -1;
|
|
96
|
+
bridge.readRegularFile.mock.calls.forEach(([file], index) => {
|
|
97
|
+
if (file === manifestPath)
|
|
98
|
+
readCall = index;
|
|
99
|
+
});
|
|
100
|
+
const replaceCall = bridge.writeProjectTransaction.mock.calls.find(([, destination, , options]) => destination === '.awm/sensors.json' && options.mode === 'replace');
|
|
101
|
+
expect(readCall).toBeGreaterThanOrEqual(0);
|
|
102
|
+
expect(replaceCall).toBeDefined();
|
|
103
|
+
expect(replaceCall[3]).toMatchObject({ mode: 'replace', expected: expect.any(Buffer), expectedIdentity: expect.any(Object), createParents: false });
|
|
104
|
+
expect(replaceCall[3].expectedIdentity).toBe(bridge.readRegularFile.mock.results[readCall].value.identity);
|
|
105
|
+
});
|
|
106
|
+
it('writes a validated portable v3 manifest using the exact logical registry', () => {
|
|
107
|
+
const source = portableSource();
|
|
108
|
+
const result = (0, materialize_1.materializePortableSensors)({ projectRoot, pack: 'js-ts', source, sensors: {
|
|
109
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
110
|
+
} });
|
|
111
|
+
expect(result.manifest).toMatchObject({ schemaVersion: 3, mode: 'project-sensors', source: { registry: 'baseline' } });
|
|
112
|
+
const text = fs_1.default.readFileSync(path_1.default.join(projectRoot, '.awm', 'sensors.json'), 'utf8');
|
|
113
|
+
expect(text.endsWith('\n')).toBe(true);
|
|
114
|
+
expect(text).not.toContain(path_1.default.dirname(source.path));
|
|
115
|
+
expect(JSON.parse(text)).toMatchObject({ source: { registry: 'baseline' } });
|
|
116
|
+
});
|
|
117
|
+
it('returns a bounded lease conflict before writing an asset or manifest, then succeeds after release', () => {
|
|
118
|
+
const leasedSecureFs = native_bridge_1.secureFs;
|
|
119
|
+
const input = { projectRoot, packRoot, pack: 'js-ts', sensors: {
|
|
120
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
121
|
+
} };
|
|
122
|
+
leasedSecureFs.withProjectLease(projectRoot, () => {
|
|
123
|
+
expect(() => (0, materialize_1.materializeResolvedSensors)(input)).toThrow(/project lease conflict/i);
|
|
124
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'eslint.config.awm.mjs'))).toBe(false);
|
|
125
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, '.awm', 'sensors.json'))).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
expect((0, materialize_1.materializeResolvedSensors)(input).configured).toEqual(['eslint.config.awm.mjs']);
|
|
128
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, '.awm', 'sensors.json'))).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
it('materializes a canonical pack source from a configured root with a symlinked ancestor', () => {
|
|
131
|
+
const registryRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-registry-'));
|
|
132
|
+
const linkedRegistryRoot = path_1.default.join(path_1.default.dirname(registryRoot), `${path_1.default.basename(registryRoot)}-linked`);
|
|
133
|
+
const contentRoot = path_1.default.join(registryRoot, 'content');
|
|
134
|
+
const linkedContentRoot = path_1.default.join(linkedRegistryRoot, 'content');
|
|
135
|
+
portableRegistryRoots.push(registryRoot);
|
|
136
|
+
try {
|
|
137
|
+
const selectedPackRoot = path_1.default.join(contentRoot, 'sensor-packs', 'js-ts');
|
|
138
|
+
fs_1.default.mkdirSync(selectedPackRoot, { recursive: true });
|
|
139
|
+
fs_1.default.writeFileSync(path_1.default.join(selectedPackRoot, 'pack.json'), '{}');
|
|
140
|
+
fs_1.default.writeFileSync(path_1.default.join(selectedPackRoot, 'eslint.config.awm.mjs'), 'export default [];');
|
|
141
|
+
fs_1.default.symlinkSync(registryRoot, linkedRegistryRoot);
|
|
142
|
+
const source = (0, pack_source_1.resolvePackSource)('js-ts', { registries: [{ name: 'baseline', remote: '', contentRoot: linkedContentRoot }] });
|
|
143
|
+
const result = (0, materialize_1.materializePortableSensors)({ projectRoot, pack: 'js-ts', source, sensors: {
|
|
144
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
145
|
+
} });
|
|
146
|
+
expect(result.configured).toEqual(['eslint.config.awm.mjs']);
|
|
147
|
+
expect(fs_1.default.readFileSync(path_1.default.join(projectRoot, 'eslint.config.awm.mjs'), 'utf8')).toBe('export default [];');
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
fs_1.default.rmSync(linkedRegistryRoot, { force: true });
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
it('rejects a portable source whose selected pack path belongs to another registry', () => {
|
|
154
|
+
const source = portableSource();
|
|
155
|
+
const otherRegistry = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-other-registry-'));
|
|
156
|
+
try {
|
|
157
|
+
expect(() => (0, materialize_1.materializePortableSensors)({ projectRoot, pack: 'js-ts', source: { ...source, registry: { ...source.registry, contentRoot: otherRegistry } }, sensors: {
|
|
158
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
159
|
+
} })).toThrow(/source|registry|pack/i);
|
|
160
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'eslint.config.awm.mjs'))).toBe(false);
|
|
161
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, '.awm', 'sensors.json'))).toBe(false);
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
fs_1.default.rmSync(otherRegistry, { recursive: true, force: true });
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
it('rejects a pack whose authoritative bytes changed after planning', () => {
|
|
168
|
+
const source = portableSource();
|
|
169
|
+
fs_1.default.writeFileSync(source.path, '{"changed":true}');
|
|
170
|
+
expect(() => (0, materialize_1.materializePortableSensors)({ projectRoot, pack: 'js-ts', source, sensors: {
|
|
171
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
172
|
+
} })).toThrow('changed after bootstrap planning');
|
|
173
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'eslint.config.awm.mjs'))).toBe(false);
|
|
174
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, '.awm', 'sensors.json'))).toBe(false);
|
|
175
|
+
});
|
|
176
|
+
it('fails lease acquisition before creating an asset when .awm cannot hold the lease', () => {
|
|
177
|
+
const source = portableSource();
|
|
178
|
+
fs_1.default.writeFileSync(path_1.default.join(projectRoot, '.awm'), 'not a directory');
|
|
179
|
+
const unlink = jest.spyOn(fs_1.default, 'unlinkSync');
|
|
180
|
+
try {
|
|
181
|
+
expect(() => (0, materialize_1.materializePortableSensors)({ projectRoot, pack: 'js-ts', source, sensors: {
|
|
182
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
183
|
+
} })).toThrow();
|
|
184
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'eslint.config.awm.mjs'))).toBe(false);
|
|
185
|
+
expect(fs_1.default.readFileSync(path_1.default.join(projectRoot, '.awm'), 'utf8')).toBe('not a directory');
|
|
186
|
+
expect(unlink).not.toHaveBeenCalled();
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
unlink.mockRestore();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
it('validates portable v3 input before writing assets', () => {
|
|
193
|
+
const source = portableSource();
|
|
194
|
+
expect(() => (0, materialize_1.materializePortableSensors)({ projectRoot, pack: 'js-ts', source: { ...source, registry: { ...source.registry, name: 'Baseline' } }, sensors: {
|
|
195
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
196
|
+
} })).toThrow('registry');
|
|
197
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'eslint.config.awm.mjs'))).toBe(false);
|
|
198
|
+
});
|
|
199
|
+
it('validates every selected portable asset before publishing the first one', () => {
|
|
200
|
+
const source = portableSource();
|
|
201
|
+
expect(() => (0, materialize_1.materializePortableSensors)({ projectRoot, pack: 'js-ts', source, sensors: {
|
|
202
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs', 'missing.awm.mjs'], initializedCompatibility: evidence },
|
|
203
|
+
} })).toThrow(/missing/i);
|
|
204
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'eslint.config.awm.mjs'))).toBe(false);
|
|
205
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, '.awm', 'sensors.json'))).toBe(false);
|
|
206
|
+
});
|
|
207
|
+
it('compensates only assets it created when manifest publication fails', () => {
|
|
208
|
+
const source = portableSource();
|
|
209
|
+
(0, safe_file_1.setSecureFsForTests)({
|
|
210
|
+
withProjectLease: native_bridge_1.secureFs.withProjectLease,
|
|
211
|
+
readRegularFile: native_bridge_1.secureFs.readRegularFile,
|
|
212
|
+
writeProjectTransaction: ((root, destination, content, options) => {
|
|
213
|
+
if (destination === '.awm/sensors.json')
|
|
214
|
+
throw new Error('injected manifest publication failure');
|
|
215
|
+
native_bridge_1.secureFs.writeProjectTransaction(root, destination, content, options);
|
|
216
|
+
}),
|
|
217
|
+
removeObservedProjectFile: native_bridge_1.secureFs.removeObservedProjectFile,
|
|
218
|
+
});
|
|
219
|
+
expect(() => (0, materialize_1.materializePortableSensors)({ projectRoot, pack: 'js-ts', source, sensors: {
|
|
220
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
221
|
+
} })).toThrow('injected manifest publication failure');
|
|
222
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'eslint.config.awm.mjs'))).toBe(false);
|
|
223
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, '.awm', 'sensors.json'))).toBe(false);
|
|
224
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, '.awm', 'sensors.json.tmp'))).toBe(false);
|
|
225
|
+
});
|
|
226
|
+
it('contains no pathname-based deletion in production materialization', () => {
|
|
227
|
+
const source = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../../../../src/commands/sensors/compatibility/materialize.ts'), 'utf8');
|
|
228
|
+
expect(source).not.toMatch(/\b(?:unlink|rm)Sync\s*\(/);
|
|
229
|
+
});
|
|
33
230
|
it('preserves a destination that already exists', () => {
|
|
34
231
|
fs_1.default.writeFileSync(path_1.default.join(projectRoot, 'eslint.config.awm.mjs'), 'owner content');
|
|
35
232
|
const result = (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
|
|
@@ -38,6 +235,202 @@ describe('materializeResolvedSensors', () => {
|
|
|
38
235
|
expect(result.preserved).toEqual(['eslint.config.awm.mjs']);
|
|
39
236
|
expect(fs_1.default.readFileSync(path_1.default.join(projectRoot, 'eslint.config.awm.mjs'), 'utf8')).toBe('owner content');
|
|
40
237
|
});
|
|
238
|
+
it('throws a non-conflict asset I/O failure and never publishes a manifest reference', () => {
|
|
239
|
+
const writeProjectTransaction = jest.fn((_root, destination) => {
|
|
240
|
+
if (destination === 'eslint.config.awm.mjs') {
|
|
241
|
+
throw new Error('secure-fs target exists or transaction failed');
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
(0, safe_file_1.setSecureFsForTests)({
|
|
245
|
+
withProjectLease: ((_root, operation) => operation()),
|
|
246
|
+
readRegularFile: jest.fn((...args) => native_bridge_1.secureFs.readRegularFile(...args)),
|
|
247
|
+
writeProjectTransaction: writeProjectTransaction,
|
|
248
|
+
});
|
|
249
|
+
expect(() => (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
|
|
250
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
251
|
+
} })).toThrow('secure-fs target exists or transaction failed');
|
|
252
|
+
expect(writeProjectTransaction).toHaveBeenCalledTimes(1);
|
|
253
|
+
expect(writeProjectTransaction).toHaveBeenCalledWith(projectRoot, 'eslint.config.awm.mjs', expect.any(Buffer), { mode: 'create', createParents: true });
|
|
254
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, '.awm', 'sensors.json'))).toBe(false);
|
|
255
|
+
});
|
|
256
|
+
it('rejects a symlinked project destination component before writing an asset', () => {
|
|
257
|
+
const outside = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-outside-'));
|
|
258
|
+
try {
|
|
259
|
+
fs_1.default.mkdirSync(path_1.default.join(packRoot, 'configs'));
|
|
260
|
+
fs_1.default.writeFileSync(path_1.default.join(packRoot, 'configs', 'eslint.config.awm.mjs'), 'registry content');
|
|
261
|
+
fs_1.default.symlinkSync(outside, path_1.default.join(projectRoot, 'configs'));
|
|
262
|
+
expect(() => (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
|
|
263
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['configs/eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
264
|
+
} })).toThrow(/symlink|destination/i);
|
|
265
|
+
expect(fs_1.default.existsSync(path_1.default.join(outside, 'eslint.config.awm.mjs'))).toBe(false);
|
|
266
|
+
}
|
|
267
|
+
finally {
|
|
268
|
+
fs_1.default.rmSync(outside, { recursive: true, force: true });
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
it('does not stage an asset through a destination ancestor swapped to a symlink', () => {
|
|
272
|
+
const outside = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-outside-'));
|
|
273
|
+
const directory = path_1.default.join(projectRoot, 'configs');
|
|
274
|
+
const originalWrite = fs_1.default.writeFileSync.bind(fs_1.default);
|
|
275
|
+
const write = jest.spyOn(fs_1.default, 'writeFileSync');
|
|
276
|
+
let swapped = false;
|
|
277
|
+
let stagedThroughSwappedAncestor = false;
|
|
278
|
+
try {
|
|
279
|
+
fs_1.default.mkdirSync(path_1.default.join(packRoot, 'configs'));
|
|
280
|
+
fs_1.default.writeFileSync(path_1.default.join(packRoot, 'configs', 'eslint.config.awm.mjs'), 'registry content');
|
|
281
|
+
write.mockImplementation(((file, data, options) => {
|
|
282
|
+
if (!swapped && typeof file === 'string' && path_1.default.basename(file).startsWith('.eslint.config.awm.mjs.')) {
|
|
283
|
+
swapped = true;
|
|
284
|
+
fs_1.default.renameSync(directory, `${directory}.original`);
|
|
285
|
+
fs_1.default.symlinkSync(outside, directory);
|
|
286
|
+
stagedThroughSwappedAncestor = path_1.default.dirname(file) === directory;
|
|
287
|
+
}
|
|
288
|
+
return originalWrite(file, data, options);
|
|
289
|
+
}));
|
|
290
|
+
const result = (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
|
|
291
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['configs/eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
292
|
+
} });
|
|
293
|
+
expect(result.configured).toEqual(['configs/eslint.config.awm.mjs']);
|
|
294
|
+
expect(stagedThroughSwappedAncestor).toBe(false);
|
|
295
|
+
expect(fs_1.default.readdirSync(outside)).toEqual([]);
|
|
296
|
+
}
|
|
297
|
+
finally {
|
|
298
|
+
write.mockRestore();
|
|
299
|
+
fs_1.default.rmSync(outside, { recursive: true, force: true });
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
it('rejects a symlinked .awm ancestor before publishing a portable v3 manifest', () => {
|
|
303
|
+
const outside = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-outside-'));
|
|
304
|
+
try {
|
|
305
|
+
fs_1.default.symlinkSync(outside, path_1.default.join(projectRoot, '.awm'));
|
|
306
|
+
expect(() => (0, materialize_1.materializePortableSensors)({ projectRoot, pack: 'js-ts', source: portableSource(), sensors: {
|
|
307
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
308
|
+
} })).toThrow(/symlink|destination|safe/i);
|
|
309
|
+
expect(fs_1.default.readdirSync(outside)).toEqual([]);
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
fs_1.default.rmSync(outside, { recursive: true, force: true });
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
it('does not overwrite a v3 manifest concurrently published after staging', () => {
|
|
316
|
+
const manifestPath = path_1.default.join(projectRoot, '.awm', 'sensors.json');
|
|
317
|
+
fs_1.default.mkdirSync(path_1.default.dirname(manifestPath));
|
|
318
|
+
fs_1.default.writeFileSync(manifestPath, 'concurrent manifest');
|
|
319
|
+
expect(() => (0, materialize_1.materializePortableSensors)({ projectRoot, pack: 'js-ts', source: portableSource(), sensors: {
|
|
320
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
321
|
+
} })).toThrow(/manifest|exist|concurrent|publish|destination/i);
|
|
322
|
+
expect(fs_1.default.readFileSync(manifestPath, 'utf8')).toBe('concurrent manifest');
|
|
323
|
+
});
|
|
324
|
+
it('rejects a symlinked packageRoot before writing an asset', () => {
|
|
325
|
+
const outside = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-outside-'));
|
|
326
|
+
try {
|
|
327
|
+
fs_1.default.symlinkSync(outside, path_1.default.join(projectRoot, 'package'));
|
|
328
|
+
expect(() => (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', packageRoot: 'package', sensors: {
|
|
329
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
330
|
+
} })).toThrow(/symlink|destination/i);
|
|
331
|
+
expect(fs_1.default.existsSync(path_1.default.join(outside, 'eslint.config.awm.mjs'))).toBe(false);
|
|
332
|
+
}
|
|
333
|
+
finally {
|
|
334
|
+
fs_1.default.rmSync(outside, { recursive: true, force: true });
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
it('does not overwrite an asset concurrently published after destination observation', () => {
|
|
338
|
+
const destination = path_1.default.join(projectRoot, 'eslint.config.awm.mjs');
|
|
339
|
+
fs_1.default.writeFileSync(destination, 'owner content');
|
|
340
|
+
const result = (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
|
|
341
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
342
|
+
} });
|
|
343
|
+
expect(result.configured).toEqual([]);
|
|
344
|
+
expect(result.preserved).toEqual(['eslint.config.awm.mjs']);
|
|
345
|
+
expect(fs_1.default.readFileSync(destination, 'utf8')).toBe('owner content');
|
|
346
|
+
});
|
|
347
|
+
test.each(['C:/sensor-assets/eslint.config.awm.mjs', 'C:sensor-assets/eslint.config.awm.mjs'])('rejects Windows-rooted or drive-qualified asset paths: %s', asset => {
|
|
348
|
+
expect(() => (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
|
|
349
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: [asset], initializedCompatibility: evidence },
|
|
350
|
+
} })).toThrow(/contained relative asset path/i);
|
|
351
|
+
});
|
|
352
|
+
it('rejects a selected asset beneath a symlinked registry directory before copying it', () => {
|
|
353
|
+
const outside = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-outside-'));
|
|
354
|
+
try {
|
|
355
|
+
fs_1.default.mkdirSync(path_1.default.join(packRoot, 'configs'));
|
|
356
|
+
fs_1.default.writeFileSync(path_1.default.join(outside, 'eslint.config.awm.mjs'), 'outside content');
|
|
357
|
+
fs_1.default.symlinkSync(outside, path_1.default.join(packRoot, 'configs', 'linked'));
|
|
358
|
+
expect(() => (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
|
|
359
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['configs/linked/eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
360
|
+
} })).toThrow('symlink');
|
|
361
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'configs', 'linked', 'eslint.config.awm.mjs'))).toBe(false);
|
|
362
|
+
}
|
|
363
|
+
finally {
|
|
364
|
+
fs_1.default.rmSync(outside, { recursive: true, force: true });
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
it.skip('rejects an asset replaced after inspection and before its safe open', () => {
|
|
368
|
+
const originalOpen = fs_1.default.openSync.bind(fs_1.default);
|
|
369
|
+
const open = jest.spyOn(fs_1.default, 'openSync');
|
|
370
|
+
let swapped = false;
|
|
371
|
+
try {
|
|
372
|
+
open.mockImplementation(((file, flags, mode) => {
|
|
373
|
+
if (!swapped && file === path_1.default.join(packRoot, 'eslint.config.awm.mjs')) {
|
|
374
|
+
swapped = true;
|
|
375
|
+
fs_1.default.renameSync(file, path_1.default.join(packRoot, 'eslint.config.awm.original.mjs'));
|
|
376
|
+
fs_1.default.writeFileSync(file, 'replacement content');
|
|
377
|
+
}
|
|
378
|
+
return originalOpen(file, flags, mode);
|
|
379
|
+
}));
|
|
380
|
+
expect(() => (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
|
|
381
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
382
|
+
} })).toThrow(/changed|identity|safe/i);
|
|
383
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'eslint.config.awm.mjs'))).toBe(false);
|
|
384
|
+
}
|
|
385
|
+
finally {
|
|
386
|
+
open.mockRestore();
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
it('re-establishes parent containment immediately before safely opening an asset', () => {
|
|
390
|
+
const outside = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-outside-'));
|
|
391
|
+
const originalLstat = fs_1.default.lstatSync.bind(fs_1.default);
|
|
392
|
+
const lstat = jest.spyOn(fs_1.default, 'lstatSync');
|
|
393
|
+
const directory = path_1.default.join(packRoot, 'configs');
|
|
394
|
+
let inspections = 0;
|
|
395
|
+
try {
|
|
396
|
+
fs_1.default.mkdirSync(directory);
|
|
397
|
+
fs_1.default.writeFileSync(path_1.default.join(directory, 'eslint.config.awm.mjs'), 'registry content');
|
|
398
|
+
fs_1.default.writeFileSync(path_1.default.join(outside, 'eslint.config.awm.mjs'), 'outside content');
|
|
399
|
+
lstat.mockImplementation(((candidate, options) => {
|
|
400
|
+
if (candidate === directory && ++inspections === 2) {
|
|
401
|
+
fs_1.default.renameSync(directory, `${directory}.original`);
|
|
402
|
+
fs_1.default.symlinkSync(outside, directory);
|
|
403
|
+
}
|
|
404
|
+
return originalLstat(candidate, options);
|
|
405
|
+
}));
|
|
406
|
+
expect(() => (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
|
|
407
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['configs/eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
408
|
+
} })).toThrow('symlink');
|
|
409
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'configs', 'eslint.config.awm.mjs'))).toBe(false);
|
|
410
|
+
}
|
|
411
|
+
finally {
|
|
412
|
+
lstat.mockRestore();
|
|
413
|
+
fs_1.default.rmSync(outside, { recursive: true, force: true });
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
it('rejects a pack root reached through a symlinked ancestor before copying registry content', () => {
|
|
417
|
+
const registry = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-registry-'));
|
|
418
|
+
const linkedRegistry = path_1.default.join(path_1.default.dirname(registry), `${path_1.default.basename(registry)}-linked`);
|
|
419
|
+
const linkedPackRoot = path_1.default.join(linkedRegistry, 'sensor-packs', 'js-ts');
|
|
420
|
+
try {
|
|
421
|
+
fs_1.default.mkdirSync(path_1.default.join(registry, 'sensor-packs', 'js-ts'), { recursive: true });
|
|
422
|
+
fs_1.default.writeFileSync(path_1.default.join(registry, 'sensor-packs', 'js-ts', 'eslint.config.awm.mjs'), 'outside content');
|
|
423
|
+
fs_1.default.symlinkSync(registry, linkedRegistry);
|
|
424
|
+
expect(() => (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot: linkedPackRoot, pack: 'js-ts', sensors: {
|
|
425
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
426
|
+
} })).toThrow('symlink');
|
|
427
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'eslint.config.awm.mjs'))).toBe(false);
|
|
428
|
+
}
|
|
429
|
+
finally {
|
|
430
|
+
fs_1.default.rmSync(linkedRegistry, { force: true });
|
|
431
|
+
fs_1.default.rmSync(registry, { recursive: true, force: true });
|
|
432
|
+
}
|
|
433
|
+
});
|
|
41
434
|
it('revalidates a shared Semgrep policy reference without materializing the policy itself', () => {
|
|
42
435
|
const sensorPacks = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-policy-'));
|
|
43
436
|
const semgrepPack = path_1.default.join(sensorPacks, 'python');
|
|
@@ -69,4 +462,21 @@ describe('materializeResolvedSensors', () => {
|
|
|
69
462
|
expect(result.orphaned).toEqual(['eslint.config.awm.cjs']);
|
|
70
463
|
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'eslint.config.awm.cjs'))).toBe(true);
|
|
71
464
|
});
|
|
465
|
+
it('does not touch a stale JavaScript-era manifest temporary file', () => {
|
|
466
|
+
const now = jest.spyOn(Date, 'now').mockReturnValue(12345);
|
|
467
|
+
const awm = path_1.default.join(projectRoot, '.awm');
|
|
468
|
+
const temporary = path_1.default.join(awm, `.sensors.json.${process.pid}.12345.tmp`);
|
|
469
|
+
try {
|
|
470
|
+
fs_1.default.mkdirSync(awm);
|
|
471
|
+
fs_1.default.writeFileSync(temporary, 'not ours');
|
|
472
|
+
expect(() => (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
|
|
473
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
474
|
+
} })).not.toThrow();
|
|
475
|
+
expect(fs_1.default.readFileSync(temporary, 'utf8')).toBe('not ours');
|
|
476
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'eslint.config.awm.mjs'))).toBe(true);
|
|
477
|
+
}
|
|
478
|
+
finally {
|
|
479
|
+
now.mockRestore();
|
|
480
|
+
}
|
|
481
|
+
});
|
|
72
482
|
});
|