@neurcode-ai/cli 0.9.35 → 0.9.37

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 (62) hide show
  1. package/README.md +4 -2
  2. package/dist/api-client.d.ts +345 -1
  3. package/dist/api-client.d.ts.map +1 -1
  4. package/dist/api-client.js +237 -9
  5. package/dist/api-client.js.map +1 -1
  6. package/dist/commands/audit.d.ts +3 -0
  7. package/dist/commands/audit.d.ts.map +1 -0
  8. package/dist/commands/audit.js +133 -0
  9. package/dist/commands/audit.js.map +1 -0
  10. package/dist/commands/contract.d.ts +3 -0
  11. package/dist/commands/contract.d.ts.map +1 -0
  12. package/dist/commands/contract.js +235 -0
  13. package/dist/commands/contract.js.map +1 -0
  14. package/dist/commands/feedback.d.ts +3 -0
  15. package/dist/commands/feedback.d.ts.map +1 -0
  16. package/dist/commands/feedback.js +208 -0
  17. package/dist/commands/feedback.js.map +1 -0
  18. package/dist/commands/plan.d.ts.map +1 -1
  19. package/dist/commands/plan.js +19 -3
  20. package/dist/commands/plan.js.map +1 -1
  21. package/dist/commands/policy.d.ts.map +1 -1
  22. package/dist/commands/policy.js +611 -26
  23. package/dist/commands/policy.js.map +1 -1
  24. package/dist/commands/remediate.d.ts +17 -0
  25. package/dist/commands/remediate.d.ts.map +1 -0
  26. package/dist/commands/remediate.js +252 -0
  27. package/dist/commands/remediate.js.map +1 -0
  28. package/dist/commands/ship.d.ts.map +1 -1
  29. package/dist/commands/ship.js +67 -14
  30. package/dist/commands/ship.js.map +1 -1
  31. package/dist/commands/verify.d.ts +14 -0
  32. package/dist/commands/verify.d.ts.map +1 -1
  33. package/dist/commands/verify.js +564 -14
  34. package/dist/commands/verify.js.map +1 -1
  35. package/dist/index.js +94 -0
  36. package/dist/index.js.map +1 -1
  37. package/dist/utils/artifact-signature.d.ts +34 -0
  38. package/dist/utils/artifact-signature.d.ts.map +1 -0
  39. package/dist/utils/artifact-signature.js +229 -0
  40. package/dist/utils/artifact-signature.js.map +1 -0
  41. package/dist/utils/change-contract.d.ts +2 -0
  42. package/dist/utils/change-contract.d.ts.map +1 -1
  43. package/dist/utils/change-contract.js +21 -1
  44. package/dist/utils/change-contract.js.map +1 -1
  45. package/dist/utils/policy-compiler.d.ts +2 -0
  46. package/dist/utils/policy-compiler.d.ts.map +1 -1
  47. package/dist/utils/policy-compiler.js +15 -0
  48. package/dist/utils/policy-compiler.js.map +1 -1
  49. package/dist/utils/policy-exceptions.d.ts +11 -1
  50. package/dist/utils/policy-exceptions.d.ts.map +1 -1
  51. package/dist/utils/policy-exceptions.js +94 -6
  52. package/dist/utils/policy-exceptions.js.map +1 -1
  53. package/dist/utils/policy-governance.d.ts +22 -1
  54. package/dist/utils/policy-governance.d.ts.map +1 -1
  55. package/dist/utils/policy-governance.js +178 -14
  56. package/dist/utils/policy-governance.js.map +1 -1
  57. package/dist/utils/policy-packs.d.ts +1 -1
  58. package/dist/utils/policy-packs.d.ts.map +1 -1
  59. package/dist/utils/policy-packs.js +185 -0
  60. package/dist/utils/policy-packs.js.map +1 -1
  61. package/package.json +15 -13
  62. package/LICENSE +0 -201
@@ -44,6 +44,7 @@ const git_1 = require("../utils/git");
44
44
  const diff_parser_1 = require("@neurcode-ai/diff-parser");
45
45
  const policy_engine_1 = require("@neurcode-ai/policy-engine");
46
46
  const governance_runtime_1 = require("@neurcode-ai/governance-runtime");
47
+ const contracts_1 = require("@neurcode-ai/contracts");
47
48
  const config_1 = require("../config");
48
49
  const api_client_1 = require("../api-client");
49
50
  const path_1 = require("path");
@@ -63,6 +64,7 @@ const policy_audit_1 = require("../utils/policy-audit");
63
64
  const governance_1 = require("../utils/governance");
64
65
  const policy_compiler_1 = require("../utils/policy-compiler");
65
66
  const change_contract_1 = require("../utils/change-contract");
67
+ const artifact_signature_1 = require("../utils/artifact-signature");
66
68
  const policy_1 = require("@neurcode-ai/policy");
67
69
  // Import chalk with fallback
68
70
  let chalk;
@@ -85,6 +87,155 @@ catch {
85
87
  };
86
88
  }
87
89
  ;
