agentic-workflow-manager 9.4.1 → 9.5.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.
@@ -7,6 +7,36 @@ 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
+ function resolveWithoutNoFollow(...args) {
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/compatibility/pack-source').resolvePackSource;
19
+ });
20
+ jest.dontMock('fs');
21
+ if (!resolve)
22
+ throw new Error('portable pack source resolver could not be loaded');
23
+ return resolve(...args);
24
+ }
25
+ function resolveWithoutNoFollowWithExactStats(...args) {
26
+ let resolve;
27
+ jest.isolateModules(() => {
28
+ jest.doMock('fs', () => {
29
+ const actual = jest.requireActual('fs');
30
+ const constants = { ...actual.constants, O_NOFOLLOW: undefined };
31
+ return { __esModule: true, default: { ...actual, constants }, ...actual, constants };
32
+ });
33
+ resolve = require('../../../../src/commands/sensors/compatibility/pack-source').resolvePackSource;
34
+ });
35
+ jest.dontMock('fs');
36
+ if (!resolve)
37
+ throw new Error('portable exact-stat pack source resolver could not be loaded');
38
+ return resolve(...args);
39
+ }
10
40
  describe('resolvePackSource', () => {
11
41
  it('uses the first registry containing the exact contained pack file', () => {
12
42
  const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-'));
@@ -52,4 +82,190 @@ describe('resolvePackSource', () => {
52
82
  fs_1.default.rmSync(root, { recursive: true, force: true });
53
83
  }
54
84
  });
85
+ it('rejects a symlinked registry content root', () => {
86
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-root-link-'));
87
+ const target = path_1.default.join(root, 'target');
88
+ const linked = path_1.default.join(root, 'linked');
89
+ try {
90
+ fs_1.default.mkdirSync(path_1.default.join(target, 'sensor-packs', 'js-ts'), { recursive: true });
91
+ fs_1.default.writeFileSync(path_1.default.join(target, 'sensor-packs', 'js-ts', 'pack.json'), '{}');
92
+ fs_1.default.symlinkSync(target, linked, 'dir');
93
+ expect(() => (0, pack_source_1.resolvePackSource)('js-ts', { registries: [{ name: 'linked', remote: '', contentRoot: linked }] }))
94
+ .toThrow(/symbolic|symlink/i);
95
+ }
96
+ finally {
97
+ fs_1.default.rmSync(root, { recursive: true, force: true });
98
+ }
99
+ });
100
+ it('opens the resolved pack with no-follow semantics before reading it', () => {
101
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-nofollow-'));
102
+ const open = jest.spyOn(fs_1.default, 'openSync');
103
+ try {
104
+ const packFile = path_1.default.join(root, 'sensor-packs', 'js-ts', 'pack.json');
105
+ fs_1.default.mkdirSync(path_1.default.dirname(packFile), { recursive: true });
106
+ fs_1.default.writeFileSync(packFile, '{}');
107
+ (0, pack_source_1.resolvePackSource)('js-ts', { registries: [{ name: 'safe', remote: '', contentRoot: root }] });
108
+ const flags = open.mock.calls[0]?.[1];
109
+ expect(typeof flags).toBe('number');
110
+ if (typeof fs_1.default.constants.O_NOFOLLOW === 'number') {
111
+ expect(flags & fs_1.default.constants.O_NOFOLLOW).toBe(fs_1.default.constants.O_NOFOLLOW);
112
+ }
113
+ else {
114
+ expect(flags).toBe(fs_1.default.constants.O_RDONLY);
115
+ }
116
+ }
117
+ finally {
118
+ open.mockRestore();
119
+ fs_1.default.rmSync(root, { recursive: true, force: true });
120
+ }
121
+ });
122
+ it('resolves an inspected regular pack when no-follow is unavailable', () => {
123
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-portable-open-'));
124
+ try {
125
+ const packFile = path_1.default.join(root, 'sensor-packs', 'js-ts', 'pack.json');
126
+ fs_1.default.mkdirSync(path_1.default.dirname(packFile), { recursive: true });
127
+ fs_1.default.writeFileSync(packFile, '{"from":"portable"}');
128
+ expect(resolveWithoutNoFollow('js-ts', { registries: [{ name: 'safe', remote: '', contentRoot: root }] }).content)
129
+ .toBe('{"from":"portable"}');
130
+ }
131
+ finally {
132
+ fs_1.default.rmSync(root, { recursive: true, force: true });
133
+ }
134
+ });
135
+ it('uses exact bigint file identities for the portable safe-open fallback', () => {
136
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-bigint-'));
137
+ const lstat = jest.spyOn(fs_1.default, 'lstatSync');
138
+ const fstat = jest.spyOn(fs_1.default, 'fstatSync');
139
+ try {
140
+ const packFile = path_1.default.join(root, 'sensor-packs', 'js-ts', 'pack.json');
141
+ fs_1.default.mkdirSync(path_1.default.dirname(packFile), { recursive: true });
142
+ fs_1.default.writeFileSync(packFile, '{"from":"bigint"}');
143
+ expect(resolveWithoutNoFollowWithExactStats('js-ts', { registries: [{ name: 'safe', remote: '', contentRoot: root }] }).content)
144
+ .toBe('{"from":"bigint"}');
145
+ expect(lstat).toHaveBeenCalledWith(expect.any(String), { bigint: true });
146
+ expect(fstat).toHaveBeenCalledWith(expect.any(Number), { bigint: true });
147
+ }
148
+ finally {
149
+ lstat.mockRestore();
150
+ fstat.mockRestore();
151
+ fs_1.default.rmSync(root, { recursive: true, force: true });
152
+ }
153
+ });
154
+ it('rejects a parent-directory swap between inspection and no-follow open', () => {
155
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-parent-swap-'));
156
+ const packDir = path_1.default.join(root, 'sensor-packs', 'js-ts');
157
+ const movedPackDir = path_1.default.join(root, 'sensor-packs', 'js-ts-original');
158
+ const packFile = path_1.default.join(packDir, 'pack.json');
159
+ const originalOpen = fs_1.default.openSync.bind(fs_1.default);
160
+ const open = jest.spyOn(fs_1.default, 'openSync');
161
+ let swapped = false;
162
+ try {
163
+ fs_1.default.mkdirSync(packDir, { recursive: true });
164
+ fs_1.default.writeFileSync(packFile, '{"from":"original"}');
165
+ open.mockImplementation(((file, flags, mode) => {
166
+ if (!swapped && file === fs_1.default.realpathSync(packFile)) {
167
+ swapped = true;
168
+ fs_1.default.renameSync(packDir, movedPackDir);
169
+ fs_1.default.mkdirSync(packDir, { recursive: true });
170
+ fs_1.default.writeFileSync(packFile, '{"from":"replacement"}');
171
+ }
172
+ return originalOpen(file, flags, mode);
173
+ }));
174
+ expect(() => (0, pack_source_1.resolvePackSource)('js-ts', { registries: [{ name: 'safe', remote: '', contentRoot: root }] }))
175
+ .toThrow(/changed|identity|regular|safe/i);
176
+ }
177
+ finally {
178
+ open.mockRestore();
179
+ fs_1.default.rmSync(root, { recursive: true, force: true });
180
+ }
181
+ });
182
+ it('accepts a parent symlink replacement when it resolves to the inspected pack inode', () => {
183
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-same-inode-'));
184
+ const packDir = path_1.default.join(root, 'sensor-packs', 'js-ts');
185
+ const movedPackDir = path_1.default.join(root, 'sensor-packs', 'js-ts-original');
186
+ const packFile = path_1.default.join(packDir, 'pack.json');
187
+ const originalOpen = fs_1.default.openSync.bind(fs_1.default);
188
+ const open = jest.spyOn(fs_1.default, 'openSync');
189
+ let swapped = false;
190
+ try {
191
+ fs_1.default.mkdirSync(packDir, { recursive: true });
192
+ fs_1.default.writeFileSync(packFile, '{"from":"original"}');
193
+ open.mockImplementation(((file, flags, mode) => {
194
+ if (!swapped && file === fs_1.default.realpathSync(packFile)) {
195
+ swapped = true;
196
+ fs_1.default.renameSync(packDir, movedPackDir);
197
+ fs_1.default.symlinkSync(movedPackDir, packDir, 'dir');
198
+ }
199
+ return originalOpen(file, flags, mode);
200
+ }));
201
+ expect((0, pack_source_1.resolvePackSource)('js-ts', { registries: [{ name: 'safe', remote: '', contentRoot: root }] }).content)
202
+ .toBe('{"from":"original"}');
203
+ }
204
+ finally {
205
+ open.mockRestore();
206
+ fs_1.default.rmSync(root, { recursive: true, force: true });
207
+ }
208
+ });
209
+ it('rejects same-inode content growth between inspection and open', () => {
210
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-size-race-'));
211
+ const packFile = path_1.default.join(root, 'sensor-packs', 'js-ts', 'pack.json');
212
+ const originalOpen = fs_1.default.openSync.bind(fs_1.default);
213
+ const open = jest.spyOn(fs_1.default, 'openSync');
214
+ let changed = false;
215
+ try {
216
+ fs_1.default.mkdirSync(path_1.default.dirname(packFile), { recursive: true });
217
+ fs_1.default.writeFileSync(packFile, '{"from":"original"}');
218
+ open.mockImplementation(((file, flags, mode) => {
219
+ if (!changed && file === fs_1.default.realpathSync(packFile)) {
220
+ changed = true;
221
+ fs_1.default.appendFileSync(packFile, 'x');
222
+ }
223
+ return originalOpen(file, flags, mode);
224
+ }));
225
+ expect(() => resolveWithoutNoFollow('js-ts', { registries: [{ name: 'safe', remote: '', contentRoot: root }] }))
226
+ .toThrow(/changed|size|identity/i);
227
+ }
228
+ finally {
229
+ open.mockRestore();
230
+ fs_1.default.rmSync(root, { recursive: true, force: true });
231
+ }
232
+ });
233
+ it('rejects same-inode truncation after safe open before reading', () => {
234
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-truncate-race-'));
235
+ const packFile = path_1.default.join(root, 'sensor-packs', 'js-ts', 'pack.json');
236
+ const originalRead = fs_1.default.readSync.bind(fs_1.default);
237
+ const read = jest.spyOn(fs_1.default, 'readSync');
238
+ let truncated = false;
239
+ try {
240
+ fs_1.default.mkdirSync(path_1.default.dirname(packFile), { recursive: true });
241
+ fs_1.default.writeFileSync(packFile, '{"from":"original"}');
242
+ read.mockImplementation(((descriptor, buffer, offset, length, position) => {
243
+ if (!truncated) {
244
+ truncated = true;
245
+ fs_1.default.truncateSync(packFile, 0);
246
+ }
247
+ return originalRead(descriptor, buffer, offset, length, position);
248
+ }));
249
+ expect(() => resolveWithoutNoFollow('js-ts', { registries: [{ name: 'safe', remote: '', contentRoot: root }] }))
250
+ .toThrow(/changed|size|identity/i);
251
+ }
252
+ finally {
253
+ read.mockRestore();
254
+ fs_1.default.rmSync(root, { recursive: true, force: true });
255
+ }
256
+ });
257
+ it('bounds diagnostics when a long content root cannot be inspected', () => {
258
+ const contentRoot = path_1.default.join(os_1.default.tmpdir(), `awm-${'x'.repeat(200_000)}`);
259
+ let message = '';
260
+ try {
261
+ (0, pack_source_1.resolvePackSource)('js-ts', { registries: [{ name: 'safe', remote: 'https://user:secret@example.invalid/registry', contentRoot }] });
262
+ }
263
+ catch (error) {
264
+ message = error instanceof Error ? error.message : String(error);
265
+ }
266
+ expect(message.length).toBeLessThan(4096);
267
+ expect(message).not.toContain(contentRoot);
268
+ expect(message).not.toContain('secret');
269
+ expect(message).toMatch(/inspect|source|registry/i);
270
+ });
55
271
  });
@@ -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
+ });
@@ -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,42 @@ 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' });
81
83
  });
