@neurcode/action 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -1,14 +1,25 @@
1
1
  import * as core from '@actions/core';
2
2
  import * as exec from '@actions/exec';
3
3
  import * as github from '@actions/github';
4
- import { parseCliVerifyJsonPayload } from '@neurcode-ai/contracts';
4
+ import {
5
+ parseCliVerifyJsonPayload,
6
+ RUNTIME_COMPATIBILITY_CONTRACT_VERSION,
7
+ } from '@neurcode-ai/contracts';
5
8
  import { existsSync, readFileSync, writeFileSync } from 'fs';
9
+ import * as http from 'http';
10
+ import * as https from 'https';
6
11
  import { join, resolve } from 'path';
7
12
  import {
8
13
  buildVerifyArgs,
9
14
  getVerifyFallbackDecision,
10
15
  isMissingPlanVerificationFailure,
16
+ resolveEnterpriseEnforcement,
11
17
  } from './verify-mode';
18
+ import {
19
+ parseApiHealthCompatibilityPayload,
20
+ parseCliCompatibilityPayload,
21
+ validateActionHandshake,
22
+ } from './runtime-compat';
12
23
 
13
24
  interface Violation {
14
25
  file: string;
@@ -112,6 +123,19 @@ interface CliInvocation {
112
123
  description: string;
113
124
  }
114
125
 
126
+ interface VerifyCapabilities {
127
+ supportsCompiledPolicy: boolean;
128
+ supportsChangeContract: boolean;
129
+ supportsEnforceChangeContract: boolean;
130
+ }
131
+
132
+ interface HttpJsonResponse {
133
+ statusCode: number;
134
+ body: string;
135
+ payload: unknown | null;
136
+ url: string;
137
+ }
138
+
115
139
  type CliInstallSource = 'npm' | 'workspace';
116
140
 
117
141
  const ANSI_PATTERN = /\u001b\[[0-9;]*m/g;
@@ -163,6 +187,16 @@ function parseBoolean(input: string | undefined, fallback: boolean): boolean {
163
187
  return fallback;
164
188
  }
165
189
 
190
+ function parseBooleanOrUndefined(input: string | undefined): boolean | undefined {
191
+ if (input === undefined || input === null || input.trim() === '') {
192
+ return undefined;
193
+ }
194
+ const normalized = input.trim().toLowerCase();
195
+ if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
196
+ if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
197
+ return undefined;
198
+ }
199
+
166
200
  function parsePositiveInt(input: string | undefined, fallback: number, min: number, max: number): number {
167
201
  const parsed = Number(input);
168
202
  if (!Number.isFinite(parsed)) return fallback;
@@ -303,6 +337,70 @@ function readJsonFile(path: string): Record<string, unknown> | null {
303
337
  }
304
338
  }
305
339
 
340
+ function assertStrictDeterministicArtifacts(input: {
341
+ cwd: string;
342
+ strictMode: boolean;
343
+ compiledPolicyPath?: string;
344
+ changeContractPath?: string;
345
+ supportsCompiledPolicy: boolean;
346
+ supportsChangeContract: boolean;
347
+ }): void {
348
+ if (!input.strictMode) return;
349
+
350
+ const errors: string[] = [];
351
+
352
+ if (!input.supportsCompiledPolicy) {
353
+ errors.push('Current CLI does not support --compiled-policy, but strict mode requires a compiled policy artifact.');
354
+ }
355
+ if (!input.supportsChangeContract) {
356
+ errors.push('Current CLI does not support --change-contract, but strict mode requires a change contract artifact.');
357
+ }
358
+
359
+ const compiledPath = input.compiledPolicyPath?.trim();
360
+ if (!compiledPath) {
361
+ errors.push('Missing compiled policy artifact path in strict mode (compiled_policy_path).');
362
+ } else {
363
+ const absoluteCompiledPath = resolve(input.cwd, compiledPath);
364
+ const compiled = readJsonFile(absoluteCompiledPath);
365
+ if (!compiled) {
366
+ errors.push(`Compiled policy artifact not found or invalid JSON: ${compiledPath}`);
367
+ } else {
368
+ const hasFingerprint = typeof compiled.fingerprint === 'string' && compiled.fingerprint.trim().length > 0;
369
+ const hasRules = Array.isArray(compiled.rules) || Array.isArray(compiled.statements);
370
+ if (!hasFingerprint && !hasRules) {
371
+ errors.push(
372
+ `Compiled policy artifact appears invalid (missing fingerprint/rules): ${compiledPath}`
373
+ );
374
+ }
375
+ }
376
+ }
377
+
378
+ const contractPath = input.changeContractPath?.trim();
379
+ if (!contractPath) {
380
+ errors.push('Missing change contract artifact path in strict mode (change_contract_path).');
381
+ } else {
382
+ const absoluteContractPath = resolve(input.cwd, contractPath);
383
+ const contract = readJsonFile(absoluteContractPath);
384
+ if (!contract) {
385
+ errors.push(`Change contract artifact not found or invalid JSON: ${contractPath}`);
386
+ } else {
387
+ const hasContractId = typeof contract.contractId === 'string' && contract.contractId.trim().length > 0;
388
+ const hasPlanId = typeof contract.planId === 'string' && contract.planId.trim().length > 0;
389
+ if (!hasContractId || !hasPlanId) {
390
+ errors.push(
391
+ `Change contract artifact appears invalid (missing contractId/planId): ${contractPath}`
392
+ );
393
+ }
394
+ }
395
+ }
396
+
397
+ if (errors.length > 0) {
398
+ throw new Error(
399
+ `Strict enterprise mode requires deterministic compiled-policy + change-contract artifacts:\n- ${errors.join('\n- ')}`
400
+ );
401
+ }
402
+ }
403
+
306
404
  function detectProjectOrgId(cwd: string): string | undefined {
307
405
  const statePath = join(cwd, '.neurcode', 'config.json');
308
406
  const state = readJsonFile(statePath);
@@ -381,6 +479,187 @@ function extractLastJsonObject(output: string): unknown | null {
381
479
  return null;
382
480
  }
383
481
 
482
+ function resolveActionPackageVersion(): string {
483
+ const fromEnv = process.env.NEURCODE_ACTION_VERSION || process.env.npm_package_version;
484
+ if (fromEnv && fromEnv.trim()) {
485
+ return fromEnv.trim();
486
+ }
487
+
488
+ const candidates = [
489
+ resolve(__dirname, '../package.json'),
490
+ resolve(process.cwd(), 'package.json'),
491
+ ];
492
+ for (const candidate of candidates) {
493
+ try {
494
+ if (!existsSync(candidate)) continue;
495
+ const parsed = JSON.parse(readFileSync(candidate, 'utf-8'));
496
+ const version = parsed?.version;
497
+ if (typeof version === 'string' && version.trim()) {
498
+ return version.trim();
499
+ }
500
+ } catch {
501
+ // Ignore and continue to next candidate.
502
+ }
503
+ }
504
+
505
+ return '0.0.0';
506
+ }
507
+
508
+ function normalizeApiBaseUrl(value: string): string {
509
+ return value.trim().replace(/\/+$/, '');
510
+ }
511
+
512
+ function requestJson(url: string, timeoutMs: number): Promise<HttpJsonResponse> {
513
+ return new Promise((resolvePromise, rejectPromise) => {
514
+ let target: URL;
515
+ try {
516
+ target = new URL(url);
517
+ } catch (error) {
518
+ rejectPromise(new Error(`Invalid API URL: ${url} (${error instanceof Error ? error.message : String(error)})`));
519
+ return;
520
+ }
521
+ const transport = target.protocol === 'http:' ? http : https;
522
+ const request = transport.request(
523
+ {
524
+ protocol: target.protocol,
525
+ hostname: target.hostname,
526
+ port: target.port || undefined,
527
+ path: `${target.pathname}${target.search}`,
528
+ method: 'GET',
529
+ headers: {
530
+ Accept: 'application/json',
531
+ 'User-Agent': 'neurcode-action/runtime-compat',
532
+ },
533
+ },
534
+ (response) => {
535
+ let body = '';
536
+ response.setEncoding('utf8');
537
+ response.on('data', (chunk) => {
538
+ body += chunk;
539
+ });
540
+ response.on('end', () => {
541
+ let payload: unknown | null = null;
542
+ try {
543
+ payload = body ? JSON.parse(body) : null;
544
+ } catch {
545
+ payload = null;
546
+ }
547
+ resolvePromise({
548
+ statusCode: response.statusCode || 0,
549
+ body,
550
+ payload,
551
+ url,
552
+ });
553
+ });
554
+ }
555
+ );
556
+
557
+ request.on('error', (error) => {
558
+ rejectPromise(new Error(`Request failed for ${url}: ${error.message}`));
559
+ });
560
+
561
+ request.setTimeout(timeoutMs, () => {
562
+ request.destroy(new Error(`Request timed out after ${timeoutMs}ms`));
563
+ });
564
+
565
+ request.end();
566
+ });
567
+ }
568
+
569
+ async function resolveApiHealthPayload(apiBaseUrl: string, timeoutMs: number): Promise<unknown> {
570
+ const baseUrl = normalizeApiBaseUrl(apiBaseUrl);
571
+ const candidates = [`${baseUrl}/api/v1/health`, `${baseUrl}/health`];
572
+ const failures: string[] = [];
573
+
574
+ for (const url of candidates) {
575
+ try {
576
+ const response = await requestJson(url, timeoutMs);
577
+ const payload = response.payload;
578
+ const statusValue =
579
+ payload && typeof payload === 'object' && !Array.isArray(payload)
580
+ ? (payload as Record<string, unknown>).status
581
+ : null;
582
+ if (response.statusCode >= 200 && response.statusCode < 300 && statusValue === 'ok') {
583
+ return payload;
584
+ }
585
+ failures.push(`${url} returned ${response.statusCode}`);
586
+ } catch (error) {
587
+ failures.push(error instanceof Error ? error.message : String(error));
588
+ }
589
+ }
590
+
591
+ throw new Error(
592
+ `API compatibility probe failed (${failures.join(' | ')}).`
593
+ );
594
+ }
595
+
596
+ async function runCompatibilityHandshake(input: {
597
+ cli: CliInvocation;
598
+ cwd: string;
599
+ timeoutMinutes: number;
600
+ apiUrl?: string;
601
+ requireApiCompatibility: boolean;
602
+ }): Promise<{
603
+ actionVersion: string;
604
+ cliVersion: string;
605
+ apiVersion?: string;
606
+ }> {
607
+ const actionVersion = resolveActionPackageVersion();
608
+
609
+ const compatCommand = withCliCommandTimeout(input.cli, ['compat', '--json'], input.timeoutMinutes);
610
+ const compatResult = await runCommand(compatCommand.cmd, compatCommand.args, { cwd: input.cwd });
611
+ if (compatResult.exitCode !== 0) {
612
+ throw new Error(
613
+ `Compatibility handshake failed: unable to run "neurcode compat --json". ` +
614
+ `Output: ${stripAnsi(compatResult.output).trim() || 'no output'}`
615
+ );
616
+ }
617
+
618
+ const cliCompatPayload = extractLastJsonObject(compatResult.output);
619
+ if (!cliCompatPayload) {
620
+ throw new Error('Compatibility handshake failed: CLI compat output did not contain JSON.');
621
+ }
622
+ const cliCompatibility = parseCliCompatibilityPayload(cliCompatPayload);
623
+
624
+ let apiCompatibility = null;
625
+ let apiVersion: string | undefined;
626
+ if (input.apiUrl) {
627
+ const apiHealthPayload = await resolveApiHealthPayload(input.apiUrl, 10000);
628
+ apiCompatibility = parseApiHealthCompatibilityPayload(apiHealthPayload);
629
+ if (!apiCompatibility && input.requireApiCompatibility) {
630
+ throw new Error(
631
+ 'Compatibility handshake failed: API health payload does not include compatibility metadata.'
632
+ );
633
+ }
634
+ if (apiCompatibility?.componentVersion) {
635
+ apiVersion = apiCompatibility.componentVersion;
636
+ } else if (
637
+ apiHealthPayload
638
+ && typeof apiHealthPayload === 'object'
639
+ && !Array.isArray(apiHealthPayload)
640
+ && typeof (apiHealthPayload as Record<string, unknown>).version === 'string'
641
+ ) {
642
+ apiVersion = (apiHealthPayload as Record<string, unknown>).version as string;
643
+ }
644
+ }
645
+
646
+ const errors = validateActionHandshake({
647
+ actionVersion,
648
+ cliCompatibility,
649
+ apiCompatibility,
650
+ requireApiCompatibility: input.requireApiCompatibility && Boolean(input.apiUrl),
651
+ });
652
+ if (errors.length > 0) {
653
+ throw new Error(`Compatibility handshake failed:\n- ${errors.join('\n- ')}`);
654
+ }
655
+
656
+ return {
657
+ actionVersion,
658
+ cliVersion: cliCompatibility.componentVersion,
659
+ apiVersion,
660
+ };
661
+ }
662
+
384
663
  function parseVerifyResult(output: string): NeurcodeVerifyResult | null {
385
664
  const parsed = extractLastJsonObject(output);
386
665
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return null;
@@ -893,6 +1172,26 @@ function withCliCommandTimeout(
893
1172
  return withCommandTimeout(cli.cmd, [...cli.argsPrefix, ...args], timeoutMinutes);
894
1173
  }
895
1174
 
1175
+ async function resolveVerifyCapabilities(cli: CliInvocation, cwd: string): Promise<VerifyCapabilities> {
1176
+ const helpCommand = withCliCommandTimeout(cli, ['verify', '--help'], 2);
1177
+ const helpResult = await runCommand(helpCommand.cmd, helpCommand.args, { cwd });
1178
+ if (helpResult.exitCode !== 0) {
1179
+ core.warning('Unable to detect verify flag capabilities; defaulting to full verify flag set.');
1180
+ return {
1181
+ supportsCompiledPolicy: true,
1182
+ supportsChangeContract: true,
1183
+ supportsEnforceChangeContract: true,
1184
+ };
1185
+ }
1186
+
1187
+ const helpText = stripAnsi(helpResult.output).toLowerCase();
1188
+ return {
1189
+ supportsCompiledPolicy: helpText.includes('--compiled-policy'),
1190
+ supportsChangeContract: helpText.includes('--change-contract'),
1191
+ supportsEnforceChangeContract: helpText.includes('--enforce-change-contract'),
1192
+ };
1193
+ }
1194
+
896
1195
  async function resolveCliInvocation(cwd: string): Promise<CliInvocation | null> {
897
1196
  const direct = await runCommand('neurcode', ['--version'], { cwd });
898
1197
  if (direct.exitCode === 0) {
@@ -1166,10 +1465,26 @@ async function run(): Promise<void> {
1166
1465
  const cliInstallSource = parseCliInstallSource(core.getInput('neurcode_cli_source'));
1167
1466
  const cliWorkspacePath = core.getInput('neurcode_cli_workspace_path') || 'packages/cli';
1168
1467
  const verifyTimeoutMinutes = parsePositiveInt(core.getInput('verify_timeout_minutes'), 8, 1, 120);
1468
+ const enforceCompatibilityHandshake = parseBoolean(
1469
+ core.getInput('enforce_compatibility_handshake'),
1470
+ true
1471
+ );
1472
+ const requireApiCompatibilityHandshake = parseBoolean(
1473
+ core.getInput('require_api_compatibility_handshake'),
1474
+ true
1475
+ );
1476
+ const compatibilityProbeTimeoutMinutes = parsePositiveInt(
1477
+ core.getInput('compatibility_probe_timeout_minutes'),
1478
+ 2,
1479
+ 1,
1480
+ 15
1481
+ );
1482
+ const configuredApiUrl = (process.env.NEURCODE_API_URL || '').trim();
1169
1483
  const verifyPolicyOnly = parseBoolean(core.getInput('verify_policy_only'), false);
1484
+ const enterpriseMode = parseBoolean(core.getInput('enterprise_mode'), true);
1170
1485
  const compiledPolicyPath = (core.getInput('compiled_policy_path') || 'neurcode.policy.compiled.json').trim();
1171
1486
  const changeContractPath = (core.getInput('change_contract_path') || '.neurcode/change-contract.json').trim();
1172
- const enforceChangeContract = parseBoolean(core.getInput('enforce_change_contract'), false);
1487
+ const enforceChangeContractOverride = parseBooleanOrUndefined(core.getInput('enforce_change_contract'));
1173
1488
  const changedFilesOnly = parseBoolean(core.getInput('changed_files_only'), false);
1174
1489
 
1175
1490
  const autoRemediate = parseBoolean(core.getInput('auto_remediate'), false);
@@ -1189,7 +1504,15 @@ async function run(): Promise<void> {
1189
1504
  );
1190
1505
  const remediationCommit = parseBoolean(core.getInput('remediation_commit'), false);
1191
1506
  const remediationPush = parseBoolean(core.getInput('remediation_push'), false);
1192
- const enforceStrictVerification = parseBoolean(core.getInput('enforce_strict_verification'), false);
1507
+ const enforceStrictVerificationOverride = parseBooleanOrUndefined(core.getInput('enforce_strict_verification'));
1508
+ const enterpriseEnforcement = resolveEnterpriseEnforcement({
1509
+ enterpriseMode,
1510
+ verifyPolicyOnly,
1511
+ enforceChangeContract: enforceChangeContractOverride,
1512
+ enforceStrictVerification: enforceStrictVerificationOverride,
1513
+ });
1514
+ let enforceChangeContract = enterpriseEnforcement.enforceChangeContract;
1515
+ const enforceStrictVerification = enterpriseEnforcement.enforceStrictVerification;
1193
1516
  const verifyAfterRemediation = parseBoolean(core.getInput('verify_after_remediation'), true);
1194
1517
  const verifyAfterRemediationTimeoutMinutes = parsePositiveInt(
1195
1518
  core.getInput('verify_after_remediation_timeout_minutes'),
@@ -1235,6 +1558,55 @@ async function run(): Promise<void> {
1235
1558
  workspacePath: cliWorkspacePath,
1236
1559
  });
1237
1560
 
1561
+ if (enforceCompatibilityHandshake) {
1562
+ const handshake = await runCompatibilityHandshake({
1563
+ cli: cliInvocation,
1564
+ cwd,
1565
+ timeoutMinutes: compatibilityProbeTimeoutMinutes,
1566
+ apiUrl: configuredApiUrl || undefined,
1567
+ requireApiCompatibility: requireApiCompatibilityHandshake,
1568
+ });
1569
+ core.info(
1570
+ `Compatibility handshake passed (action=${handshake.actionVersion}, cli=${handshake.cliVersion}${handshake.apiVersion ? `, api=${handshake.apiVersion}` : ''}).`
1571
+ );
1572
+ core.setOutput('compatibility_handshake', 'passed');
1573
+ core.setOutput('compatibility_contract_version', RUNTIME_COMPATIBILITY_CONTRACT_VERSION);
1574
+ core.setOutput('compatibility_action_version', handshake.actionVersion);
1575
+ core.setOutput('compatibility_cli_version', handshake.cliVersion);
1576
+ if (handshake.apiVersion) {
1577
+ core.setOutput('compatibility_api_version', handshake.apiVersion);
1578
+ }
1579
+ } else {
1580
+ core.warning('Compatibility handshake is disabled via enforce_compatibility_handshake=false.');
1581
+ core.setOutput('compatibility_handshake', 'skipped');
1582
+ }
1583
+
1584
+ const verifyCapabilities = await resolveVerifyCapabilities(cliInvocation, cwd);
1585
+ const effectiveCompiledPolicyPath = verifyCapabilities.supportsCompiledPolicy
1586
+ ? (compiledPolicyPath || undefined)
1587
+ : undefined;
1588
+ const effectiveChangeContractPath = verifyCapabilities.supportsChangeContract
1589
+ ? (changeContractPath || undefined)
1590
+ : undefined;
1591
+ if (!verifyCapabilities.supportsCompiledPolicy && compiledPolicyPath) {
1592
+ core.warning('CLI does not support --compiled-policy; compiled policy artifact input will be ignored.');
1593
+ }
1594
+ if (!verifyCapabilities.supportsChangeContract && changeContractPath) {
1595
+ core.warning('CLI does not support --change-contract; contract path input will be ignored.');
1596
+ }
1597
+ if (enforceChangeContract && !verifyCapabilities.supportsEnforceChangeContract) {
1598
+ core.warning('CLI does not support --enforce-change-contract; contract hard-fail mode will be disabled.');
1599
+ enforceChangeContract = false;
1600
+ }
1601
+ assertStrictDeterministicArtifacts({
1602
+ cwd,
1603
+ strictMode: enforceStrictVerification,
1604
+ compiledPolicyPath: effectiveCompiledPolicyPath,
1605
+ changeContractPath: effectiveChangeContractPath,
1606
+ supportsCompiledPolicy: verifyCapabilities.supportsCompiledPolicy,
1607
+ supportsChangeContract: verifyCapabilities.supportsChangeContract,
1608
+ });
1609
+
1238
1610
  const pr = github.context.payload.pull_request;
1239
1611
  const defaultBaseRef = pr ? `origin/${pr.base.ref}` : 'HEAD~1';
1240
1612
  const baseRef = baseRefInput.trim() || defaultBaseRef;
@@ -1242,6 +1614,13 @@ async function run(): Promise<void> {
1242
1614
  ? `Running verify in PR context against ${baseRef}`
1243
1615
  : `Running verify in push context against ${baseRef}`);
1244
1616
  core.info(`Verification mode: ${verifyPolicyOnly ? 'policy-only' : 'plan-aware'}`);
1617
+ core.info(`Enterprise mode: ${enterpriseMode ? 'enabled' : 'disabled'}`);
1618
+ core.info(
1619
+ `Verify capabilities => compiled_policy=${verifyCapabilities.supportsCompiledPolicy}, change_contract=${verifyCapabilities.supportsChangeContract}, enforce_change_contract=${verifyCapabilities.supportsEnforceChangeContract}`
1620
+ );
1621
+ core.info(
1622
+ `Enterprise enforcement => enforce_change_contract=${enforceChangeContract}, enforce_strict_verification=${enforceStrictVerification}`
1623
+ );
1245
1624
 
1246
1625
  let changedFiles = new Set<string>();
1247
1626
  if (changedFilesOnly) {
@@ -1255,6 +1634,7 @@ async function run(): Promise<void> {
1255
1634
  }
1256
1635
 
1257
1636
  let effectiveVerifyPolicyOnly = verifyPolicyOnly;
1637
+ let effectiveEnforceChangeContract = enforceChangeContract;
1258
1638
  const hasExplicitPlanId = Boolean(planId && planId.trim());
1259
1639
  let policyOnlyFallbackUsed = false;
1260
1640
  let fallbackFailureHint: string | null = null;
@@ -1264,9 +1644,10 @@ async function run(): Promise<void> {
1264
1644
  projectId: projectId || undefined,
1265
1645
  policyOnly: effectiveVerifyPolicyOnly,
1266
1646
  record,
1267
- compiledPolicyPath: compiledPolicyPath || undefined,
1268
- changeContractPath: changeContractPath || undefined,
1269
- enforceChangeContract,
1647
+ compiledPolicyPath: effectiveCompiledPolicyPath,
1648
+ changeContractPath: effectiveChangeContractPath,
1649
+ enforceChangeContract: effectiveEnforceChangeContract,
1650
+ strictArtifacts: enforceStrictVerification,
1270
1651
  });
1271
1652
 
1272
1653
  let verifyCommand = withCliCommandTimeout(cliInvocation, verifyArgs, verifyTimeoutMinutes);
@@ -1294,15 +1675,22 @@ async function run(): Promise<void> {
1294
1675
  );
1295
1676
  policyOnlyFallbackUsed = true;
1296
1677
  effectiveVerifyPolicyOnly = true;
1678
+ effectiveEnforceChangeContract =
1679
+ (typeof enforceChangeContractOverride === 'boolean' ? enforceChangeContractOverride : false)
1680
+ && verifyCapabilities.supportsEnforceChangeContract;
1681
+ if (typeof enforceChangeContractOverride !== 'boolean' && enforceChangeContract) {
1682
+ core.info('Policy-only fallback detected; auto-disabling change-contract hard-fail for fallback run.');
1683
+ }
1297
1684
  verifyArgs = buildVerifyArgs({
1298
1685
  baseRef,
1299
1686
  projectId: projectId || undefined,
1300
1687
  planId: undefined,
1301
1688
  policyOnly: true,
1302
1689
  record,
1303
- compiledPolicyPath: compiledPolicyPath || undefined,
1304
- changeContractPath: changeContractPath || undefined,
1305
- enforceChangeContract,
1690
+ compiledPolicyPath: effectiveCompiledPolicyPath,
1691
+ changeContractPath: effectiveChangeContractPath,
1692
+ enforceChangeContract: effectiveEnforceChangeContract,
1693
+ strictArtifacts: enforceStrictVerification,
1306
1694
  });
1307
1695
  verifyCommand = withCliCommandTimeout(cliInvocation, verifyArgs, verifyTimeoutMinutes);
1308
1696
  verifyRun = await runCommand(verifyCommand.cmd, verifyCommand.args, {
@@ -1628,6 +2016,9 @@ async function run(): Promise<void> {
1628
2016
  : 'plan_aware';
1629
2017
  core.setOutput('verify_mode', verifyModeOutput);
1630
2018
  core.setOutput('policy_only_fallback_used', policyOnlyFallbackUsed ? 'true' : 'false');
2019
+ core.setOutput('enterprise_mode_active', enterpriseMode ? 'true' : 'false');
2020
+ core.setOutput('enterprise_enforced_change_contract', effectiveEnforceChangeContract ? 'true' : 'false');
2021
+ core.setOutput('enterprise_enforced_strict_verification', enforceStrictVerification ? 'true' : 'false');
1631
2022
 
1632
2023
  if (remediation) {
1633
2024
  core.setOutput('remediation_status', remediation.status);