90
+ function toArtifactSignatureSummary(status) {
91
+ return {
92
+ required: status.required,
93
+ present: status.present,
94
+ valid: status.valid,
95
+ keyId: status.keyId,
96
+ verifiedWithKeyId: status.verifiedWithKeyId,
97
+ issues: [...status.issues],
98
+ };
99
+ }
100
+ function resolveCliComponentVersion() {
101
+ const fromEnv = process.env.NEURCODE_CLI_VERSION || process.env.npm_package_version;
102
+ if (fromEnv && fromEnv.trim()) {
103
+ return fromEnv.trim();
104
+ }
105
+ try {
106
+ const packagePath = (0, path_1.join)(__dirname, '../../package.json');
107
+ const raw = (0, fs_1.readFileSync)(packagePath, 'utf-8');
108
+ const parsed = JSON.parse(raw);
109
+ if (typeof parsed.version === 'string' && parsed.version.trim()) {
110
+ return parsed.version.trim();
111
+ }
112
+ }
113
+ catch {
114
+ // Ignore and fall back.
115
+ }
116
+ return '0.0.0';
117
+ }
118
+ const CLI_COMPONENT_VERSION = resolveCliComponentVersion();
119
+ function asCompatibilityRecord(value) {
120
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
121
+ return null;
122
+ }
123
+ return value;
124
+ }
125
+ async function probeApiRuntimeCompatibility(apiUrl) {
126
+ const normalizedApiUrl = apiUrl.replace(/\/$/, '');
127
+ const healthUrl = `${normalizedApiUrl}/health`;
128
+ const controller = new AbortController();
129
+ const timeoutId = setTimeout(() => controller.abort(), 5000);
130
+ try {
131
+ const response = await fetch(healthUrl, {
132
+ method: 'GET',
133
+ signal: controller.signal,
134
+ headers: {
135
+ 'User-Agent': 'neurcode-cli-verify/compat-probe',
136
+ },
137
+ });
138
+ if (!response.ok) {
139
+ return {
140
+ healthUrl,
141
+ apiVersion: null,
142
+ status: 'warn',
143
+ messages: [`Health endpoint returned status ${response.status}; skipping runtime compatibility handshake.`],
144
+ };
145
+ }
146
+ const payload = (await response.json().catch(() => ({})));
147
+ const compatibility = asCompatibilityRecord(payload.compatibility);
148
+ const apiVersionFromHealth = typeof payload.version === 'string' && payload.version.trim()
149
+ ? payload.version.trim()
150
+ : null;
151
+ if (!compatibility) {
152
+ return {
153
+ healthUrl,
154
+ apiVersion: apiVersionFromHealth,
155
+ status: 'warn',
156
+ messages: ['API health payload is missing compatibility metadata.'],
157
+ };
158
+ }
159
+ const contractId = typeof compatibility.contractId === 'string' ? compatibility.contractId.trim() : '';
160
+ const runtimeContractVersion = typeof compatibility.runtimeContractVersion === 'string'
161
+ ? compatibility.runtimeContractVersion.trim()
162
+ : '';
163
+ const cliJsonContractVersion = typeof compatibility.cliJsonContractVersion === 'string'
164
+ ? compatibility.cliJsonContractVersion.trim()
165
+ : '';
166
+ const component = typeof compatibility.component === 'string' ? compatibility.component.trim() : '';
167
+ const componentVersion = typeof compatibility.componentVersion === 'string'
168
+ ? compatibility.componentVersion.trim()
169
+ : '';
170
+ const minimumPeerVersions = asCompatibilityRecord(compatibility.minimumPeerVersions) || {};
171
+ const apiRequiresCli = typeof minimumPeerVersions.cli === 'string' && minimumPeerVersions.cli.trim()
172
+ ? minimumPeerVersions.cli.trim()
173
+ : undefined;
174
+ const errors = [];
175
+ if (contractId !== contracts_1.RUNTIME_COMPATIBILITY_CONTRACT_ID) {
176
+ errors.push(`API compatibility contractId mismatch (expected ${contracts_1.RUNTIME_COMPATIBILITY_CONTRACT_ID}, got ${contractId || 'missing'}).`);
177
+ }
178
+ if (runtimeContractVersion !== contracts_1.RUNTIME_COMPATIBILITY_CONTRACT_VERSION) {
179
+ errors.push(`API runtimeContractVersion mismatch (expected ${contracts_1.RUNTIME_COMPATIBILITY_CONTRACT_VERSION}, got ${runtimeContractVersion || 'missing'}).`);
180
+ }
181
+ if (cliJsonContractVersion !== contracts_1.CLI_JSON_CONTRACT_VERSION) {
182
+ errors.push(`API cliJsonContractVersion mismatch (expected ${contracts_1.CLI_JSON_CONTRACT_VERSION}, got ${cliJsonContractVersion || 'missing'}).`);
183
+ }
184
+ if (component !== 'api') {
185
+ errors.push(`API compatibility payload component must be "api" (received ${component || 'missing'}).`);
186
+ }
187
+ const resolvedApiVersion = componentVersion || apiVersionFromHealth;
188
+ const minimumApiForCli = (0, contracts_1.getMinimumCompatiblePeerVersion)('cli', 'api');
189
+ if (minimumApiForCli && resolvedApiVersion) {
190
+ const cliRequiresApi = (0, contracts_1.isSemverAtLeast)(resolvedApiVersion, minimumApiForCli);
191
+ if (cliRequiresApi === null) {
192
+ errors.push(`Unable to compare API version "${resolvedApiVersion}" against required minimum "${minimumApiForCli}".`);
193
+ }
194
+ else if (!cliRequiresApi) {
195
+ errors.push(`API version ${resolvedApiVersion} is below CLI required minimum ${minimumApiForCli}.`);
196
+ }
197
+ }
198
+ if (apiRequiresCli) {
199
+ const apiRequiresThisCli = (0, contracts_1.isSemverAtLeast)(CLI_COMPONENT_VERSION, apiRequiresCli);
200
+ if (apiRequiresThisCli === null) {
201
+ errors.push(`Unable to compare CLI version "${CLI_COMPONENT_VERSION}" against API required minimum "${apiRequiresCli}".`);
202
+ }
203
+ else if (!apiRequiresThisCli) {
204
+ errors.push(`CLI version ${CLI_COMPONENT_VERSION} is below API required minimum ${apiRequiresCli}.`);
205
+ }
206
+ }
207
+ if (errors.length > 0) {
208
+ return {
209
+ healthUrl,
210
+ apiVersion: resolvedApiVersion || null,
211
+ status: 'error',
212
+ messages: errors,
213
+ };
214
+ }
215
+ return {
216
+ healthUrl,
217
+ apiVersion: resolvedApiVersion || null,
218
+ status: 'ok',
219
+ messages: [],
220
+ };
221
+ }
222
+ catch (error) {
223
+ const message = error instanceof Error && error.name === 'AbortError'
224
+ ? 'Health endpoint timed out after 5s.'
225
+ : error instanceof Error
226
+ ? error.message
227
+ : 'Unknown error';
228
+ return {
229
+ healthUrl,
230
+ apiVersion: null,
231
+ status: 'warn',
232
+ messages: [`Runtime compatibility probe failed: ${message}`],
233
+ };
234
+ }
235
+ finally {
236
+ clearTimeout(timeoutId);
237
+ }
238
+ }
88
239
  /**
89
240
  * Check if a file path should be excluded from verification analysis
90
241
  * Excludes internal/system files that should not count towards plan adherence
@@ -363,8 +514,14 @@ function resolvePolicyDecisionFromViolations(violations) {
363
514
  }
364
515
  function explainExceptionEligibilityReason(reason) {
365
516
  switch (reason) {
517
+ case 'reason_required':
518
+ return 'exception reason does not meet governance minimum length';
519
+ case 'duration_exceeds_max':
520
+ return 'exception expiry window exceeds governance maximum duration';
366
521
  case 'approval_required':
367
522
  return 'exception exists but approvals are required';
523
+ case 'critical_approvals_required':
524
+ return 'critical rule exception requires additional independent approvals';
368
525
  case 'insufficient_approvals':
369
526
  return 'exception exists but approval threshold is not met';
370
527
  case 'self_approval_only':
@@ -385,6 +542,94 @@ function resolveAuditIntegrityStatus(requireIntegrity, auditIntegrity) {
385
542
  issues,
386
543
  };
387
544
  }
545
+ function describePolicyExceptionSource(mode) {
546
+ switch (mode) {
547
+ case 'org':
548
+ return 'org control plane';
549
+ case 'org_fallback_local':
550
+ return 'local file fallback (org unavailable)';
551
+ case 'local':
552
+ default:
553
+ return 'local file';
554
+ }
555
+ }
556
+ function pickExceptionIdentity(userId, email, allowSet) {
557
+ const normalizedUserId = typeof userId === 'string' ? userId.trim() : '';
558
+ const normalizedEmail = typeof email === 'string' ? email.trim() : '';
559
+ if (allowSet.size > 0) {
560
+ if (normalizedEmail && allowSet.has(normalizedEmail.toLowerCase())) {
561
+ return normalizedEmail;
562
+ }
563
+ if (normalizedUserId && allowSet.has(normalizedUserId.toLowerCase())) {
564
+ return normalizedUserId;
565
+ }
566
+ }
567
+ if (normalizedEmail)
568
+ return normalizedEmail;
569
+ if (normalizedUserId)
570
+ return normalizedUserId;
571
+ return 'unknown';
572
+ }
573
+ function mapOrgPolicyExceptionToLocalEntry(exception, allowSet) {
574
+ const createdBy = pickExceptionIdentity(exception.createdBy, exception.requestedByEmail, allowSet);
575
+ const requestedBy = pickExceptionIdentity(exception.requestedBy, exception.requestedByEmail, allowSet);
576
+ return {
577
+ id: exception.id,
578
+ rulePattern: exception.rulePattern,
579
+ filePattern: exception.filePattern,
580
+ reason: exception.reason,
581
+ ticket: exception.ticket,
582
+ createdAt: exception.createdAt,
583
+ createdBy,
584
+ requestedBy,
585
+ expiresAt: exception.expiresAt,
586
+ severity: exception.severity,
587
+ active: exception.active === true
588
+ && exception.workflowState !== 'revoked'
589
+ && exception.workflowState !== 'rejected',
590
+ approvals: (exception.approvals || []).map((approval) => ({
591
+ approver: pickExceptionIdentity(approval.approverUserId, approval.approverEmail, allowSet),
592
+ approvedAt: approval.createdAt,
593
+ comment: approval.note || null,
594
+ })),
595
+ };
596
+ }
597
+ async function resolveEffectivePolicyExceptions(input) {
598
+ const localExceptions = (0, policy_exceptions_1.readPolicyExceptions)(input.projectRoot);
599
+ if (!input.useOrgControlPlane) {
600
+ return {
601
+ mode: 'local',
602
+ exceptions: localExceptions,
603
+ localConfigured: localExceptions.length,
604
+ orgConfigured: 0,
605
+ warning: null,
606
+ };
607
+ }
608
+ try {
609
+ const orgExceptions = await input.client.listOrgPolicyExceptions({ limit: 250 });
610
+ const allowSet = new Set((input.governance.exceptionApprovals.allowedApprovers || []).map((item) => item.toLowerCase()));
611
+ const mapped = orgExceptions
612
+ .map((entry) => mapOrgPolicyExceptionToLocalEntry(entry, allowSet))
613
+ .sort((left, right) => right.createdAt.localeCompare(left.createdAt));
614
+ return {
615
+ mode: 'org',
616
+ exceptions: mapped,
617
+ localConfigured: localExceptions.length,
618
+ orgConfigured: mapped.length,
619
+ warning: null,
620
+ };
621
+ }
622
+ catch (error) {
623
+ const message = error instanceof Error ? error.message : 'Unknown error';
624
+ return {
625
+ mode: 'org_fallback_local',
626
+ exceptions: localExceptions,
627
+ localConfigured: localExceptions.length,
628
+ orgConfigured: 0,
629
+ warning: `Org policy exceptions unavailable; falling back to local exceptions (${message})`,
630
+ };
631
+ }
632
+ }
388
633
  async function recordVerificationIfRequested(options, config, payload) {
389
634
  if (!options.record) {
390
635
  return;
@@ -740,15 +985,30 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
740
985
  const policyResult = (0, policy_engine_1.evaluateRules)(diffFilesForPolicy, effectiveRules.allRules);
741
986
  policyViolations = (policyResult.violations || []);
742
987
  policyViolations = policyViolations.filter((v) => !ignoreFilter(v.file));
743
- const governance = (0, policy_governance_1.readPolicyGovernanceConfig)(projectRoot);
988
+ const localPolicyGovernance = (0, policy_governance_1.readPolicyGovernanceConfig)(projectRoot);
989
+ const governance = (0, policy_governance_1.mergePolicyGovernanceWithOrgOverrides)(localPolicyGovernance, orgGovernanceSettings?.policyGovernance);
744
990
  const auditIntegrity = (0, policy_audit_1.verifyPolicyAuditIntegrity)(projectRoot);
745
991
  const auditIntegrityStatus = resolveAuditIntegrityStatus(governance.audit.requireIntegrity, auditIntegrity);
746
- const configuredPolicyExceptions = (0, policy_exceptions_1.readPolicyExceptions)(projectRoot);
992
+ const policyExceptionResolution = await resolveEffectivePolicyExceptions({
993
+ client,
994
+ projectRoot,
995
+ useOrgControlPlane: Boolean(config.apiKey),
996
+ governance,
997
+ });
998
+ if (policyExceptionResolution.warning && !options.json) {
999
+ console.log(chalk.dim(` ${policyExceptionResolution.warning}`));
1000
+ }
1001
+ const configuredPolicyExceptions = policyExceptionResolution.exceptions;
747
1002
  const exceptionDecision = (0, policy_exceptions_1.applyPolicyExceptions)(policyViolations, configuredPolicyExceptions, {
748
1003
  requireApproval: governance.exceptionApprovals.required,
749
1004
  minApprovals: governance.exceptionApprovals.minApprovals,
750
1005
  disallowSelfApproval: governance.exceptionApprovals.disallowSelfApproval,
751
1006
  allowedApprovers: governance.exceptionApprovals.allowedApprovers,
1007
+ requireReason: governance.exceptionApprovals.requireReason,
1008
+ minReasonLength: governance.exceptionApprovals.minReasonLength,
1009
+ maxExpiryDays: governance.exceptionApprovals.maxExpiryDays,
1010
+ criticalRulePatterns: governance.exceptionApprovals.criticalRulePatterns,
1011
+ criticalMinApprovals: governance.exceptionApprovals.criticalMinApprovals,
752
1012
  });
753
1013
  const suppressedViolations = exceptionDecision.suppressedViolations.filter((item) => !ignoreFilter(item.file));
754
1014
  const blockedViolations = exceptionDecision.blockedViolations
@@ -757,7 +1017,10 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
757
1017
  file: item.file,
758
1018
  rule: item.rule,
759
1019
  severity: 'block',
760
- message: `Exception ${item.exceptionId} cannot be applied: ${explainExceptionEligibilityReason(item.eligibilityReason)}`,
1020
+ message: `Exception ${item.exceptionId} cannot be applied: ${explainExceptionEligibilityReason(item.eligibilityReason)}` +
1021
+ (item.requiredApprovals > 0
1022
+ ? ` (approvals ${item.effectiveApprovals}/${item.requiredApprovals}${item.critical ? ', critical rule gate' : ''})`
1023
+ : ''),
761
1024
  ...(item.line != null ? { line: item.line } : {}),
762
1025
  }));
763
1026
  policyViolations = [
@@ -789,6 +1052,10 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
789
1052
  ? `Policy violations: ${policyViolations.map((v) => `${v.file}: ${v.message || v.rule}`).join('; ')}`
790
1053
  : 'Policy check completed';
791
1054
  const policyExceptionsSummary = {
1055
+ sourceMode: policyExceptionResolution.mode,
1056
+ sourceWarning: policyExceptionResolution.warning,
1057
+ localConfigured: policyExceptionResolution.localConfigured,
1058
+ orgConfigured: policyExceptionResolution.orgConfigured,
792
1059
  configured: configuredPolicyExceptions.length,
793
1060
  active: exceptionDecision.activeExceptions.length,
794
1061
  usable: exceptionDecision.usableExceptions.length,
@@ -871,6 +1138,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
871
1138
  console.log(chalk.red(` • ${v.file}: ${v.message || v.rule}`));
872
1139
  });
873
1140
  }
1141
+ console.log(chalk.dim(` Policy exceptions source: ${describePolicyExceptionSource(policyExceptionsSummary.sourceMode)}`));
874
1142
  if (policyExceptionsSummary.suppressed > 0) {
875
1143
  console.log(chalk.yellow(` Policy exceptions applied: ${policyExceptionsSummary.suppressed}`));
876
1144
  }
@@ -913,9 +1181,48 @@ async function verifyCommand(options) {
913
1181
  };
914
1182
  const enforceChangeContract = options.enforceChangeContract === true ||
915
1183
  isEnabledFlag(process.env.NEURCODE_VERIFY_ENFORCE_CHANGE_CONTRACT);
1184
+ const explicitStrictArtifactMode = options.strictArtifacts === true ||
1185
+ isEnabledFlag(process.env.NEURCODE_VERIFY_STRICT_ARTIFACTS) ||
1186
+ isEnabledFlag(process.env.NEURCODE_ENTERPRISE_MODE);
1187
+ const ciEnterpriseDefaultStrict = process.env.CI === 'true'
1188
+ && !isEnabledFlag(process.env.NEURCODE_VERIFY_ALLOW_NON_STRICT_CI)
1189
+ && Boolean(options.apiKey || process.env.NEURCODE_API_KEY);
1190
+ const strictArtifactMode = explicitStrictArtifactMode || ciEnterpriseDefaultStrict;
1191
+ const signingConfig = resolveGovernanceSigningConfig();
1192
+ const aiLogSigningKey = signingConfig.signingKey;
1193
+ const aiLogSigningKeyId = signingConfig.signingKeyId;
1194
+ const aiLogSigningKeys = signingConfig.signingKeys;
1195
+ const aiLogSigner = signingConfig.signer;
1196
+ const hasSigningMaterial = Boolean(aiLogSigningKey) || Object.keys(aiLogSigningKeys).length > 0;
1197
+ const allowUnsignedArtifacts = isEnabledFlag(process.env.NEURCODE_VERIFY_ALLOW_UNSIGNED_ARTIFACTS)
1198
+ || isEnabledFlag(process.env.NEURCODE_VERIFY_DISABLE_SIGNED_ARTIFACTS);
1199
+ const requireSignedArtifacts = options.requireSignedArtifacts === true
1200
+ || isEnabledFlag(process.env.NEURCODE_VERIFY_REQUIRE_SIGNED_ARTIFACTS)
1201
+ || (!allowUnsignedArtifacts && strictArtifactMode && hasSigningMaterial);
916
1202
  const changeContractRead = (0, change_contract_1.readChangeContract)(projectRoot, options.changeContract);
917
1203
  const compiledPolicyRead = (0, policy_compiler_1.readCompiledPolicyArtifact)(projectRoot, options.compiledPolicy);
918
1204
  let compiledPolicyMetadata = resolveCompiledPolicyMetadata(compiledPolicyRead.artifact, compiledPolicyRead.exists ? compiledPolicyRead.path : null);
1205
+ const compiledPolicySignatureStatus = compiledPolicyRead.artifact
1206
+ ? (0, artifact_signature_1.verifyGovernanceArtifactSignature)({
1207
+ artifact: compiledPolicyRead.artifact,
1208
+ requireSigned: requireSignedArtifacts,
1209
+ signingKey: aiLogSigningKey,
1210
+ signingKeyId: aiLogSigningKeyId,
1211
+ signingKeys: aiLogSigningKeys,
1212
+ })
1213
+ : null;
1214
+ if (compiledPolicyMetadata && compiledPolicySignatureStatus) {
1215
+ compiledPolicyMetadata.signature = toArtifactSignatureSummary(compiledPolicySignatureStatus);
1216
+ }
1217
+ const changeContractSignatureStatus = changeContractRead.contract
1218
+ ? (0, artifact_signature_1.verifyGovernanceArtifactSignature)({
1219
+ artifact: changeContractRead.contract,
1220
+ requireSigned: requireSignedArtifacts,
1221
+ signingKey: aiLogSigningKey,
1222
+ signingKeyId: aiLogSigningKeyId,
1223
+ signingKeys: aiLogSigningKeys,
1224
+ })
1225
+ : null;
919
1226
  let changeContractSummary = {
920
1227
  path: changeContractRead.path,
921
1228
  exists: changeContractRead.exists,
@@ -923,6 +1230,9 @@ async function verifyCommand(options) {
923
1230
  valid: changeContractRead.contract ? null : changeContractRead.exists ? false : null,
924
1231
  planId: changeContractRead.contract?.planId || null,
925
1232
  contractId: changeContractRead.contract?.contractId || null,
1233
+ signature: changeContractSignatureStatus
1234
+ ? toArtifactSignatureSummary(changeContractSignatureStatus)
1235
+ : undefined,
926
1236
  violations: changeContractRead.error
927
1237
  ? [
928
1238
  {
@@ -932,6 +1242,120 @@ async function verifyCommand(options) {
932
1242
  ]
933
1243
  : [],
934
1244
  };
1245
+ if (strictArtifactMode) {
1246
+ const strictErrors = [];
1247
+ if (!compiledPolicyRead.artifact) {
1248
+ strictErrors.push(compiledPolicyRead.error
1249
+ ? `Compiled policy artifact invalid (${compiledPolicyRead.error})`
1250
+ : `Compiled policy artifact missing (${compiledPolicyRead.path})`);
1251
+ }
1252
+ if (!changeContractRead.contract) {
1253
+ strictErrors.push(changeContractRead.error
1254
+ ? `Change contract artifact invalid (${changeContractRead.error})`
1255
+ : `Change contract artifact missing (${changeContractRead.path})`);
1256
+ }
1257
+ if (compiledPolicySignatureStatus
1258
+ && !compiledPolicySignatureStatus.valid
1259
+ && (requireSignedArtifacts || compiledPolicySignatureStatus.present)) {
1260
+ strictErrors.push(`Compiled policy artifact signature validation failed (${compiledPolicySignatureStatus.issues.join('; ') || 'unknown issue'})`);
1261
+ }
1262
+ if (changeContractSignatureStatus
1263
+ && !changeContractSignatureStatus.valid
1264
+ && (requireSignedArtifacts || changeContractSignatureStatus.present)) {
1265
+ strictErrors.push(`Change contract artifact signature validation failed (${changeContractSignatureStatus.issues.join('; ') || 'unknown issue'})`);
1266
+ }
1267
+ if (strictErrors.length > 0) {
1268
+ const message = `Strict artifact mode requires deterministic compiled-policy + change-contract artifacts.\n- ${strictErrors.join('\n- ')}`;
1269
+ if (options.json) {
1270
+ emitVerifyJson({
1271
+ grade: 'F',
1272
+ score: 0,
1273
+ verdict: 'FAIL',
1274
+ violations: strictErrors.map((entry) => ({
1275
+ file: entry.toLowerCase().includes('compiled policy') ? compiledPolicyRead.path : changeContractRead.path,
1276
+ rule: 'deterministic_artifacts_required',
1277
+ severity: 'block',
1278
+ message: entry,
1279
+ })),
1280
+ adherenceScore: 0,
1281
+ bloatCount: 0,
1282
+ bloatFiles: [],
1283
+ plannedFilesModified: 0,
1284
+ totalPlannedFiles: 0,
1285
+ message,
1286
+ scopeGuardPassed: false,
1287
+ mode: 'strict_artifacts_required',
1288
+ policyOnly: options.policyOnly === true,
1289
+ changeContract: changeContractSummary,
1290
+ ...(compiledPolicyMetadata ? { policyCompilation: compiledPolicyMetadata } : {}),
1291
+ });
1292
+ }
1293
+ else {
1294
+ (0, scope_telemetry_1.printScopeTelemetry)(chalk, scopeTelemetry, {
1295
+ includeBlockedWarning: true,
1296
+ });
1297
+ console.log(chalk.red('\n⛔ Deterministic Artifact Requirements Failed'));
1298
+ strictErrors.forEach((entry) => {
1299
+ console.log(chalk.red(` • ${entry}`));
1300
+ });
1301
+ console.log(chalk.dim('\nSet --compiled-policy and --change-contract with valid artifacts before verify.'));
1302
+ if (requireSignedArtifacts) {
1303
+ console.log(chalk.dim('Enable signing keys via NEURCODE_GOVERNANCE_SIGNING_KEY or NEURCODE_GOVERNANCE_SIGNING_KEYS to generate signed artifacts.\n'));
1304
+ }
1305
+ else {
1306
+ console.log('');
1307
+ }
1308
+ }
1309
+ process.exit(2);
1310
+ }
1311
+ }
1312
+ if (!strictArtifactMode && requireSignedArtifacts) {
1313
+ const signatureErrors = [];
1314
+ if (compiledPolicyRead.artifact && compiledPolicySignatureStatus && !compiledPolicySignatureStatus.valid) {
1315
+ signatureErrors.push(`Compiled policy artifact signature validation failed (${compiledPolicySignatureStatus.issues.join('; ') || 'unknown issue'})`);
1316
+ }
1317
+ if (changeContractRead.contract && changeContractSignatureStatus && !changeContractSignatureStatus.valid) {
1318
+ signatureErrors.push(`Change contract artifact signature validation failed (${changeContractSignatureStatus.issues.join('; ') || 'unknown issue'})`);
1319
+ }
1320
+ if (signatureErrors.length > 0) {
1321
+ const message = `Signed artifact enforcement failed.\n- ${signatureErrors.join('\n- ')}`;
1322
+ if (options.json) {
1323
+ emitVerifyJson({
1324
+ grade: 'F',
1325
+ score: 0,
1326
+ verdict: 'FAIL',
1327
+ violations: signatureErrors.map((entry) => ({
1328
+ file: entry.toLowerCase().includes('compiled policy') ? compiledPolicyRead.path : changeContractRead.path,
1329
+ rule: 'signed_artifacts_required',
1330
+ severity: 'block',
1331
+ message: entry,
1332
+ })),
1333
+ adherenceScore: 0,
1334
+ bloatCount: 0,
1335
+ bloatFiles: [],
1336
+ plannedFilesModified: 0,
1337
+ totalPlannedFiles: 0,
1338
+ message,
1339
+ scopeGuardPassed: false,
1340
+ mode: 'signed_artifacts_required',
1341
+ policyOnly: options.policyOnly === true,
1342
+ changeContract: changeContractSummary,
1343
+ ...(compiledPolicyMetadata ? { policyCompilation: compiledPolicyMetadata } : {}),
1344
+ });
1345
+ }
1346
+ else {
1347
+ (0, scope_telemetry_1.printScopeTelemetry)(chalk, scopeTelemetry, {
1348
+ includeBlockedWarning: true,
1349
+ });
1350
+ console.log(chalk.red('\n⛔ Signed Artifact Requirements Failed'));
1351
+ signatureErrors.forEach((entry) => {
1352
+ console.log(chalk.red(` • ${entry}`));
1353
+ });
1354
+ console.log(chalk.dim('\nEnable signing keys via NEURCODE_GOVERNANCE_SIGNING_KEY or NEURCODE_GOVERNANCE_SIGNING_KEYS and regenerate artifacts.\n'));
1355
+ }
1356
+ process.exit(2);
1357
+ }
1358
+ }
935
1359
  if (!options.json) {
936
1360
  (0, scope_telemetry_1.printScopeTelemetry)(chalk, scopeTelemetry, {
937
1361
  includeBlockedWarning: true,
@@ -948,6 +1372,28 @@ async function verifyCommand(options) {
948
1372
  else if (changeContractRead.contract) {
949
1373
  console.log(chalk.dim(` Change contract loaded: ${changeContractRead.path}`));
950
1374
  }
1375
+ if (compiledPolicySignatureStatus) {
1376
+ if (compiledPolicySignatureStatus.valid) {
1377
+ console.log(chalk.dim(` Compiled policy signature: valid${compiledPolicySignatureStatus.verifiedWithKeyId ? ` (key ${compiledPolicySignatureStatus.verifiedWithKeyId})` : ''}`));
1378
+ }
1379
+ else if (compiledPolicySignatureStatus.present || requireSignedArtifacts) {
1380
+ console.log(chalk.yellow(` Compiled policy signature: invalid (${compiledPolicySignatureStatus.issues.join('; ') || 'unknown issue'})`));
1381
+ }
1382
+ }
1383
+ if (changeContractSignatureStatus) {
1384
+ if (changeContractSignatureStatus.valid) {
1385
+ console.log(chalk.dim(` Change contract signature: valid${changeContractSignatureStatus.verifiedWithKeyId ? ` (key ${changeContractSignatureStatus.verifiedWithKeyId})` : ''}`));
1386
+ }
1387
+ else if (changeContractSignatureStatus.present || requireSignedArtifacts) {
1388
+ console.log(chalk.yellow(` Change contract signature: invalid (${changeContractSignatureStatus.issues.join('; ') || 'unknown issue'})`));
1389
+ }
1390
+ }
1391
+ if (ciEnterpriseDefaultStrict && !explicitStrictArtifactMode) {
1392
+ console.log(chalk.dim(' CI enterprise mode detected: strict deterministic artifact enforcement is auto-enabled (set NEURCODE_VERIFY_ALLOW_NON_STRICT_CI=1 to opt out).'));
1393
+ }
1394
+ if (requireSignedArtifacts) {
1395
+ console.log(chalk.dim(' Artifact signature enforcement: enabled (set NEURCODE_VERIFY_ALLOW_UNSIGNED_ARTIFACTS=1 to relax)'));
1396
+ }
951
1397
  }
952
1398
  // Load configuration
953
1399
  const config = (0, config_1.loadConfig)();
@@ -975,6 +1421,72 @@ async function verifyCommand(options) {
975
1421
  // Ensure no trailing slash
976
1422
  config.apiUrl = config.apiUrl.replace(/\/$/, '');
977
1423
  }
1424
+ const enforceCompatibilityHandshake = isEnabledFlag(process.env.NEURCODE_VERIFY_ENFORCE_COMPAT_HANDSHAKE)
1425
+ || strictArtifactMode
1426
+ || (process.env.CI === 'true' && Boolean(config.apiKey));
1427
+ if (config.apiKey && config.apiUrl) {
1428
+ const compatibilityProbe = await probeApiRuntimeCompatibility(config.apiUrl);
1429
+ if (compatibilityProbe.status !== 'ok' && enforceCompatibilityHandshake) {
1430
+ const failureMessages = compatibilityProbe.messages.length > 0
1431
+ ? compatibilityProbe.messages
1432
+ : ['Runtime compatibility handshake did not return a successful result.'];
1433
+ const message = `Runtime compatibility handshake failed against ${compatibilityProbe.healthUrl}.\n` +
1434
+ failureMessages.map((entry) => `- ${entry}`).join('\n');
1435
+ if (options.json) {
1436
+ emitVerifyJson({
1437
+ grade: 'F',
1438
+ score: 0,
1439
+ verdict: 'FAIL',
1440
+ violations: failureMessages.map((entry) => ({
1441
+ file: 'runtime-compatibility',
1442
+ rule: 'runtime_compatibility_handshake',
1443
+ severity: 'block',
1444
+ message: entry,
1445
+ })),
1446
+ adherenceScore: 0,
1447
+ bloatCount: 0,
1448
+ bloatFiles: [],
1449
+ plannedFilesModified: 0,
1450
+ totalPlannedFiles: 0,
1451
+ message,
1452
+ scopeGuardPassed: false,
1453
+ mode: 'runtime_compatibility_failed',
1454
+ policyOnly: options.policyOnly === true,
1455
+ changeContract: changeContractSummary,
1456
+ ...(compiledPolicyMetadata ? { policyCompilation: compiledPolicyMetadata } : {}),
1457
+ });
1458
+ }
1459
+ else {
1460
+ console.log(chalk.red('\n⛔ Runtime Compatibility Handshake Failed'));
1461
+ failureMessages.forEach((entry) => {
1462
+ console.log(chalk.red(` • ${entry}`));
1463
+ });
1464
+ console.log(chalk.dim(` Health endpoint: ${compatibilityProbe.healthUrl}`));
1465
+ if (compatibilityProbe.apiVersion) {
1466
+ console.log(chalk.dim(` API version: ${compatibilityProbe.apiVersion}`));
1467
+ }
1468
+ console.log(chalk.dim(` CLI version: ${CLI_COMPONENT_VERSION}`));
1469
+ console.log(chalk.dim(' Upgrade/downgrade CLI, Action, or API to satisfy the runtime compatibility contract before running verify.\n'));
1470
+ }
1471
+ process.exit(2);
1472
+ }
1473
+ if (compatibilityProbe.status === 'error' && !options.json) {
1474
+ console.log(chalk.yellow('\n⚠️ Runtime compatibility mismatch detected (advisory mode).'));
1475
+ compatibilityProbe.messages.forEach((entry) => {
1476
+ console.log(chalk.yellow(` • ${entry}`));
1477
+ });
1478
+ }
1479
+ else if (compatibilityProbe.status === 'warn' && !options.json) {
1480
+ compatibilityProbe.messages.forEach((entry) => {
1481
+ console.log(chalk.dim(` ${entry}`));
1482
+ });
1483
+ }
1484
+ else if (compatibilityProbe.status === 'ok'
1485
+ && !options.json
1486
+ && isEnabledFlag(process.env.NEURCODE_VERIFY_VERBOSE_COMPAT_HANDSHAKE)) {
1487
+ console.log(chalk.dim(` Runtime compatibility check passed (CLI ${CLI_COMPONENT_VERSION}, API ${compatibilityProbe.apiVersion || 'unknown'})`));
1488
+ }
1489
+ }
978
1490
  // Explicitly load config file to get sessionId and lastSessionId
979
1491
  const configPath = (0, path_1.join)(projectRoot, 'neurcode.config.json');
980
1492
  let configData = {};
@@ -997,11 +1509,6 @@ async function verifyCommand(options) {
997
1509
  orgId: (0, state_1.getOrgId)(),
998
1510
  projectId: projectId || null,
999
1511
  };
1000
- const signingConfig = resolveGovernanceSigningConfig();
1001
- const aiLogSigningKey = signingConfig.signingKey;
1002
- const aiLogSigningKeyId = signingConfig.signingKeyId;
1003
- const aiLogSigningKeys = signingConfig.signingKeys;
1004
- const aiLogSigner = signingConfig.signer;
1005
1512
  let orgGovernanceSettings = null;
1006
1513
  if (config.apiKey) {
1007
1514
  try {
@@ -1012,6 +1519,9 @@ async function verifyCommand(options) {
1012
1519
  requireSignedAiLogs: remoteSettings.requireSignedAiLogs === true,
1013
1520
  requireManualApproval: remoteSettings.requireManualApproval !== false,
1014
1521
  minimumManualApprovals: Math.max(1, Math.min(5, Math.floor(remoteSettings.minimumManualApprovals || 1))),
1522
+ ...(remoteSettings.policyGovernance && typeof remoteSettings.policyGovernance === 'object'
1523
+ ? { policyGovernance: remoteSettings.policyGovernance }
1524
+ : {}),
1015
1525
  updatedAt: remoteSettings.updatedAt,
1016
1526
  };
1017
1527
  }
@@ -1024,7 +1534,6 @@ async function verifyCommand(options) {
1024
1534
  }
1025
1535
  }
1026
1536
  const signedLogsRequired = isSignedAiLogsRequired(orgGovernanceSettings);
1027
- const hasSigningMaterial = Boolean(aiLogSigningKey) || Object.keys(aiLogSigningKeys).length > 0;
1028
1537
  const recordVerifyEvent = (verdict, note, changedFiles, planId) => {
1029
1538
  if (!brainScope.orgId || !brainScope.projectId) {
1030
1539
  return;
@@ -1287,7 +1796,9 @@ async function verifyCommand(options) {
1287
1796
  if (options.policyOnly) {
1288
1797
  await runPolicyOnlyModeAndExit('explicit');
1289
1798
  }
1290
- const requirePlan = options.requirePlan === true || process.env.NEURCODE_VERIFY_REQUIRE_PLAN === '1';
1799
+ const requirePlan = options.requirePlan === true
1800
+ || process.env.NEURCODE_VERIFY_REQUIRE_PLAN === '1'
1801
+ || strictArtifactMode;
1291
1802
  // Get planId: Priority 1: options flag, Priority 2: state file (.neurcode/config.json), Priority 3: legacy config
1292
1803
  let planId = options.planId;
1293
1804
  if (!planId) {
@@ -1724,7 +2235,11 @@ async function verifyCommand(options) {
1724
2235
  }
1725
2236
  }
1726
2237
  // Check user tier - Policy Compliance and A-F Grading are PRO features
1727
- const governance = (0, policy_governance_1.readPolicyGovernanceConfig)(projectRoot);
2238
+ const localPolicyGovernance = (0, policy_governance_1.readPolicyGovernanceConfig)(projectRoot);
2239
+ const governance = (0, policy_governance_1.mergePolicyGovernanceWithOrgOverrides)(localPolicyGovernance, orgGovernanceSettings?.policyGovernance);
2240
+ if (!options.json && orgGovernanceSettings?.policyGovernance) {
2241
+ console.log(chalk.dim(' Org policy governance controls active: local config merged with org-level enforcement floor'));
2242
+ }
1728
2243
  const auditIntegrity = (0, policy_audit_1.verifyPolicyAuditIntegrity)(projectRoot);
1729
2244
  const auditIntegrityStatus = resolveAuditIntegrityStatus(governance.audit.requireIntegrity, auditIntegrity);
1730
2245
  const { getUserTier } = await Promise.resolve().then(() => __importStar(require('../utils/tier')));
@@ -1790,12 +2305,26 @@ async function verifyCommand(options) {
1790
2305
  const diffFilesForPolicy = diffFiles.filter((f) => !shouldIgnore(f.path));
1791
2306
  const policyResult = (0, policy_engine_1.evaluateRules)(diffFilesForPolicy, effectiveRules.allRules);
1792
2307
  policyViolations = policyResult.violations.filter((v) => !shouldIgnore(v.file));
1793
- const configuredPolicyExceptions = (0, policy_exceptions_1.readPolicyExceptions)(projectRoot);
2308
+ const policyExceptionResolution = await resolveEffectivePolicyExceptions({
2309
+ client,
2310
+ projectRoot,
2311
+ useOrgControlPlane: Boolean(config.apiKey),
2312
+ governance,
2313
+ });
2314
+ if (policyExceptionResolution.warning && !options.json) {
2315
+ console.log(chalk.dim(` ${policyExceptionResolution.warning}`));
2316
+ }
2317
+ const configuredPolicyExceptions = policyExceptionResolution.exceptions;
1794
2318
  const exceptionDecision = (0, policy_exceptions_1.applyPolicyExceptions)(policyViolations, configuredPolicyExceptions, {
1795
2319
  requireApproval: governance.exceptionApprovals.required,
1796
2320
  minApprovals: governance.exceptionApprovals.minApprovals,
1797
2321
  disallowSelfApproval: governance.exceptionApprovals.disallowSelfApproval,
1798
2322
  allowedApprovers: governance.exceptionApprovals.allowedApprovers,
2323
+ requireReason: governance.exceptionApprovals.requireReason,
2324
+ minReasonLength: governance.exceptionApprovals.minReasonLength,
2325
+ maxExpiryDays: governance.exceptionApprovals.maxExpiryDays,
2326
+ criticalRulePatterns: governance.exceptionApprovals.criticalRulePatterns,
2327
+ criticalMinApprovals: governance.exceptionApprovals.criticalMinApprovals,
1799
2328
  });
1800
2329
  const suppressedPolicyViolations = exceptionDecision.suppressedViolations.filter((item) => !shouldIgnore(item.file));
1801
2330
  const blockedPolicyViolations = exceptionDecision.blockedViolations
@@ -1804,7 +2333,10 @@ async function verifyCommand(options) {
1804
2333
  file: item.file,
1805
2334
  rule: item.rule,
1806
2335
  severity: 'block',
1807
- message: `Exception ${item.exceptionId} cannot be applied: ${explainExceptionEligibilityReason(item.eligibilityReason)}`,
2336
+ message: `Exception ${item.exceptionId} cannot be applied: ${explainExceptionEligibilityReason(item.eligibilityReason)}` +
2337
+ (item.requiredApprovals > 0
2338
+ ? ` (approvals ${item.effectiveApprovals}/${item.requiredApprovals}${item.critical ? ', critical rule gate' : ''})`
2339
+ : ''),
1808
2340
  ...(item.line != null ? { line: item.line } : {}),
1809
2341
  }));
1810
2342
  policyViolations = [
@@ -1821,6 +2353,10 @@ async function verifyCommand(options) {
1821
2353
  }
1822
2354
  policyDecision = resolvePolicyDecisionFromViolations(policyViolations);
1823
2355
  const policyExceptionsSummary = {
2356
+ sourceMode: policyExceptionResolution.mode,
2357
+ sourceWarning: policyExceptionResolution.warning,
2358
+ localConfigured: policyExceptionResolution.localConfigured,
2359
+ orgConfigured: policyExceptionResolution.orgConfigured,
1824
2360
  configured: configuredPolicyExceptions.length,
1825
2361
  active: exceptionDecision.activeExceptions.length,
1826
2362
  usable: exceptionDecision.usableExceptions.length,
@@ -1903,6 +2439,7 @@ async function verifyCommand(options) {
1903
2439
  valid: changeContractEvaluation.valid,
1904
2440
  planId: changeContractRead.contract?.planId || null,
1905
2441
  contractId: changeContractRead.contract?.contractId || null,
2442
+ signature: changeContractSummary.signature,
1906
2443
  coverage: changeContractEvaluation.coverage,
1907
2444
  violations: changeContractEvaluation.violations.map((item) => ({
1908
2445
  code: item.code,
@@ -1982,6 +2519,9 @@ async function verifyCommand(options) {
1982
2519
  // Call verify API
1983
2520
  if (!options.json) {
1984
2521
  console.log(chalk.dim(' Sending to Neurcode API...\n'));
2522
+ if (options.asyncMode) {
2523
+ console.log(chalk.dim(' Queue-backed verification enabled (async job mode).'));
2524
+ }
1985
2525
  }
1986
2526
  try {
1987
2527
  let verifySource = 'api';
@@ -1992,7 +2532,13 @@ async function verifyCommand(options) {
1992
2532
  .map((policy) => policy.rule_text)
1993
2533
  .filter((ruleText) => typeof ruleText === 'string' && ruleText.trim().length > 0);
1994
2534
  try {
1995
- verifyResult = await client.verifyPlan(finalPlanId, diffStats, changedFiles, projectId, intentConstraintsForVerification, deterministicPolicyRules, 'api', compiledPolicyMetadata);
2535
+ verifyResult = await client.verifyPlan(finalPlanId, diffStats, changedFiles, projectId, intentConstraintsForVerification, deterministicPolicyRules, 'api', compiledPolicyMetadata, {
2536
+ async: options.asyncMode === true,
2537
+ pollIntervalMs: Number.isFinite(options.verifyJobPollMs) ? options.verifyJobPollMs : undefined,
2538
+ timeoutMs: Number.isFinite(options.verifyJobTimeoutMs) ? options.verifyJobTimeoutMs : undefined,
2539
+ idempotencyKey: options.verifyIdempotencyKey,
2540
+ maxAttempts: Number.isFinite(options.verifyJobMaxAttempts) ? options.verifyJobMaxAttempts : undefined,
2541
+ });
1996
2542
  }
1997
2543
  catch (verifyApiError) {
1998
2544
  if (planFilesForVerification.length === 0) {
@@ -2208,6 +2754,7 @@ async function verifyCommand(options) {
2208
2754
  if (governanceResult) {
2209
2755
  displayGovernanceInsights(governanceResult, { explain: options.explain });
2210
2756
  }
2757
+ console.log(chalk.dim(`\n Policy exceptions source: ${describePolicyExceptionSource(policyExceptionsSummary.sourceMode)}`));
2211
2758
  if (policyExceptionsSummary.suppressed > 0) {
2212
2759
  console.log(chalk.yellow(`\n⚠️ Policy exceptions applied: ${policyExceptionsSummary.suppressed}`));
2213
2760
  if (policyExceptionsSummary.matchedExceptionIds.length > 0) {
@@ -2596,6 +3143,9 @@ function buildGovernancePayload(governance, orgGovernanceSettings, options) {
2596
3143
  requireSignedAiLogs: orgGovernanceSettings.requireSignedAiLogs,
2597
3144
  requireManualApproval: orgGovernanceSettings.requireManualApproval,
2598
3145
  minimumManualApprovals: orgGovernanceSettings.minimumManualApprovals,
3146
+ ...(orgGovernanceSettings.policyGovernance
3147
+ ? { policyGovernance: orgGovernanceSettings.policyGovernance }
3148
+ : {}),
2599
3149
  updatedAt: orgGovernanceSettings.updatedAt || null,
2600
3150
  }
2601
3151
  : null,