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.
Files changed (57) hide show
  1. package/dist/src/commands/preflight/checks.js +58 -27
  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 +75 -2
  5. package/dist/src/commands/sensors/compatibility/materialize.js +256 -54
  6. package/dist/src/commands/sensors/compatibility/pack-source.js +61 -17
  7. package/dist/src/commands/sensors/compatibility/safe-file.js +286 -0
  8. package/dist/src/commands/sensors/compatibility/source.js +75 -0
  9. package/dist/src/commands/sensors/coverage/index.js +2 -2
  10. package/dist/src/commands/sensors/coverage/resolve.js +35 -29
  11. package/dist/src/commands/sensors/detection.js +31 -0
  12. package/dist/src/commands/sensors/exec.js +116 -11
  13. package/dist/src/commands/sensors/index.js +51 -10
  14. package/dist/src/commands/sensors/init.js +17 -168
  15. package/dist/src/commands/sensors/migrate.js +209 -0
  16. package/dist/src/commands/sensors/project.js +107 -0
  17. package/dist/src/commands/sensors/run.js +66 -47
  18. package/dist/src/commands/sensors/status.js +97 -56
  19. package/dist/src/core/init/steps.js +24 -11
  20. package/dist/src/core/secure-fs/native-bridge.js +209 -0
  21. package/dist/tests/commands/preflight/preflight.test.js +31 -0
  22. package/dist/tests/commands/sensors/bootstrap-observational.test.js +24 -0
  23. package/dist/tests/commands/sensors/bootstrap.test.js +175 -0
  24. package/dist/tests/commands/sensors/compatibility/contract.test.js +38 -0
  25. package/dist/tests/commands/sensors/compatibility/manifest.test.js +90 -0
  26. package/dist/tests/commands/sensors/compatibility/materialize.test.js +410 -0
  27. package/dist/tests/commands/sensors/compatibility/pack-source.test.js +249 -0
  28. package/dist/tests/commands/sensors/compatibility/safe-file.test.js +52 -0
  29. package/dist/tests/commands/sensors/compatibility/source.test.js +121 -0
  30. package/dist/tests/commands/sensors/coverage/index.test.js +20 -0
  31. package/dist/tests/commands/sensors/coverage/resolve.test.js +36 -34
  32. package/dist/tests/commands/sensors/exec.test.js +86 -0
  33. package/dist/tests/commands/sensors/index.test.js +54 -0
  34. package/dist/tests/commands/sensors/init-pack-unavailable.test.js +21 -84
  35. package/dist/tests/commands/sensors/init.test.js +56 -490
  36. package/dist/tests/commands/sensors/migrate.test.js +513 -0
  37. package/dist/tests/commands/sensors/project.test.js +102 -0
  38. package/dist/tests/commands/sensors/run.test.js +107 -4
  39. package/dist/tests/commands/sensors/status.test.js +106 -2
  40. package/dist/tests/core/init/orchestrator.test.js +3 -1
  41. package/dist/tests/core/secure-fs/native-bridge.test.js +733 -0
  42. package/dist/tests/integration/preflight-json-pipe.e2e.test.js +5 -0
  43. package/dist/tests/integration/sensor-bootstrap.e2e.test.js +165 -0
  44. package/dist/tests/integration/sensor-compatibility.e2e.test.js +14 -23
  45. package/dist/tests/integration/sensor-coverage.e2e.test.js +15 -24
  46. package/dist/tests/integration/sensor-structured-run.e2e.test.js +3 -1
  47. package/dist/tests/structural/native-release-artifacts.test.js +22 -0
  48. package/dist/tests/structural/release-flags-are-reachable-from-ci.test.js +10 -0
  49. package/dist/tests/structural/release-registry-provenance.test.js +4 -0
  50. package/dist/tests/structural/sensor-portability-contract.test.js +44 -0
  51. package/package.json +6 -2
  52. package/prebuilds/darwin-arm64/secure_fs.node +0 -0
  53. package/prebuilds/darwin-x64/secure_fs.node +0 -0
  54. package/prebuilds/linux-arm64/secure_fs.node +0 -0
  55. package/prebuilds/linux-x64/secure_fs.node +0 -0
  56. package/prebuilds/win32-arm64/secure_fs.node +0 -0
  57. package/prebuilds/win32-x64/secure_fs.node +0 -0
