agentic-workflow-manager 9.5.0 → 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.
Files changed (46) hide show
  1. package/dist/src/commands/preflight/checks.js +2 -2
  2. package/dist/src/commands/sensors/bootstrap.js +171 -0
  3. package/dist/src/commands/sensors/compatibility/contract.js +18 -15
  4. package/dist/src/commands/sensors/compatibility/manifest.js +29 -1
  5. package/dist/src/commands/sensors/compatibility/materialize.js +256 -54
  6. package/dist/src/commands/sensors/compatibility/safe-file.js +240 -5
  7. package/dist/src/commands/sensors/coverage/index.js +2 -2
  8. package/dist/src/commands/sensors/coverage/resolve.js +20 -10
  9. package/dist/src/commands/sensors/detection.js +31 -0
  10. package/dist/src/commands/sensors/exec.js +116 -11
  11. package/dist/src/commands/sensors/index.js +51 -10
  12. package/dist/src/commands/sensors/init.js +17 -170
  13. package/dist/src/commands/sensors/migrate.js +209 -0
  14. package/dist/src/commands/sensors/run.js +2 -2
  15. package/dist/src/core/init/steps.js +24 -11
  16. package/dist/src/core/secure-fs/native-bridge.js +209 -0
  17. package/dist/tests/commands/sensors/bootstrap-observational.test.js +24 -0
  18. package/dist/tests/commands/sensors/bootstrap.test.js +175 -0
  19. package/dist/tests/commands/sensors/compatibility/contract.test.js +38 -0
  20. package/dist/tests/commands/sensors/compatibility/manifest.test.js +67 -0
  21. package/dist/tests/commands/sensors/compatibility/materialize.test.js +410 -0
  22. package/dist/tests/commands/sensors/compatibility/pack-source.test.js +34 -1
  23. package/dist/tests/commands/sensors/compatibility/safe-file.test.js +52 -0
  24. package/dist/tests/commands/sensors/coverage/index.test.js +20 -0
  25. package/dist/tests/commands/sensors/coverage/resolve.test.js +10 -0
  26. package/dist/tests/commands/sensors/exec.test.js +86 -0
  27. package/dist/tests/commands/sensors/index.test.js +54 -0
  28. package/dist/tests/commands/sensors/init-pack-unavailable.test.js +21 -84
  29. package/dist/tests/commands/sensors/init.test.js +56 -490
  30. package/dist/tests/commands/sensors/migrate.test.js +513 -0
  31. package/dist/tests/core/init/orchestrator.test.js +3 -1
  32. package/dist/tests/core/secure-fs/native-bridge.test.js +733 -0
  33. package/dist/tests/integration/sensor-bootstrap.e2e.test.js +165 -0
  34. package/dist/tests/integration/sensor-compatibility.e2e.test.js +10 -2
  35. package/dist/tests/integration/sensor-structured-run.e2e.test.js +3 -1
  36. package/dist/tests/structural/native-release-artifacts.test.js +22 -0
  37. package/dist/tests/structural/release-flags-are-reachable-from-ci.test.js +10 -0
  38. package/dist/tests/structural/release-registry-provenance.test.js +4 -0
  39. package/dist/tests/structural/sensor-portability-contract.test.js +44 -0
  40. package/package.json +6 -2
  41. package/prebuilds/darwin-arm64/secure_fs.node +0 -0
  42. package/prebuilds/darwin-x64/secure_fs.node +0 -0
  43. package/prebuilds/linux-arm64/secure_fs.node +0 -0
  44. package/prebuilds/linux-x64/secure_fs.node +0 -0
  45. package/prebuilds/win32-arm64/secure_fs.node +0 -0
  46. 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
  });
@@ -7,6 +7,8 @@ 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 pack_source_1 = require("../../../../src/commands/sensors/compatibility/pack-source");
10
+ const safe_file_1 = require("../../../../src/commands/sensors/compatibility/safe-file");
11
+ const itLinux = process.platform === 'linux' ? it : it.skip;
10
12
  function resolveWithoutNoFollow(...args) {
11
13
  let resolve;
12
14
  jest.isolateModules(() => {
@@ -15,6 +17,7 @@ function resolveWithoutNoFollow(...args) {
15
17
  const constants = { ...actual.constants, O_NOFOLLOW: undefined };
16
18
  return { __esModule: true, default: { ...actual, constants }, ...actual, constants };
17
19
  });
20
+ require('../../../../src/commands/sensors/compatibility/safe-file').setPortableReadForTests(true);
18
21
  resolve = require('../../../../src/commands/sensors/compatibility/pack-source').resolvePackSource;
19
22
  });
20
23
  jest.dontMock('fs');
@@ -30,6 +33,7 @@ function resolveWithoutNoFollowWithExactStats(...args) {
30
33
  const constants = { ...actual.constants, O_NOFOLLOW: undefined };
31
34
  return { __esModule: true, default: { ...actual, constants }, ...actual, constants };
32
35
  });