82
84
  test('old pack without coverage is no_reference, not covered', () => {
83
85
  writeManifest({ pack: 'js-ts', sensors: {} });
84
86
  configure(['baseline']);
85
87
  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
- }
88
+ expect((0, resolve_1.resolveCoverageInputs)(project)).toMatchObject({ kind: 'no_reference', pack: 'js-ts', registry: 'baseline' });
92
89
  });
93
- test('native Windows without no-follow support fails closed before resolving normal coverage inputs', () => {
90
+ test('normal regular coverage inputs resolve regardless of no-follow availability', () => {
94
91
  writeManifest({ pack: 'js-ts', sensors: {} });
95
92
  configure(['baseline']);
96
93
  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
- }
94
+ expect(resolveCoverageWithoutNoFollow(project)).toMatchObject({ kind: 'ready' });
103
95
  });
104
96
  test.each([
105
97
  ['manifest-malformed', '{broken', /Invalid JSON.*sensors\.json/],
106
98
  ['manifest-oversize', Buffer.alloc(1024 * 1024 + 1), /sensors\.json.*exceeds 1 MiB/],
107
99
  ])('rejects %s', (_name, body, expected) => {
108
100
  writeManifest(body);
109
- expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(noFollowUnavailableOnNativeWindows && _name !== 'manifest-oversize' ? safetyError : expected);
101
+ expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(expected);
110
102
  });
111
103
  test.each([
112
104
  ['pack-malformed', '{broken', /Invalid JSON.*pack\.json/],
113
- ['pack-oversize', Buffer.alloc(1024 * 1024 + 1), /pack.*pack\.json.*exceeds the 1 MiB limit/],
105
+ ['pack-oversize', Buffer.alloc(1024 * 1024 + 1), /pack source exceeds the 1 MiB limit/],
114
106
  ])('rejects %s', (_name, body, expected) => {
115
107
  writeManifest({ pack: 'js-ts', sensors: {} });
116
108
  configure(['baseline']);
117
109
  writePack('baseline', 'js-ts', body);
118
- expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(noFollowUnavailableOnNativeWindows ? safetyError : expected);
110
+ expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(expected);
119
111
  });
120
112
  test('rejects a symlinked manifest without dereferencing it', () => {
121
113
  const manifest = path_1.default.join(project, '.awm', 'sensors.json');
@@ -133,12 +125,12 @@ test('rejects a JSON object that is not a valid pack', () => {
133
125
  writeManifest({ pack: 'js-ts', sensors: {} });
134
126
  configure(['baseline']);
135
127
  writePack('baseline', 'js-ts', { coverage });
136
- expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(noFollowUnavailableOnNativeWindows ? safetyError : /Invalid sensor pack.*name/);
128
+ expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(/Invalid sensor pack.*name/);
137
129
  });
138
130
  test('rejects registry names that are not safe path components', () => {
139
131
  writeManifest({ pack: 'js-ts', sensors: {} });
140
132
  configure(['']);
141
- expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(noFollowUnavailableOnNativeWindows ? safetyError : /malformed entry name|Invalid registry name/);
133
+ expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(/malformed entry name|Invalid registry name/);
142
134
  });
143
135
  test.each([
144
136
  [{ schemaVersion: 3, name: 'js-ts', sensors: {}, coverage: {} }, /supported: legacy, 2/],
@@ -147,5 +139,5 @@ test.each([
147
139
  writeManifest({ pack: 'js-ts', sensors: {} });
148
140
  configure(['baseline']);
149
141
  writePack('baseline', 'js-ts', pack);
150
- expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(noFollowUnavailableOnNativeWindows ? safetyError : expected);
142
+ expect(() => (0, resolve_1.resolveCoverageInputs)(project)).toThrow(expected);
151
143
  });
@@ -0,0 +1,102 @@
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 projectModule = '../../../src/commands/sensors/project';
10
+ function tempDir() {
11
+ return fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-sensor-project-'));
12
+ }
13
+ function writeJson(file, value) {
14
+ fs_1.default.mkdirSync(path_1.default.dirname(file), { recursive: true });
15
+ fs_1.default.writeFileSync(file, JSON.stringify(value));
16
+ }
17
+ function v3Manifest(packageRoot) {
18
+ return {
19
+ schemaVersion: 3,
20
+ mode: 'project-sensors',
21
+ pack: 'js-ts',
22
+ source: { registry: 'baseline' },
23
+ ...(packageRoot ? { packageRoot } : {}),
24
+ sensors: {},
25
+ };
26
+ }
27
+ describe('sensor project authority', () => {
28
+ const roots = [];
29
+ afterEach(() => { for (const root of roots.splice(0))
30
+ fs_1.default.rmSync(root, { recursive: true, force: true }); });
31
+ function resolve(startCwd) {
32
+ return require(projectModule).resolveSensorProject(startCwd);
33
+ }
34
+ it('selects the nearest regular manifest without crossing the Git boundary', () => {
35
+ const root = tempDir();
36
+ roots.push(root);
37
+ fs_1.default.mkdirSync(path_1.default.join(root, '.git'));
38
+ writeJson(path_1.default.join(root, '.awm/sensors.json'), v3Manifest());
39
+ writeJson(path_1.default.join(root, 'packages/app/.awm/sensors.json'), v3Manifest());
40
+ fs_1.default.mkdirSync(path_1.default.join(root, 'packages/app/src'), { recursive: true });
41
+ const result = resolve(path_1.default.join(root, 'packages/app/src'));
42
+ expect(result).toMatchObject({ state: 'configured', projectRoot: path_1.default.join(root, 'packages/app'), manifestPath: path_1.default.join(root, 'packages/app/.awm/sensors.json') });
43
+ });
44
+ it('treats a .git file as a worktree boundary and rejects an escaping packageRoot', () => {
45
+ const root = tempDir();
46
+ roots.push(root);
47
+ fs_1.default.writeFileSync(path_1.default.join(root, '.git'), 'gitdir: /elsewhere');
48
+ writeJson(path_1.default.join(root, '.awm/sensors.json'), v3Manifest('../outside'));
49
+ fs_1.default.mkdirSync(path_1.default.join(root, 'cli/src'), { recursive: true });
50
+ expect(resolve(path_1.default.join(root, 'cli/src'))).toMatchObject({ state: 'invalid', projectRoot: root });
51
+ });
52
+ it('inspects only the exact CWD outside Git and returns missing for absent or malformed manifests', () => {
53
+ const parent = tempDir();
54
+ roots.push(parent);
55
+ writeJson(path_1.default.join(parent, '.awm/sensors.json'), v3Manifest());
56
+ const cwd = path_1.default.join(parent, 'child');
57
+ fs_1.default.mkdirSync(cwd);
58
+ expect(resolve(cwd)).toMatchObject({ state: 'missing', projectRoot: cwd });
59
+ fs_1.default.mkdirSync(path_1.default.join(cwd, '.awm'));
60
+ fs_1.default.writeFileSync(path_1.default.join(cwd, '.awm/sensors.json'), '{bad json');
61
+ expect(resolve(cwd)).toMatchObject({ state: 'invalid', projectRoot: cwd });
62
+ });
63
+ it('rejects a symlinked manifest and a packageRoot that is not a directory', () => {
64
+ const root = tempDir();
65
+ roots.push(root);
66
+ fs_1.default.mkdirSync(path_1.default.join(root, '.git'));
67
+ fs_1.default.mkdirSync(path_1.default.join(root, '.awm'), { recursive: true });
68
+ writeJson(path_1.default.join(root, 'elsewhere.json'), v3Manifest());
69
+ fs_1.default.symlinkSync(path_1.default.join(root, 'elsewhere.json'), path_1.default.join(root, '.awm/sensors.json'));
70
+ expect(resolve(root)).toMatchObject({ state: 'invalid', reason: expect.stringMatching(/regular file/) });
71
+ fs_1.default.unlinkSync(path_1.default.join(root, '.awm/sensors.json'));
72
+ fs_1.default.symlinkSync(path_1.default.join(root, 'missing.json'), path_1.default.join(root, '.awm/sensors.json'));
73
+ expect(resolve(root)).toMatchObject({ state: 'invalid', reason: expect.stringMatching(/regular file/) });
74
+ fs_1.default.unlinkSync(path_1.default.join(root, '.awm/sensors.json'));
75
+ writeJson(path_1.default.join(root, '.awm/sensors.json'), v3Manifest('not-a-directory'));
76
+ fs_1.default.writeFileSync(path_1.default.join(root, 'not-a-directory'), 'file');
77
+ expect(resolve(root)).toMatchObject({ state: 'invalid', reason: expect.stringMatching(/existing directory/) });
78
+ writeJson(path_1.default.join(root, '.awm/sensors.json'), v3Manifest('missing-directory'));
79
+ expect(resolve(root)).toMatchObject({ state: 'invalid', reason: expect.stringMatching(/existing directory/) });
80
+ });
81
+ it('rejects invalid UTF-8 and packageRoot symlinks escaping the manifest directory', () => {
82
+ const root = tempDir();
83
+ roots.push(root);
84
+ fs_1.default.mkdirSync(path_1.default.join(root, '.git'));
85
+ fs_1.default.mkdirSync(path_1.default.join(root, '.awm'));
86
+ fs_1.default.writeFileSync(path_1.default.join(root, '.awm/sensors.json'), Buffer.from([0xff, 0xfe]));
87
+ expect(resolve(root)).toMatchObject({ state: 'invalid', reason: expect.stringMatching(/UTF-8/) });
88
+ writeJson(path_1.default.join(root, '.awm/sensors.json'), v3Manifest('linked-package'));
89
+ const outside = tempDir();
90
+ roots.push(outside);
91
+ fs_1.default.symlinkSync(outside, path_1.default.join(root, 'linked-package'));
92
+ expect(resolve(root)).toMatchObject({ state: 'invalid', reason: expect.stringMatching(/escapes/) });
93
+ });
94
+ it('never selects a parent manifest above the Git boundary', () => {
95
+ const parent = tempDir();
96
+ roots.push(parent);
97
+ writeJson(path_1.default.join(parent, '.awm/sensors.json'), v3Manifest());
98
+ const root = path_1.default.join(parent, 'repository');
99
+ fs_1.default.mkdirSync(path_1.default.join(root, '.git'), { recursive: true });
100
+ expect(resolve(root)).toMatchObject({ state: 'missing', projectRoot: root });
101
+ });
102
+ });