@@ -7,7 +7,43 @@ 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;
12
+ function resolveWithoutNoFollow(...args) {
13
+ let resolve;
14
+ jest.isolateModules(() => {
15
+ jest.doMock('fs', () => {
16
+ const actual = jest.requireActual('fs');
17
+ const constants = { ...actual.constants, O_NOFOLLOW: undefined };
18
+ return { __esModule: true, default: { ...actual, constants }, ...actual, constants };
19
+ });
20
+ require('../../../../src/commands/sensors/compatibility/safe-file').setPortableReadForTests(true);
21
+ resolve = require('../../../../src/commands/sensors/compatibility/pack-source').resolvePackSource;
22
+ });
23
+ jest.dontMock('fs');
24
+ if (!resolve)
25
+ throw new Error('portable pack source resolver could not be loaded');
26
+ return resolve(...args);
27
+ }
28
+ function resolveWithoutNoFollowWithExactStats(...args) {
29
+ let resolve;
30
+ jest.isolateModules(() => {
31
+ jest.doMock('fs', () => {
32
+ const actual = jest.requireActual('fs');
33
+ const constants = { ...actual.constants, O_NOFOLLOW: undefined };
34
+ return { __esModule: true, default: { ...actual, constants }, ...actual, constants };
35
+ });
36
+ require('../../../../src/commands/sensors/compatibility/safe-file').setPortableReadForTests(true);
37
+ resolve = require('../../../../src/commands/sensors/compatibility/pack-source').resolvePackSource;
38
+ });
39
+ jest.dontMock('fs');
40
+ if (!resolve)
41
+ throw new Error('portable exact-stat pack source resolver could not be loaded');
42
+ return resolve(...args);
43
+ }
10
44
  describe('resolvePackSource', () => {
45
+ beforeEach(() => (0, safe_file_1.setPortableReadForTests)(true));
46
+ afterEach(() => (0, safe_file_1.setPortableReadForTests)(false));
11
47
  it('uses the first registry containing the exact contained pack file', () => {
12
48
  const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-'));
13
49
  try {
@@ -52,4 +88,217 @@ describe('resolvePackSource', () => {
52
88
  fs_1.default.rmSync(root, { recursive: true, force: true });
53
89
  }
54
90
  });
91
+ it('rejects a symlinked registry content root', () => {
92
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-root-link-'));
93
+ const target = path_1.default.join(root, 'target');
94
+ const linked = path_1.default.join(root, 'linked');
95
+ try {
96
+ fs_1.default.mkdirSync(path_1.default.join(target, 'sensor-packs', 'js-ts'), { recursive: true });
97
+ fs_1.default.writeFileSync(path_1.default.join(target, 'sensor-packs', 'js-ts', 'pack.json'), '{}');
98
+ fs_1.default.symlinkSync(target, linked, 'dir');
99
+ expect(() => (0, pack_source_1.resolvePackSource)('js-ts', { registries: [{ name: 'linked', remote: '', contentRoot: linked }] }))
100
+ .toThrow(/symbolic|symlink/i);
101
+ }
102
+ finally {
103
+ fs_1.default.rmSync(root, { recursive: true, force: true });
104
+ }
105
+ });
106
+ it('opens the resolved pack with no-follow semantics before reading it', () => {
107
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-nofollow-'));
108
+ const open = jest.spyOn(fs_1.default, 'openSync');
109
+ try {
110
+ const packFile = path_1.default.join(root, 'sensor-packs', 'js-ts', 'pack.json');
111
+ fs_1.default.mkdirSync(path_1.default.dirname(packFile), { recursive: true });
112
+ fs_1.default.writeFileSync(packFile, '{}');
113
+ (0, pack_source_1.resolvePackSource)('js-ts', { registries: [{ name: 'safe', remote: '', contentRoot: root }] });
114
+ const flags = open.mock.calls[0]?.[1];
115
+ expect(typeof flags).toBe('number');
116
+ if (typeof fs_1.default.constants.O_NOFOLLOW === 'number') {
117
+ expect(flags & fs_1.default.constants.O_NOFOLLOW).toBe(fs_1.default.constants.O_NOFOLLOW);
118
+ }
119
+ else {
120
+ expect(flags).toBe(fs_1.default.constants.O_RDONLY);
121
+ }
122
+ }
123
+ finally {
124
+ open.mockRestore();
125
+ fs_1.default.rmSync(root, { recursive: true, force: true });
126
+ }
127
+ });
128
+ it('resolves an inspected regular pack when no-follow is unavailable', () => {
129
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-portable-open-'));
130
+ try {
131
+ const packFile = path_1.default.join(root, 'sensor-packs', 'js-ts', 'pack.json');
132
+ fs_1.default.mkdirSync(path_1.default.dirname(packFile), { recursive: true });
133
+ fs_1.default.writeFileSync(packFile, '{"from":"portable"}');
134
+ expect(resolveWithoutNoFollow('js-ts', { registries: [{ name: 'safe', remote: '', contentRoot: root }] }).content)
135
+ .toBe('{"from":"portable"}');
136
+ }
137
+ finally {
138
+ fs_1.default.rmSync(root, { recursive: true, force: true });
139
+ }
140
+ });
141
+ it('uses exact bigint file identities for the portable safe-open fallback', () => {
142
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-bigint-'));
143
+ const lstat = jest.spyOn(fs_1.default, 'lstatSync');
144
+ const fstat = jest.spyOn(fs_1.default, 'fstatSync');
145
+ try {
146
+ const packFile = path_1.default.join(root, 'sensor-packs', 'js-ts', 'pack.json');
147
+ fs_1.default.mkdirSync(path_1.default.dirname(packFile), { recursive: true });
148
+ fs_1.default.writeFileSync(packFile, '{"from":"bigint"}');
149
+ expect(resolveWithoutNoFollowWithExactStats('js-ts', { registries: [{ name: 'safe', remote: '', contentRoot: root }] }).content)
150
+ .toBe('{"from":"bigint"}');
151
+ expect(lstat).toHaveBeenCalledWith(expect.any(String), { bigint: true });
152
+ expect(fstat).toHaveBeenCalledWith(expect.any(Number), { bigint: true });
153
+ }
154
+ finally {
155
+ lstat.mockRestore();
156
+ fstat.mockRestore();
157
+ fs_1.default.rmSync(root, { recursive: true, force: true });
158
+ }
159
+ });
160
+ it('rejects a parent-directory swap between inspection and no-follow open', () => {
161
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-parent-swap-'));
162
+ const packDir = path_1.default.join(root, 'sensor-packs', 'js-ts');
163
+ const movedPackDir = path_1.default.join(root, 'sensor-packs', 'js-ts-original');
164
+ const packFile = path_1.default.join(packDir, 'pack.json');
165
+ const originalOpen = fs_1.default.openSync.bind(fs_1.default);
166
+ const open = jest.spyOn(fs_1.default, 'openSync');
167
+ let swapped = false;
168
+ try {
169
+ fs_1.default.mkdirSync(packDir, { recursive: true });
170
+ fs_1.default.writeFileSync(packFile, '{"from":"original"}');
171
+ open.mockImplementation(((file, flags, mode) => {
172
+ if (!swapped && file === fs_1.default.realpathSync(packFile)) {
173
+ swapped = true;
174
+ fs_1.default.renameSync(packDir, movedPackDir);
175
+ fs_1.default.mkdirSync(packDir, { recursive: true });
176
+ fs_1.default.writeFileSync(packFile, '{"from":"replacement"}');
177
+ }
178
+ return originalOpen(file, flags, mode);
179
+ }));
180
+ expect(() => (0, pack_source_1.resolvePackSource)('js-ts', { registries: [{ name: 'safe', remote: '', contentRoot: root }] }))
181
+ .toThrow(/changed|identity|regular|safe/i);
182
+ }
183
+ finally {
184
+ open.mockRestore();
185
+ fs_1.default.rmSync(root, { recursive: true, force: true });
186
+ }
187
+ });
188
+ itLinux('accepts a parent symlink replacement when Linux descriptor binding preserves the inspected pack inode', () => {
189
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-same-inode-'));
190
+ const packDir = path_1.default.join(root, 'sensor-packs', 'js-ts');
191
+ const movedPackDir = path_1.default.join(root, 'sensor-packs', 'js-ts-original');
192
+ const packFile = path_1.default.join(packDir, 'pack.json');
193
+ const originalOpen = fs_1.default.openSync.bind(fs_1.default);
194
+ const open = jest.spyOn(fs_1.default, 'openSync');
195
+ let swapped = false;
196
+ try {
197
+ fs_1.default.mkdirSync(packDir, { recursive: true });
198
+ fs_1.default.writeFileSync(packFile, '{"from":"original"}');
199
+ open.mockImplementation(((file, flags, mode) => {
200
+ if (!swapped && file === fs_1.default.realpathSync(packFile)) {
201
+ swapped = true;
202
+ fs_1.default.renameSync(packDir, movedPackDir);
203
+ fs_1.default.symlinkSync(movedPackDir, packDir, 'dir');
204
+ }
205
+ return originalOpen(file, flags, mode);
206
+ }));
207
+ expect((0, pack_source_1.resolvePackSource)('js-ts', { registries: [{ name: 'safe', remote: '', contentRoot: root }] }).content)
208
+ .toBe('{"from":"original"}');
209
+ }
210
+ finally {
211
+ open.mockRestore();
212
+ fs_1.default.rmSync(root, { recursive: true, force: true });
213
+ }
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
+ });
242
+ it('rejects same-inode content growth between inspection and open', () => {
243
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-size-race-'));
244
+ const packFile = path_1.default.join(root, 'sensor-packs', 'js-ts', 'pack.json');
245
+ const originalOpen = fs_1.default.openSync.bind(fs_1.default);
246
+ const open = jest.spyOn(fs_1.default, 'openSync');
247
+ let changed = false;
248
+ try {
249
+ fs_1.default.mkdirSync(path_1.default.dirname(packFile), { recursive: true });
250
+ fs_1.default.writeFileSync(packFile, '{"from":"original"}');
251
+ open.mockImplementation(((file, flags, mode) => {
252
+ if (!changed && file === fs_1.default.realpathSync(packFile)) {
253
+ changed = true;
254
+ fs_1.default.appendFileSync(packFile, 'x');
255
+ }
256
+ return originalOpen(file, flags, mode);
257
+ }));
258
+ expect(() => resolveWithoutNoFollow('js-ts', { registries: [{ name: 'safe', remote: '', contentRoot: root }] }))
259
+ .toThrow(/changed|size|identity/i);
260
+ }
261
+ finally {
262
+ open.mockRestore();
263
+ fs_1.default.rmSync(root, { recursive: true, force: true });
264
+ }
265
+ });
266
+ it('rejects same-inode truncation after safe open before reading', () => {
267
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-truncate-race-'));
268
+ const packFile = path_1.default.join(root, 'sensor-packs', 'js-ts', 'pack.json');
269
+ const originalRead = fs_1.default.readSync.bind(fs_1.default);
270
+ const read = jest.spyOn(fs_1.default, 'readSync');
271
+ let truncated = false;
272
+ try {
273
+ fs_1.default.mkdirSync(path_1.default.dirname(packFile), { recursive: true });
274
+ fs_1.default.writeFileSync(packFile, '{"from":"original"}');
275
+ read.mockImplementation(((descriptor, buffer, offset, length, position) => {
276
+ if (!truncated) {
277
+ truncated = true;
278
+ fs_1.default.truncateSync(packFile, 0);
279
+ }
280
+ return originalRead(descriptor, buffer, offset, length, position);
281
+ }));
282
+ expect(() => resolveWithoutNoFollow('js-ts', { registries: [{ name: 'safe', remote: '', contentRoot: root }] }))
283
+ .toThrow(/changed|size|identity/i);
284
+ }
285
+ finally {
286
+ read.mockRestore();
287
+ fs_1.default.rmSync(root, { recursive: true, force: true });
288
+ }
289
+ });
290
+ it('bounds diagnostics when a long content root cannot be inspected', () => {
291
+ const contentRoot = path_1.default.join(os_1.default.tmpdir(), `awm-${'x'.repeat(200_000)}`);
292
+ let message = '';
293
+ try {
294
+ (0, pack_source_1.resolvePackSource)('js-ts', { registries: [{ name: 'safe', remote: 'https://user:secret@example.invalid/registry', contentRoot }] });
295
+ }
296
+ catch (error) {
297
+ message = error instanceof Error ? error.message : String(error);
298
+ }
299
+ expect(message.length).toBeLessThan(4096);
300
+ expect(message).not.toContain(contentRoot);
301
+ expect(message).not.toContain('secret');
302
+ expect(message).toMatch(/inspect|source|registry/i);
303
+ });
55
304
  });
@@ -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
+ });
@@ -0,0 +1,121 @@
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 manifest_1 = require("../../../../src/commands/sensors/compatibility/manifest");
10
+ const source_1 = require("../../../../src/commands/sensors/compatibility/source");
11
+ function manifest(registryRoot) {
12
+ return (0, manifest_1.parseSensorManifest)({
13
+ schemaVersion: 2, pack: 'js-ts', ...(registryRoot ? { registryRoot } : {}), sensors: {
14
+ lint: {
15
+ enabled: false, variantId: 'eslint-9',
16
+ command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
17
+ initializedCompatibility: { state: 'certified', reason: 'fixture', variantId: 'eslint-9', toolVersion: '9.0.0', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] },
18
+ },
19
+ },
20
+ }, 'manifest');
21
+ }
22
+ function pack() {
23
+ return {
24
+ schemaVersion: 2, name: 'js-ts', description: 'fixture', detects: ['package.json'], sensors: {
25
+ lint: { applicability: { allFiles: ['package.json'] }, variants: [{
26
+ id: 'eslint-9', priority: 1,
27
+ requirements: { tool: 'eslint', toolRange: '>=9 <10', runtime: 'node', runtimeRange: '>=20' },
28
+ certifiedRange: '>=9 <10', assets: [], formatter: 'generic', probe: { kind: 'eslint-print-config' },
29
+ command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
30
+ }] },
31
+ },
32
+ coverage: { schemaVersion: 1, classes: { linting: { description: 'fixture', detectors: [{ sensor: 'lint' }], remedy: { summary: 'fixture', command: 'npm test' } } } },
33
+ };
34
+ }
35
+ function writePack(root) {
36
+ const directory = path_1.default.join(root, 'sensor-packs', 'js-ts');
37
+ fs_1.default.mkdirSync(directory, { recursive: true });
38
+ fs_1.default.writeFileSync(path_1.default.join(directory, 'pack.json'), JSON.stringify(pack()));
39
+ }
40
+ describe('resolveSensorSource', () => {
41
+ const roots = [];
42
+ afterEach(() => { for (const root of roots.splice(0))
43
+ fs_1.default.rmSync(root, { recursive: true, force: true }); });
44
+ it('preserves an existing v2 provenance path without registry-order rebinding', () => {
45
+ const bound = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-source-bound-'));
46
+ roots.push(bound);
47
+ const other = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-source-other-'));
48
+ roots.push(other);
49
+ writePack(bound);
50
+ writePack(other);
51
+ expect((0, source_1.resolveSensorSource)(manifest(bound), { registries: [{ name: 'other', remote: 'local', contentRoot: other }] }))
52
+ .toMatchObject({ kind: 'legacy-bound', source: { registry: { name: 'manifest-provenance' } } });
53
+ });
54
+ it('rebinds an absent v2 provenance only when exactly one registry matches every sensor identity', () => {
55
+ const absent = path_1.default.join(os_1.default.tmpdir(), 'does-not-exist-awma');
56
+ const registry = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-source-rebound-'));
57
+ roots.push(registry);
58
+ writePack(registry);
59
+ expect((0, source_1.resolveSensorSource)(manifest(absent), { registries: [{ name: 'baseline', remote: 'local', contentRoot: registry }] }))
60
+ .toMatchObject({ kind: 'legacy-rebound', source: { registry: { name: 'baseline' } } });
61
+ });
62
+ it('fails closed for zero or multiple structural candidates without exposing paths or remotes', () => {
63
+ const absent = path_1.default.join(os_1.default.tmpdir(), 'does-not-exist-awmb');
64
+ expect((0, source_1.resolveSensorSource)(manifest(absent), { registries: [] })).toMatchObject({ kind: 'source-unavailable', reason: 'no-compatible-registry' });
65
+ const first = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-source-first-'));
66
+ roots.push(first);
67
+ const second = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-source-second-'));
68
+ roots.push(second);
69
+ writePack(first);
70
+ writePack(second);
71
+ const result = (0, source_1.resolveSensorSource)(manifest(absent), { registries: [
72
+ { name: 'first', remote: 'https://user:secret@example.invalid/first', contentRoot: first },
73
+ { name: 'second', remote: 'https://user:secret@example.invalid/second', contentRoot: second },
74
+ ] });
75
+ expect(result).toMatchObject({ kind: 'source-ambiguous', candidates: ['first', 'second'] });
76
+ expect(JSON.stringify(result)).not.toContain('secret');
77
+ });
78
+ it('rejects a malformed v3 logical registry identity instead of classifying it as unavailable', () => {
79
+ expect(() => (0, source_1.resolveSensorSource)({
80
+ kind: 'v3', pack: { mode: 'project-sensors', pack: 'js-ts', source: { registry: '../escape' } },
81
+ }, { registries: [] })).toThrow(/registry.*stable|invalid.*registry/i);
82
+ });
83
+ it('bounds ambiguity diagnostics for extremely long valid registry names', () => {
84
+ const first = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-source-long-first-'));
85
+ roots.push(first);
86
+ const second = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-source-long-second-'));
87
+ roots.push(second);
88
+ writePack(first);
89
+ writePack(second);
90
+ const result = (0, source_1.resolveSensorSource)(manifest(path_1.default.join(os_1.default.tmpdir(), 'awm-source-long-missing')), { registries: [
91
+ { name: `a${'x'.repeat(200_000)}`, remote: 'https://user:secret@example.invalid/first', contentRoot: first },
92
+ { name: `b${'x'.repeat(200_000)}`, remote: 'https://user:secret@example.invalid/second', contentRoot: second },
93
+ ] });
94
+ expect(result.kind).toBe('source-ambiguous');
95
+ if (result.kind === 'source-ambiguous') {
96
+ expect(result.candidates.every(candidate => candidate.length <= 128)).toBe(true);
97
+ }
98
+ expect(JSON.stringify(result).length).toBeLessThan(4096);
99
+ expect(JSON.stringify(result)).not.toContain('secret');
100
+ });
101
+ it('bounds malformed-pack diagnostics for an extremely long valid registry name', () => {
102
+ const registryRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-source-malformed-long-'));
103
+ roots.push(registryRoot);
104
+ writePack(registryRoot);
105
+ fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'sensor-packs', 'js-ts', 'pack.json'), '{"schemaVersion":');
106
+ const name = `a${'x'.repeat(200_000)}`;
107
+ let message = '';
108
+ try {
109
+ (0, source_1.resolveSensorSource)(manifest(path_1.default.join(os_1.default.tmpdir(), 'awm-source-malformed-long-missing')), {
110
+ registries: [{ name, remote: 'https://user:secret@example.invalid/registry', contentRoot: registryRoot }],
111
+ });
112
+ }
113
+ catch (error) {
114
+ message = error instanceof Error ? error.message : String(error);
115
+ }
116
+ expect(message).toMatch(/invalid sensor pack/);
117
+ expect(message.length).toBeLessThan(4096);
118
+ expect(message).not.toContain(name);
119
+ expect(message).not.toContain('secret');
120
+ });
121
+ });
@@ -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: [],
@@ -7,6 +7,21 @@ const fs_1 = __importDefault(require("fs"));
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const tmp_1 = require("../../../support/tmp");
9
9
  const resolve_1 = require("../../../../src/commands/sensors/coverage/resolve");