36
+ require('../../../../src/commands/sensors/compatibility/safe-file').setPortableReadForTests(true);
33
37
  resolve = require('../../../../src/commands/sensors/compatibility/pack-source').resolvePackSource;
34
38
  });
35
39
  jest.dontMock('fs');
@@ -38,6 +42,8 @@ function resolveWithoutNoFollowWithExactStats(...args) {
38
42
  return resolve(...args);
39
43
  }
40
44
  describe('resolvePackSource', () => {
45
+ beforeEach(() => (0, safe_file_1.setPortableReadForTests)(true));
46
+ afterEach(() => (0, safe_file_1.setPortableReadForTests)(false));
41
47
  it('uses the first registry containing the exact contained pack file', () => {
42
48
  const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-'));
43
49
  try {
@@ -179,7 +185,7 @@ describe('resolvePackSource', () => {
179
185
  fs_1.default.rmSync(root, { recursive: true, force: true });
180
186
  }
181
187
  });
182
- it('accepts a parent symlink replacement when it resolves to the inspected pack inode', () => {
188
+ itLinux('accepts a parent symlink replacement when Linux descriptor binding preserves the inspected pack inode', () => {
183
189
  const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-same-inode-'));
184
190
  const packDir = path_1.default.join(root, 'sensor-packs', 'js-ts');
185
191
  const movedPackDir = path_1.default.join(root, 'sensor-packs', 'js-ts-original');
@@ -206,6 +212,33 @@ describe('resolvePackSource', () => {
206
212
  fs_1.default.rmSync(root, { recursive: true, force: true });
207
213
  }
208
214
  });
215
+ it('rejects an observable parent symlink replacement in the portable fallback', () => {
216
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-portable-parent-swap-'));
217
+ const packDir = path_1.default.join(root, 'sensor-packs', 'js-ts');
218
+ const movedPackDir = path_1.default.join(root, 'sensor-packs', 'js-ts-original');
219
+ const packFile = path_1.default.join(packDir, 'pack.json');
220
+ const originalOpen = fs_1.default.openSync.bind(fs_1.default);
221
+ const open = jest.spyOn(fs_1.default, 'openSync');
222
+ let swapped = false;
223
+ try {
224
+ fs_1.default.mkdirSync(packDir, { recursive: true });
225
+ fs_1.default.writeFileSync(packFile, '{"from":"original"}');
226
+ open.mockImplementation(((file, flags, mode) => {
227
+ if (!swapped && file === fs_1.default.realpathSync(packFile)) {
228
+ swapped = true;
229
+ fs_1.default.renameSync(packDir, movedPackDir);
230
+ fs_1.default.symlinkSync(movedPackDir, packDir, 'dir');
231
+ }
232
+ return originalOpen(file, flags, mode);
233
+ }));
234
+ expect(() => resolveWithoutNoFollow('js-ts', { registries: [{ name: 'safe', remote: '', contentRoot: root }] }))
235
+ .toThrow(/changed|identity|safe|symlink/i);
236
+ }
237
+ finally {
238
+ open.mockRestore();
239
+ fs_1.default.rmSync(root, { recursive: true, force: true });
240
+ }
241
+ });
209
242
  it('rejects same-inode content growth between inspection and open', () => {
210
243
  const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-size-race-'));
211
244
  const packFile = path_1.default.join(root, 'sensor-packs', 'js-ts', 'pack.json');
@@ -0,0 +1,52 @@
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 safe_file_1 = require("../../../../src/commands/sensors/compatibility/safe-file");
10
+ function projectLeaseRunner() {
11
+ return require('../../../../src/commands/sensors/compatibility/safe-file').withProjectLease;
12
+ }
13
+ describe('safe-file native replacement observation', () => {
14
+ let root;
15
+ beforeEach(() => { root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-safe-file-')); });
16
+ afterEach(() => { fs_1.default.rmSync(root, { recursive: true, force: true }); });
17
+ it('carries bytes and the identity from one exact native read into replacement', () => {
18
+ const target = path_1.default.join(root, 'sensors.json');
19
+ fs_1.default.writeFileSync(target, 'observed bytes');
20
+ const inspected = fs_1.default.lstatSync(target, { bigint: true });
21
+ const observed = (0, safe_file_1.readInspectedBoundedFileWithIdentity)(target, inspected, 1024, reason => new Error(`safe read failed: ${reason}`));
22
+ (0, safe_file_1.writeProjectFile)(root, 'sensors.json', Buffer.from('new bytes'), {
23
+ mode: 'replace',
24
+ expected: observed.content,
25
+ expectedIdentity: observed.identity,
26
+ createParents: false,
27
+ });
28
+ expect(fs_1.default.readFileSync(target, 'utf8')).toBe('new bytes');
29
+ });
30
+ it('compensates only the exact project file observed after creation', () => {
31
+ const target = path_1.default.join(root, 'created.json');
32
+ fs_1.default.writeFileSync(target, 'created bytes');
33
+ const inspected = fs_1.default.lstatSync(target, { bigint: true });
34
+ const observed = (0, safe_file_1.readInspectedBoundedFileWithIdentity)(target, inspected, 1024, reason => new Error(`safe read failed: ${reason}`));
35
+ (0, safe_file_1.removeObservedProjectFile)(root, 'created.json', observed.identity);
36
+ expect(fs_1.default.existsSync(target)).toBe(false);
37
+ });
38
+ it('rejects a nested claimant before callback mutation and releases after a thrown callback', () => {
39
+ const withProjectLease = projectLeaseRunner();
40
+ const target = path_1.default.join(root, 'owner.txt');
41
+ fs_1.default.writeFileSync(target, 'owner bytes');
42
+ withProjectLease(root, () => {
43
+ expect(() => withProjectLease(root, () => fs_1.default.writeFileSync(target, 'claimant bytes')))
44
+ .toThrow(/project lease conflict/i);
45
+ expect(fs_1.default.readFileSync(target, 'utf8')).toBe('owner bytes');
46
+ });
47
+ const callbackFailure = new Error('lease callback failed');
48
+ expect(() => withProjectLease(root, () => { throw callbackFailure; })).toThrow(callbackFailure);
49
+ expect(() => withProjectLease(root, () => fs_1.default.writeFileSync(target, 'next claimant bytes'))).not.toThrow();
50
+ expect(fs_1.default.readFileSync(target, 'utf8')).toBe('next claimant bytes');
51
+ });
52
+ });
@@ -36,6 +36,26 @@ test('old pack is no_reference and preserves pack and registry', async () => {
36
36
  static: { status: 'inconclusive', reason: 'no_reference', classes: [] },
37
37
  empirical: expect.objectContaining({ status: 'evidence', classes: [expect.objectContaining({ outcome: 'coverage-unverifiable' })] }) });
38
38
  });
39
+ test('v3 coverage probes the concrete registry selected by logical resolution', async () => {
40
+ const manifest = { kind: 'v3', pack: {
41
+ schemaVersion: 3, mode: 'project-sensors', pack: 'js-ts', source: { registry: 'baseline' }, sensors: {
42
+ lint: {
43
+ enabled: true, variantId: 'eslint-9', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
44
+ initializedCompatibility: { state: 'certified', reason: 'ok', variantId: 'eslint-9', toolVersion: '9.0.0', runtimeVersion: '24.0.0', certifiedRange: '>=9', evidence: [] },
45
+ },
46
+ },
47
+ } };
48
+ const contract = { schemaVersion: 1, classes: {
49
+ linting: { description: 'Linting', detectors: [{ sensor: 'lint' }], remedy: { summary: 'Add lint', command: 'npm i -D eslint' } },
50
+ } };
51
+ const resolveLive = jest.fn().mockResolvedValue({ sensors: {} });
52
+ await (0, coverage_1.runCoverage)('/fixture', {
53
+ resolve: () => ({ kind: 'ready', projectRoot: '/fixture', pack: 'js-ts', registry: 'baseline', registryRoot: '/registries/baseline', manifest, contract }),
54
+ resolveLive,
55
+ scan: () => ({ entries: [], sources: { activeFiles: 0, archivedFiles: 0, validEntries: 0, validFindings: 0, skippedFindings: 0, skippedByReason: {} }, omittedEvidenceRefs: 0 }),
56
+ });
57
+ expect(resolveLive).toHaveBeenCalledWith('/fixture', 'js-ts', '/registries/baseline');
58
+ });
39
59
  test('ready input observes every declared detector and evaluates once', async () => {
40
60
  const manifest = { kind: 'legacy', pack: { pack: 'js-ts', sensors: { lint: { cmd: 'eslint .' }, format: { cmd: 'prettier --check .' } }, compatibility: {
41
61
  state: 'compatible-unverified', reason: 'legacy', variantId: null, toolVersion: null, runtimeVersion: null, certifiedRange: null, evidence: [],
@@ -81,6 +81,16 @@ test('registry ordering chooses the earlier exact pack when both registries cont
81
81
  writePack('second', 'js-ts', { name: 'js-ts', sensors: {}, coverage });
82
82
  expect((0, resolve_1.resolveCoverageInputs)(project)).toMatchObject({ kind: 'ready', registry: 'first' });
83
83
  });
84
+ test('v3 project-sensors resolves coverage from its declared logical registry', () => {
85
+ writeManifest({ schemaVersion: 3, mode: 'project-sensors', pack: 'js-ts', source: { registry: 'second' }, sensors: {} });
86
+ configure(['first', 'second']);
87
+ writePack('first', 'js-ts', { name: 'js-ts', sensors: {} });
88
+ writePack('second', 'js-ts', { name: 'js-ts', sensors: {}, coverage });
89
+ expect((0, resolve_1.resolveCoverageInputs)(project)).toMatchObject({
90
+ kind: 'ready', pack: 'js-ts', registry: 'second',
91
+ registryRoot: path_1.default.join(awmHome, 'registries', 'second'),
92
+ });
93
+ });
84
94
  test('old pack without coverage is no_reference, not covered', () => {
85
95
  writeManifest({ pack: 'js-ts', sensors: {} });
86
96
  configure(['baseline']);