agentic-workflow-manager 8.0.0 → 8.1.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.
@@ -3,13 +3,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseSemgrepPolicy = parseSemgrepPolicy;
7
+ exports.resolveSemgrepPolicy = resolveSemgrepPolicy;
6
8
  exports.parseStructuredCommand = parseStructuredCommand;
7
9
  exports.assertNoEqualPriorityOverlap = assertNoEqualPriorityOverlap;
8
10
  exports.parseSensorPack = parseSensorPack;
9
11
  const semver_1 = __importDefault(require("semver"));
12
+ const fs_1 = __importDefault(require("fs"));
13
+ const path_1 = __importDefault(require("path"));
10
14
  const contract_1 = require("../coverage/contract");
11
15
  const PACK_SCHEMA_VERSION = 2;
12
16
  const SHELL_EXECUTABLES = new Set(['sh', 'bash', 'cmd', 'powershell']);
17
+ const PACKAGE_MANAGERS = new Set(['npm', 'pnpm', 'yarn', 'bun']);
13
18
  const ALLOWED_PROBES = new Set([
14
19
  'version',
15
20
  'eslint-print-config',
@@ -18,6 +23,9 @@ const ALLOWED_PROBES = new Set([
18
23
  'package-script-present',
19
24
  'config-present',
20
25
  ]);
26
+ function normalizedPackageManager(value) {
27
+ return value.toLowerCase().replace(/\.exe$/, '');
28
+ }
21
29
  function isRecord(value) {
22
30
  return typeof value === 'object' && value !== null && !Array.isArray(value);
23
31
  }
@@ -62,9 +70,72 @@ function stringArray(value, source, location) {
62
70
  invalid(source, `${location} must be a nonempty array`);
63
71
  return value.map((item, index) => text(item, source, `${location}[${index}]`));
64
72
  }
73
+ function assetArray(value, source, location, allowEmpty = false) {
74
+ if (!Array.isArray(value) || (!allowEmpty && value.length === 0))
75
+ invalid(source, `${location} must be a ${allowEmpty ? '' : 'nonempty '}array`);
76
+ return value.map((item, index) => asset(item, source, `${location}[${index}]`));
77
+ }
78
+ const SEMGREP_POLICY_REF = 'shared/semgrep-policy.json';
79
+ const MAX_POLICY_BYTES = 64 * 1024;
80
+ function contained(root, candidate) {
81
+ const prefix = root.endsWith(path_1.default.sep) ? root : root + path_1.default.sep;
82
+ return candidate.startsWith(prefix);
83
+ }
84
+ function parseSemgrepPolicy(input, source) {
85
+ const value = record(input, source, 'Semgrep policy');
86
+ fields(value, ['tool', 'toolRange', 'runtime', 'runtimeRange', 'probe'], source, 'Semgrep policy');
87
+ if (value.tool !== 'semgrep' || value.runtime !== 'python' || value.probe !== 'semgrep-validate') {
88
+ invalid(source, 'Semgrep policy must declare semgrep, python, and semgrep-validate');
89
+ }
90
+ const toolRange = text(value.toolRange, source, 'Semgrep policy.toolRange');
91
+ const runtimeRange = text(value.runtimeRange, source, 'Semgrep policy.runtimeRange');
92
+ if (semver_1.default.validRange(toolRange) === null || semver_1.default.validRange(runtimeRange) === null)
93
+ invalid(source, 'Semgrep policy ranges must be valid semver ranges');
94
+ return { tool: 'semgrep', toolRange, runtime: 'python', runtimeRange, probe: 'semgrep-validate' };
95
+ }
96
+ function resolveSemgrepPolicy(policyRef, source, location = 'policy') {
97
+ if (policyRef !== SEMGREP_POLICY_REF)
98
+ invalid(source, `${location}.policyRef must be the contained AWM-owned ${SEMGREP_POLICY_REF}`);
99
+ if (typeof source !== 'string' || !path_1.default.isAbsolute(source) || path_1.default.normalize(source) !== source || path_1.default.basename(source) !== 'pack.json') {
100
+ invalid(source, `${location}.policyRef requires an absolute pack.json source`);
101
+ }
102
+ const packRoot = path_1.default.dirname(source);
103
+ const sensorPacksRoot = path_1.default.dirname(packRoot);
104
+ const candidate = path_1.default.join(sensorPacksRoot, ...SEMGREP_POLICY_REF.split('/'));
105
+ if (!contained(sensorPacksRoot, candidate))
106
+ invalid(source, `${location}.policyRef escapes sensor-packs root`);
107
+ let stat;
108
+ try {
109
+ stat = fs_1.default.lstatSync(candidate);
110
+ }
111
+ catch {
112
+ invalid(source, `${location}.policyRef does not resolve to a regular policy file`);
113
+ }
114
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_POLICY_BYTES)
115
+ invalid(source, `${location}.policyRef must resolve to a bounded regular policy file`);
116
+ let realRoot;
117
+ let realPolicy;
118
+ try {
119
+ realRoot = fs_1.default.realpathSync(sensorPacksRoot);
120
+ realPolicy = fs_1.default.realpathSync(candidate);
121
+ }
122
+ catch {
123
+ invalid(source, `${location}.policyRef cannot be canonicalized`);
124
+ }
125
+ if (!contained(realRoot, realPolicy))
126
+ invalid(source, `${location}.policyRef escapes sensor-packs root`);
127
+ let parsed;
128
+ try {
129
+ parsed = JSON.parse(fs_1.default.readFileSync(realPolicy, 'utf8'));
130
+ }
131
+ catch {
132
+ invalid(source, `${location}.policyRef must contain valid JSON`);
133
+ }
134
+ return parseSemgrepPolicy(parsed, realPolicy);
135
+ }
65
136
  function parseStructuredCommand(input, source) {
66
137
  const value = record(input, source, 'command');
67
- fields(value, ['executable', 'resolution', 'args', 'fileInput'], source, 'command');
138
+ fields(value, ['executable', 'resolution', 'args', 'packageManager', 'environment', 'fileInput', 'pythonEnvironmentRoot'], source, 'command');
68
139
  const executable = text(value.executable, source, 'command.executable');
69
140
  if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(executable) || SHELL_EXECUTABLES.has(executable.toLowerCase().replace(/\.exe$/, ''))) {
70
141
  invalid(source, 'command.executable must not be a shell or path');
@@ -78,6 +149,29 @@ function parseStructuredCommand(input, source) {
78
149
  invalid(source, `command.args[${index}] must not embed {files}`);
79
150
  }
80
151
  const command = { executable, resolution: value.resolution, args };
152
+ if ('pythonEnvironmentRoot' in value) {
153
+ if (value.resolution !== 'python-environment' || (value.pythonEnvironmentRoot !== '.venv' && value.pythonEnvironmentRoot !== 'venv'))
154
+ invalid(source, 'command.pythonEnvironmentRoot must name the selected .venv or venv Python environment');
155
+ command.pythonEnvironmentRoot = value.pythonEnvironmentRoot;
156
+ }
157
+ const executablePackageManager = normalizedPackageManager(executable);
158
+ if (PACKAGE_MANAGERS.has(executablePackageManager) && !('packageManager' in value))
159
+ invalid(source, 'command.packageManager is required for a package-manager executable');
160
+ if ('packageManager' in value) {
161
+ if (typeof value.packageManager !== 'string' || !PACKAGE_MANAGERS.has(normalizedPackageManager(value.packageManager)))
162
+ invalid(source, 'command.packageManager must be npm, pnpm, yarn, or bun');
163
+ const packageManager = normalizedPackageManager(value.packageManager);
164
+ if (packageManager !== executablePackageManager)
165
+ invalid(source, 'command.packageManager must match executable');
166
+ command.packageManager = packageManager;
167
+ }
168
+ if ('environment' in value) {
169
+ const environment = record(value.environment, source, 'command.environment');
170
+ fields(environment, ['ESLINT_USE_FLAT_CONFIG'], source, 'command.environment');
171
+ if ((environment.ESLINT_USE_FLAT_CONFIG !== 'true' && environment.ESLINT_USE_FLAT_CONFIG !== 'false') || Object.keys(environment).length !== 1)
172
+ invalid(source, 'command.environment must be the exact allowlisted ESLINT_USE_FLAT_CONFIG=true or false mapping');
173
+ command.environment = { ESLINT_USE_FLAT_CONFIG: environment.ESLINT_USE_FLAT_CONFIG };
174
+ }
81
175
  if ('fileInput' in value) {
82
176
  const fileInput = record(value.fileInput, source, 'command.fileInput');
83
177
  fields(fileInput, ['placeholder', 'extensions'], source, 'command.fileInput');
@@ -99,40 +193,61 @@ function parseStructuredCommand(input, source) {
99
193
  }
100
194
  function parseVariant(input, source, location) {
101
195
  const value = record(input, source, location);
102
- fields(value, ['id', 'priority', 'requirements', 'certifiedRange', 'command', 'assets', 'formatter', 'probe'], source, location);
196
+ fields(value, ['id', 'priority', 'requirements', 'certifiedRange', 'command', 'assets', 'formatter', 'probe', 'policyRef'], source, location);
103
197
  const certifiedRange = text(value.certifiedRange, source, `${location}.certifiedRange`);
104
198
  if (semver_1.default.validRange(certifiedRange) === null)
105
199
  invalid(source, `${location}.certifiedRange must be a valid semver range`);
106
200
  if (typeof value.priority !== 'number' || !Number.isSafeInteger(value.priority))
107
201
  invalid(source, `${location}.priority must be a safe integer`);
108
- const requirements = record(value.requirements, source, `${location}.requirements`);
109
- fields(requirements, ['tool', 'toolRange', 'runtime', 'runtimeRange', 'configFiles'], source, `${location}.requirements`);
110
- const toolRange = text(requirements.toolRange, source, `${location}.requirements.toolRange`);
111
- const runtimeRange = text(requirements.runtimeRange, source, `${location}.requirements.runtimeRange`);
202
+ const policy = 'policyRef' in value ? resolveSemgrepPolicy(value.policyRef, source, location) : undefined;
203
+ if (policy && ('requirements' in value || 'probe' in value))
204
+ invalid(source, `${location}.policyRef is authoritative; requirements and probe must not duplicate it`);
205
+ if (!policy && (!('requirements' in value) || !('probe' in value)))
206
+ invalid(source, `${location} requires requirements and probe when policyRef is absent`);
207
+ const requirements = policy ? undefined : record(value.requirements, source, `${location}.requirements`);
208
+ if (requirements)
209
+ fields(requirements, ['tool', 'toolRange', 'runtime', 'runtimeRange', 'configFiles'], source, `${location}.requirements`);
210
+ const toolRange = policy?.toolRange ?? text(requirements.toolRange, source, `${location}.requirements.toolRange`);
211
+ const runtimeRange = policy?.runtimeRange ?? text(requirements.runtimeRange, source, `${location}.requirements.runtimeRange`);
112
212
  if (semver_1.default.validRange(toolRange) === null || semver_1.default.validRange(runtimeRange) === null)
113
213
  invalid(source, `${location}.requirements ranges must be valid semver ranges`);
114
- const probe = record(value.probe, source, `${location}.probe`);
115
- fields(probe, ['kind'], source, `${location}.probe`);
116
- if (typeof probe.kind !== 'string' || !ALLOWED_PROBES.has(probe.kind))
117
- invalid(source, `${location}.probe.kind must be an allowed probe`);
214
+ const probe = policy ? undefined : record(value.probe, source, `${location}.probe`);
215
+ if (probe) {
216
+ fields(probe, ['kind'], source, `${location}.probe`);
217
+ if (typeof probe.kind !== 'string' || !ALLOWED_PROBES.has(probe.kind))
218
+ invalid(source, `${location}.probe.kind must be an allowed probe`);
219
+ }
118
220
  return {
119
221
  id: id(value.id, source, `${location}.id`),
120
222
  priority: value.priority,
121
223
  certifiedRange,
122
- requirements: { tool: text(requirements.tool, source, `${location}.requirements.tool`), toolRange, runtime: text(requirements.runtime, source, `${location}.requirements.runtime`), runtimeRange, ...('configFiles' in requirements ? { configFiles: stringArray(requirements.configFiles, source, `${location}.requirements.configFiles`).map((file, index) => asset(file, source, `${location}.requirements.configFiles[${index}]`)) } : {}) },
123
- assets: stringArray(value.assets, source, `${location}.assets`).map((entry, index) => asset(entry, source, `${location}.assets[${index}]`)),
224
+ requirements: policy
225
+ ? { tool: policy.tool, toolRange, runtime: policy.runtime, runtimeRange }
226
+ : { tool: text(requirements.tool, source, `${location}.requirements.tool`), toolRange, runtime: text(requirements.runtime, source, `${location}.requirements.runtime`), runtimeRange, ...('configFiles' in requirements ? { configFiles: stringArray(requirements.configFiles, source, `${location}.requirements.configFiles`).map((file, index) => asset(file, source, `${location}.requirements.configFiles[${index}]`)) } : {}) },
227
+ assets: assetArray(value.assets, source, `${location}.assets`, true),
124
228
  formatter: text(value.formatter, source, `${location}.formatter`),
125
- probe: { kind: probe.kind },
229
+ probe: { kind: policy?.probe ?? probe.kind },
230
+ ...(policy ? { policyRef: SEMGREP_POLICY_REF } : {}),
126
231
  command: parseStructuredCommand(value.command, source),
127
232
  };
128
233
  }
129
- function assertNoEqualPriorityOverlap(variants) {
130
- if (!Array.isArray(variants) || variants.length === 0)
131
- throw new Error('variants must be a nonempty array');
132
- const parsed = variants.map((variant, index) => parseVariant(variant, 'public overlap validator', `variants[${index}]`));
234
+ function parseHardening(input, source) {
235
+ const value = record(input, source, 'hardening');
236
+ const names = Object.keys(value);
237
+ if (names.length === 0)
238
+ invalid(source, 'hardening must be nonempty when declared');
239
+ const hardening = {};
240
+ for (const name of names) {
241
+ const entry = record(value[name], source, `hardening.${name}`);
242
+ fields(entry, ['assets'], source, `hardening.${name}`);
243
+ hardening[id(name, source, 'hardening id')] = { assets: assetArray(entry.assets, source, `hardening.${name}.assets`) };
244
+ }
245
+ return hardening;
246
+ }
247
+ function assertNoEqualPriorityOverlapParsed(parsed) {
133
248
  for (let left = 0; left < parsed.length; left++) {
134
249
  const first = parsed[left];
135
- for (let right = left + 1; right < variants.length; right++) {
250
+ for (let right = left + 1; right < parsed.length; right++) {
136
251
  const second = parsed[right];
137
252
  const toolRangesIntersect = semver_1.default.intersects(first.requirements.toolRange, second.requirements.toolRange);
138
253
  const runtimeRangesIntersect = semver_1.default.intersects(first.requirements.runtimeRange, second.requirements.runtimeRange);
@@ -142,6 +257,11 @@ function assertNoEqualPriorityOverlap(variants) {
142
257
  }
143
258
  }
144
259
  }
260
+ function assertNoEqualPriorityOverlap(variants) {
261
+ if (!Array.isArray(variants) || variants.length === 0)
262
+ throw new Error('variants must be a nonempty array');
263
+ assertNoEqualPriorityOverlapParsed(variants.map((variant, index) => parseVariant(variant, 'public overlap validator', `variants[${index}]`)));
264
+ }
145
265
  function parseSensor(input, source, location, variantIds) {
146
266
  const value = record(input, source, location);
147
267
  fields(value, ['applicability', 'variants', 'fast'], source, location);
@@ -154,7 +274,7 @@ function parseSensor(input, source, location, variantIds) {
154
274
  variantIds.add(variant.id);
155
275
  }
156
276
  try {
157
- assertNoEqualPriorityOverlap(variants);
277
+ assertNoEqualPriorityOverlapParsed(variants);
158
278
  }
159
279
  catch (error) {
160
280
  invalid(source, `${location}.variants ${error instanceof Error ? error.message : 'overlap validation failed'}`);
@@ -234,7 +354,7 @@ function parseSensorPack(input, source) {
234
354
  const value = record(input, source, 'root');
235
355
  if (!('schemaVersion' in value))
236
356
  return { kind: 'legacy', pack: parseLegacyPack(value, source) };
237
- fields(value, ['schemaVersion', 'name', 'description', 'detects', 'sensors', 'coverage'], source, 'root');
357
+ fields(value, ['schemaVersion', 'name', 'description', 'detects', 'sensors', 'coverage', 'hardening'], source, 'root');
238
358
  if (value.schemaVersion !== PACK_SCHEMA_VERSION)
239
359
  invalid(source, `unsupported pack schemaVersion ${String(value.schemaVersion)}; supported: legacy, 2; upgrade or migrate the pack`);
240
360
  const sensorsInput = record(value.sensors, source, 'sensors');
@@ -252,10 +372,11 @@ function parseSensorPack(input, source) {
252
372
  catch (error) {
253
373
  invalid(source, `coverage.${error instanceof Error ? error.message.replace(/^.*?: /, '') : 'is invalid'}`);
254
374
  }
375
+ const hardening = 'hardening' in value ? parseHardening(value.hardening, source) : undefined;
255
376
  return { kind: 'v2', pack: {
256
377
  schemaVersion: PACK_SCHEMA_VERSION,
257
378
  name: id(value.name, source, 'name'), description: text(value.description, source, 'description'), detects: stringArray(value.detects, source, 'detects'),
258
- sensors,
379
+ sensors, ...(hardening ? { hardening } : {}),
259
380
  coverage,
260
381
  } };
261
382
  }
@@ -200,7 +200,7 @@ function discoverProjectEvidence(cwd, pack, dependencies = {}) {
200
200
  const sitePackages = environment ? pythonSitePackages(root, environment.rootParts, targetPlatform) : [];
201
201
  const toolVersions = Object.fromEntries([...tools].sort().map(tool => [tool, pythonToolVersion(root, sitePackages, tool) ?? installedPackageVersion(root, tool)]));
202
202
  return {
203
- cwd: root, os: targetPlatform, runtimeVersions: { node: process.versions.node ?? null, ...(environment ? { python: environment.runtimeVersion } : {}) }, declaredToolRanges, toolVersions,
203
+ cwd: root, os: targetPlatform, runtimeVersions: { node: process.versions.node ?? null, ...(environment ? { python: environment.runtimeVersion } : {}) }, pythonEnvironmentRoot: environment?.rootParts[0] ?? null, declaredToolRanges, toolVersions,
204
204
  packageManager: declaredManager ?? (lockManagers.size === 1 ? [...lockManagers][0] : null), packageManagerConflict: lockManagers.size > 1,
205
205
  scripts, configFiles, paths: [...new Set([...(safeFile(root, 'package.json') ? ['package.json'] : []), ...locks, ...configFiles])].sort(),
206
206
  };
@@ -11,13 +11,42 @@ const discovery_1 = require("./discovery");
11
11
  const resolve_1 = require("./resolve");
12
12
  const probe_1 = require("./probe");
13
13
  const path_1 = __importDefault(require("path"));
14
+ /**
15
+ * Python package metadata is evidence only for a contained virtual environment.
16
+ * A v2 command that names a Python-runtime tool must therefore resolve through
17
+ * that same environment — never a same-named executable inherited from PATH.
18
+ *
19
+ * Keep this at the live pack boundary so the command used by probing, init's
20
+ * materialized manifest, and `sensors run` is one identical structured command.
21
+ */
22
+ function containedRuntimeCommand(variant, pythonEnvironmentRoot) {
23
+ if (variant.requirements.runtime !== 'python')
24
+ return variant;
25
+ return {
26
+ ...variant,
27
+ command: {
28
+ ...variant.command,
29
+ resolution: 'python-environment',
30
+ ...(pythonEnvironmentRoot ? { pythonEnvironmentRoot } : {}),
31
+ },
32
+ };
33
+ }
34
+ function bindContainedRuntimeCommands(pack, pythonEnvironmentRoot) {
35
+ return {
36
+ ...pack,
37
+ sensors: Object.fromEntries(Object.entries(pack.sensors).map(([name, sensor]) => [
38
+ name,
39
+ { ...sensor, variants: sensor.variants.map(variant => containedRuntimeCommand(variant, pythonEnvironmentRoot)) },
40
+ ])),
41
+ };
42
+ }
14
43
  /**
15
44
  * Re-resolve a v2 pack from the configured registry and current project evidence.
16
45
  * Manifest evidence is intentionally not an input: it is an init-time trace, not a
17
46
  * live certification source. Probes are bounded and structured through the shared
18
47
  * compatibility probe runner.
19
48
  */
20
- async function resolveLiveCompatibility(cwd, packName, registryRoot) {
49
+ async function resolveLiveCompatibility(cwd, packName, registryRoot, options = {}) {
21
50
  if (typeof cwd !== 'string' || cwd.trim() === '')
22
51
  throw new Error('cwd must be a non-empty path');
23
52
  if (typeof packName !== 'string' || !/^[a-z][a-z0-9-]*$/.test(packName))
@@ -31,29 +60,33 @@ async function resolveLiveCompatibility(cwd, packName, registryRoot) {
31
60
  const parsed = (0, contract_1.parseSensorPack)(JSON.parse(source.content), source.path);
32
61
  if (parsed.kind !== 'v2')
33
62
  throw new Error(`sensor pack "${packName}" does not provide a v2 compatibility contract`);
34
- return resolveParsedPackCompatibility(cwd, parsed.pack);
63
+ return resolveParsedPackCompatibility(cwd, parsed.pack, options);
35
64
  }
36
65
  /** Resolve a pre-parsed v2 pack. Init uses this when given an explicit registry root. */
37
- async function resolveParsedPackCompatibility(cwd, pack) {
66
+ async function resolveParsedPackCompatibility(cwd, pack, options = {}) {
38
67
  if (typeof cwd !== 'string' || cwd.trim() === '')
39
68
  throw new Error('cwd must be a non-empty path');
40
69
  if (!pack || typeof pack !== 'object' || pack.schemaVersion !== 2)
41
70
  throw new Error('pack must be a parsed v2 sensor pack');
42
71
  const evidence = (0, discovery_1.discoverProjectEvidence)(cwd, pack);
43
- const initial = (0, resolve_1.resolveProjectCompatibility)(pack, evidence).sensors;
72
+ const executionPack = bindContainedRuntimeCommands(pack, evidence.pythonEnvironmentRoot);
73
+ const resolutionEvidence = { ...evidence, ...(options.packSelection === 'explicit' ? { packSelection: 'explicit' } : {}) };
74
+ const initial = (0, resolve_1.resolveProjectCompatibility)(executionPack, resolutionEvidence).sensors;
44
75
  const sensors = {};
45
- for (const [name, sensor] of Object.entries(pack.sensors)) {
76
+ for (const [name, sensor] of Object.entries(executionPack.sensors)) {
46
77
  const base = initial[name];
47
78
  const variant = base.variantId === null ? null : sensor.variants.find(candidate => candidate.id === base.variantId) ?? null;
48
79
  const probe = variant
49
80
  ? await (0, probe_1.runCompatibilityProbe)(variant.probe, {
50
81
  cwd,
51
82
  toolExecutable: variant.command.executable,
83
+ toolResolution: variant.command.resolution,
84
+ pythonEnvironmentRoot: variant.command.pythonEnvironmentRoot,
52
85
  configFiles: evidence.configFiles,
53
86
  scripts: evidence.scripts,
54
87
  })
55
88
  : null;
56
- sensors[name] = (0, resolve_1.resolveProjectCompatibility)({ ...pack, sensors: { [name]: sensor } }, { ...evidence, probe: probe ?? undefined }).sensors[name];
89
+ sensors[name] = (0, resolve_1.resolveProjectCompatibility)({ ...executionPack, sensors: { [name]: sensor } }, { ...resolutionEvidence, probe: probe ?? undefined }).sensors[name];
57
90
  }
58
- return { pack, sensors };
91
+ return { pack: executionPack, sensors };
59
92
  }
@@ -135,7 +135,7 @@ function parseLegacyManifest(value, source) {
135
135
  }
136
136
  function parseV2Sensor(input, source, location) {
137
137
  const value = record(input, source, location);
138
- fields(value, ['enabled', 'fast', 'variantId', 'command', 'assets', 'initializedCompatibility'], source, location);
138
+ fields(value, ['enabled', 'fast', 'variantId', 'command', 'assets', 'policyRef', 'initializedCompatibility'], source, location);
139
139
  if (typeof value.enabled !== 'boolean')
140
140
  invalid(source, `${location}.enabled must be a boolean`);
141
141
  const sensor = {
@@ -152,7 +152,12 @@ function parseV2Sensor(input, source, location) {
152
152
  if (sensor.initializedCompatibility.state === 'certified' && !semver_1.default.satisfies(sensor.initializedCompatibility.toolVersion, sensor.initializedCompatibility.certifiedRange))
153
153
  invalid(source, `${location}.initializedCompatibility.toolVersion must satisfy certifiedRange`);
154
154
  if ('assets' in value)
155
- sensor.assets = stringArray(value.assets, source, `${location}.assets`, false).map((entry, index) => asset(entry, source, `${location}.assets[${index}]`));
155
+ sensor.assets = stringArray(value.assets, source, `${location}.assets`, true).map((entry, index) => asset(entry, source, `${location}.assets[${index}]`));
156
+ if ('policyRef' in value) {
157
+ if (value.policyRef !== 'shared/semgrep-policy.json')
158
+ invalid(source, `${location}.policyRef must be the contained AWM-owned shared/semgrep-policy.json`);
159
+ sensor.policyRef = value.policyRef;
160
+ }
156
161
  if ('fast' in value) {
157
162
  if (typeof value.fast !== 'boolean')
158
163
  invalid(source, `${location}.fast must be a boolean`);
@@ -167,7 +172,7 @@ function provenanceRoot(value, source) {
167
172
  return parsed;
168
173
  }
169
174
  function parseV2Manifest(value, source) {
170
- fields(value, ['schemaVersion', 'pack', 'registryRoot', 'sensors', 'concurrency'], source, 'root');
175
+ fields(value, ['schemaVersion', 'pack', 'packSelection', 'registryRoot', 'sensors', 'concurrency'], source, 'root');
171
176
  if (value.schemaVersion !== 2)
172
177
  invalid(source, `unsupported manifest schemaVersion ${String(value.schemaVersion)}; supported: legacy, 2; upgrade or migrate the manifest`);
173
178
  const pack = id(value.pack, source, 'pack');
@@ -176,6 +181,11 @@ function parseV2Manifest(value, source) {
176
181
  for (const name of Object.keys(sensorsInput))
177
182
  sensors[id(name, source, 'sensor id')] = parseV2Sensor(sensorsInput[name], source, `sensors.${name}`);
178
183
  const manifest = { schemaVersion: 2, pack, sensors };
184
+ if ('packSelection' in value) {
185
+ if (value.packSelection !== 'explicit')
186
+ invalid(source, 'packSelection must be "explicit" when present');
187
+ manifest.packSelection = 'explicit';
188
+ }
179
189
  if ('registryRoot' in value)
180
190
  manifest.registryRoot = provenanceRoot(value.registryRoot, source);
181
191
  if ('concurrency' in value) {
@@ -7,6 +7,7 @@ exports.materializeResolvedSensors = materializeResolvedSensors;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const manifest_1 = require("./manifest");
10
+ const contract_1 = require("./contract");
10
11
  function stableId(value, label) {
11
12
  if (typeof value !== 'string' || !/^[a-z][a-z0-9-]*$/.test(value))
12
13
  throw new Error(`${label} must be a stable lowercase id`);
@@ -81,7 +82,14 @@ function materializeResolvedSensors(input) {
81
82
  throw new Error('materialized sensor must be v2');
82
83
  sensors[name] = parsed.pack.sensors[name];
83
84
  }
84
- const manifest = { schemaVersion: 2, pack, sensors, ...(input.registryRoot ? { registryRoot: input.registryRoot } : {}) };
85
+ const manifest = { schemaVersion: 2, pack, sensors, ...(input.packSelection === 'explicit' ? { packSelection: 'explicit' } : {}), ...(input.registryRoot ? { registryRoot: input.registryRoot } : {}) };
86
+ // A policy reference is deliberately not a materialized asset. Resolve it here
87
+ // from the registry-owned sibling only, so a manifest cannot turn arbitrary
88
+ // registry content into a project write through a cosmetic policy field.
89
+ for (const [name, sensor] of Object.entries(sensors)) {
90
+ if (sensor.policyRef)
91
+ (0, contract_1.resolveSemgrepPolicy)(sensor.policyRef, path_1.default.join(packRoot, 'pack.json'), `sensors.${name}`);
92
+ }
85
93
  const selected = [...new Set(Object.values(sensors).flatMap(sensor => sensor.assets ?? []))].map((asset, index) => containedAsset(asset, `assets[${index}]`)).sort();
86
94
  const prior = priorAssets(projectRoot);
87
95
  const configured = [];
@@ -5,25 +5,25 @@ const exec_1 = require("../exec");
5
5
  const KINDS = new Set(['version', 'eslint-print-config', 'typescript-show-config', 'semgrep-validate', 'package-script-present', 'config-present']);
6
6
  function commandFor(kind, evidence) {
7
7
  const executable = evidence.toolExecutable ?? (kind.startsWith('typescript') ? 'tsc' : kind.startsWith('eslint') ? 'eslint' : kind.startsWith('semgrep') ? 'semgrep' : 'node');
8
- // A tool version certified from local package metadata must be probed through
9
- // the same project installation. Only the Node runtime is intentionally PATH
10
- // resolved: it is runtime evidence, not a package-local tool assertion.
8
+ // Probe an executable through the same bounded resolver as its variant's
9
+ // eventual execution. Falling back preserves legacy probe-only kinds.
11
10
  const resolution = executable === 'node'
12
11
  ? 'path'
13
- : kind === 'semgrep-validate'
12
+ : evidence.toolResolution ?? (kind === 'semgrep-validate'
14
13
  ? 'python-environment'
15
- : 'node-modules-bin';
14
+ : 'node-modules-bin');
16
15
  if (kind === 'package-script-present')
17
16
  return null;
18
17
  if (kind === 'config-present')
19
18
  return null;
19
+ const command = { executable, resolution, ...(resolution === 'python-environment' && evidence.pythonEnvironmentRoot ? { pythonEnvironmentRoot: evidence.pythonEnvironmentRoot } : {}) };
20
20
  if (kind === 'version')
21
- return { executable, resolution, args: ['--version'] };
21
+ return { ...command, args: ['--version'] };
22
22
  if (kind === 'eslint-print-config')
23
- return { executable, resolution, args: ['--print-config', evidence.configFiles?.[0] ?? 'package.json'] };
23
+ return { ...command, args: ['--print-config', evidence.configFiles?.[0] ?? 'package.json'] };
24
24
  if (kind === 'typescript-show-config')
25
- return { executable, resolution, args: ['--showConfig'] };
26
- return { executable, resolution, args: ['--validate'] };
25
+ return { ...command, args: ['--showConfig'] };
26
+ return { ...command, args: ['--validate'] };
27
27
  }
28
28
  /** Executes only the closed probe enum. Raw output is intentionally discarded. */
29
29
  async function runCompatibilityProbe(probe, evidence, executor = exec_1.runStructuredCommand) {
@@ -26,6 +26,9 @@ function result(state, reason, variant, evidence) {
26
26
  return { state, reason, variantId: variant?.id ?? null, toolVersion, runtimeVersion, certifiedRange: variant?.certifiedRange ?? null, evidence: refs.slice(0, 32) };
27
27
  }
28
28
  function applies(sensor, evidence) {
29
+ if (sensor.applicability.kind === 'explicit-or-supported-language') {
30
+ return evidence.applicable === true || evidence.packSelection === 'explicit';
31
+ }
29
32
  if (evidence.applicable === false)
30
33
  return false;
31
34
  const paths = new Set(evidence.paths ?? []);