10
+ function resolveCoverageWithoutNoFollow(cwd) {
11
+ let resolve;
12
+ jest.isolateModules(() => {
13
+ jest.doMock('fs', () => {
14
+ const actual = jest.requireActual('fs');
15
+ const constants = { ...actual.constants, O_NOFOLLOW: undefined };
16
+ return { __esModule: true, default: { ...actual, constants }, ...actual, constants };
17
+ });
18
+ resolve = require('../../../../src/commands/sensors/coverage/resolve').resolveCoverageInputs;
19
+ });
20
+ jest.dontMock('fs');
21
+ if (!resolve)
22
+ throw new Error('portable coverage resolver could not be loaded');
23
+ return resolve(cwd);
24
+ }
10
25
  let root;
11
26
  let awmHome;
12
27
  let project;
@@ -29,9 +44,6 @@ const writeManifest = (body) => fs_1.default.writeFileSync(path_1.default.join(p
29
44
  const coverage = { schemaVersion: 1, classes: {
30
45
  formatting: { description: 'Formatting', detectors: [{ sensor: 'format' }], remedy: { summary: 'Add formatter', command: 'npm i -D prettier' } },
31
46
  } };
32
- const noFollowUnavailableOnNativeWindows = process.platform === 'win32'
33
- && typeof fs_1.default.constants.O_NOFOLLOW !== 'number';
34
- const safetyError = /platform cannot guarantee no symlink dereference/;
35
47
  const writePack = (registry, pack, body) => {
36
48
  const dir = path_1.default.join(awmHome, 'registries', registry, 'sensor-packs', pack);
37
49
  fs_1.default.mkdirSync(dir, { recursive: true });
@@ -60,62 +72,52 @@ test('selects the first configured registry containing the exact pack', () => {
60
72
  configure(['first', 'second']);
61
73
  writePack('first', 'generic', { name: 'generic', sensors: {} });
62
74
  writePack('second', 'js-ts', { name: 'js-ts', sensors: {}, coverage });
63
- if (noFollowUnavailableOnNativeWindows) {
64
- expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(safetyError);
65
- }
66
- else {
67
- expect((0, resolve_1.resolveCoverageInputs)(project)).toMatchObject({ kind: 'ready', pack: 'js-ts', registry: 'second' });
68
- }
75
+ expect((0, resolve_1.resolveCoverageInputs)(project)).toMatchObject({ kind: 'ready', pack: 'js-ts', registry: 'second' });
69
76
  });
70
77
  test('registry ordering chooses the earlier exact pack when both registries contain it', () => {
71
78
  writeManifest({ pack: 'js-ts', sensors: {} });
72
79
  configure(['first', 'second']);
73
80
  writePack('first', 'js-ts', { name: 'js-ts', sensors: {}, coverage });
74
81
  writePack('second', 'js-ts', { name: 'js-ts', sensors: {}, coverage });
75
- if (noFollowUnavailableOnNativeWindows) {
76
- expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(safetyError);
77
- }
78
- else {
79
- expect((0, resolve_1.resolveCoverageInputs)(project)).toMatchObject({ kind: 'ready', registry: 'first' });
80
- }
82
+ expect((0, resolve_1.resolveCoverageInputs)(project)).toMatchObject({ kind: 'ready', registry: 'first' });
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
+ });
81
93
  });
82
94
  test('old pack without coverage is no_reference, not covered', () => {
83
95
  writeManifest({ pack: 'js-ts', sensors: {} });
84
96
  configure(['baseline']);
85
97
  writePack('baseline', 'js-ts', { name: 'js-ts', sensors: {} });
86
- if (noFollowUnavailableOnNativeWindows) {
87
- expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(safetyError);
88
- }
89
- else {
90
- expect((0, resolve_1.resolveCoverageInputs)(project)).toMatchObject({ kind: 'no_reference', pack: 'js-ts', registry: 'baseline' });
91
- }
98
+ expect((0, resolve_1.resolveCoverageInputs)(project)).toMatchObject({ kind: 'no_reference', pack: 'js-ts', registry: 'baseline' });
92
99
  });
93
- test('native Windows without no-follow support fails closed before resolving normal coverage inputs', () => {
100
+ test('normal regular coverage inputs resolve regardless of no-follow availability', () => {
94
101
  writeManifest({ pack: 'js-ts', sensors: {} });
95
102
  configure(['baseline']);
96
103
  writePack('baseline', 'js-ts', { name: 'js-ts', sensors: {}, coverage });
97
- if (noFollowUnavailableOnNativeWindows) {
98
- expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(safetyError);
99
- }
100
- else {
101
- expect((0, resolve_1.resolveCoverageInputs)(project)).toMatchObject({ kind: 'ready' });
102
- }
104
+ expect(resolveCoverageWithoutNoFollow(project)).toMatchObject({ kind: 'ready' });
103
105
  });
104
106
  test.each([
105
107
  ['manifest-malformed', '{broken', /Invalid JSON.*sensors\.json/],
106
108
  ['manifest-oversize', Buffer.alloc(1024 * 1024 + 1), /sensors\.json.*exceeds 1 MiB/],
107
109
  ])('rejects %s', (_name, body, expected) => {
108
110
  writeManifest(body);
109
- expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(noFollowUnavailableOnNativeWindows && _name !== 'manifest-oversize' ? safetyError : expected);
111
+ expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(expected);
110
112
  });
111
113
  test.each([
112
114
  ['pack-malformed', '{broken', /Invalid JSON.*pack\.json/],
113
- ['pack-oversize', Buffer.alloc(1024 * 1024 + 1), /pack.*pack\.json.*exceeds the 1 MiB limit/],
115
+ ['pack-oversize', Buffer.alloc(1024 * 1024 + 1), /pack source exceeds the 1 MiB limit/],
114
116
  ])('rejects %s', (_name, body, expected) => {
115
117
  writeManifest({ pack: 'js-ts', sensors: {} });
116
118
  configure(['baseline']);
117
119
  writePack('baseline', 'js-ts', body);
118
- expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(noFollowUnavailableOnNativeWindows ? safetyError : expected);
120
+ expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(expected);
119
121
  });
120
122
  test('rejects a symlinked manifest without dereferencing it', () => {
121
123
  const manifest = path_1.default.join(project, '.awm', 'sensors.json');
@@ -133,12 +135,12 @@ test('rejects a JSON object that is not a valid pack', () => {
133
135
  writeManifest({ pack: 'js-ts', sensors: {} });
134
136
  configure(['baseline']);
135
137
  writePack('baseline', 'js-ts', { coverage });
136
- expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(noFollowUnavailableOnNativeWindows ? safetyError : /Invalid sensor pack.*name/);
138
+ expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(/Invalid sensor pack.*name/);
137
139
  });
138
140
  test('rejects registry names that are not safe path components', () => {
139
141
  writeManifest({ pack: 'js-ts', sensors: {} });
140
142
  configure(['']);
141
- expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(noFollowUnavailableOnNativeWindows ? safetyError : /malformed entry name|Invalid registry name/);
143
+ expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(/malformed entry name|Invalid registry name/);
142
144
  });
143
145
  test.each([
144
146
  [{ schemaVersion: 3, name: 'js-ts', sensors: {}, coverage: {} }, /supported: legacy, 2/],
@@ -147,5 +149,5 @@ test.each([
147
149
  writeManifest({ pack: 'js-ts', sensors: {} });
148
150
  configure(['baseline']);
149
151
  writePack('baseline', 'js-ts', pack);
150
- expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(noFollowUnavailableOnNativeWindows ? safetyError : expected);
152
+ expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(expected);
151
153
  });