@neurcode-ai/cli 0.9.35 → 0.9.36
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/dist/api-client.d.ts +45 -0
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +12 -0
- package/dist/api-client.js.map +1 -1
- package/dist/commands/policy.d.ts.map +1 -1
- package/dist/commands/policy.js +282 -20
- package/dist/commands/policy.js.map +1 -1
- package/dist/commands/verify.d.ts +2 -0
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +89 -3
- package/dist/commands/verify.js.map +1 -1
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/policy-exceptions.d.ts +11 -1
- package/dist/utils/policy-exceptions.d.ts.map +1 -1
- package/dist/utils/policy-exceptions.js +94 -6
- package/dist/utils/policy-exceptions.js.map +1 -1
- package/dist/utils/policy-governance.d.ts +22 -1
- package/dist/utils/policy-governance.d.ts.map +1 -1
- package/dist/utils/policy-governance.js +178 -14
- package/dist/utils/policy-governance.js.map +1 -1
- package/dist/utils/policy-packs.d.ts +1 -1
- package/dist/utils/policy-packs.d.ts.map +1 -1
- package/dist/utils/policy-packs.js +185 -0
- package/dist/utils/policy-packs.js.map +1 -1
- package/package.json +15 -13
- package/LICENSE +0 -201
package/dist/commands/policy.js
CHANGED
|
@@ -63,6 +63,16 @@ function resolveActor(explicit) {
|
|
|
63
63
|
return explicit.trim();
|
|
64
64
|
return process.env.NEURCODE_ACTOR || process.env.GITHUB_ACTOR || process.env.USER || 'unknown';
|
|
65
65
|
}
|
|
66
|
+
function validateExceptionWindowByGovernance(expiresAt, maxExpiryDays) {
|
|
67
|
+
const expiryMs = Date.parse(expiresAt);
|
|
68
|
+
if (!Number.isFinite(expiryMs)) {
|
|
69
|
+
throw new Error('expiresAt must be a valid ISO datetime');
|
|
70
|
+
}
|
|
71
|
+
const maxWindowMs = Math.max(1, maxExpiryDays) * 24 * 60 * 60 * 1000;
|
|
72
|
+
if (expiryMs - Date.now() > maxWindowMs) {
|
|
73
|
+
throw new Error(`exception expiry exceeds governance max window (${maxExpiryDays} days)`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
66
76
|
async function resolveCustomPolicies(client, includeDashboardPolicies, requireDashboardPolicies) {
|
|
67
77
|
if (!includeDashboardPolicies) {
|
|
68
78
|
return {
|
|
@@ -153,7 +163,7 @@ function policyCommand(program) {
|
|
|
153
163
|
policy
|
|
154
164
|
.command('install')
|
|
155
165
|
.description('Install a policy pack for this repository')
|
|
156
|
-
.argument('<pack-id>', 'Policy pack ID (
|
|
166
|
+
.argument('<pack-id>', 'Policy pack ID (run `neurcode policy list` for all available stacks)')
|
|
157
167
|
.option('--force', 'Replace any existing installed policy pack')
|
|
158
168
|
.option('--json', 'Output as JSON')
|
|
159
169
|
.action((packId, options) => {
|
|
@@ -466,8 +476,61 @@ function policyCommand(program) {
|
|
|
466
476
|
governance
|
|
467
477
|
.command('status')
|
|
468
478
|
.description('Show policy governance settings for this repository')
|
|
479
|
+
.option('--org', 'Fetch centralized organization governance settings from Neurcode Cloud')
|
|
469
480
|
.option('--json', 'Output as JSON')
|
|
470
|
-
.action((options) => {
|
|
481
|
+
.action(async (options) => {
|
|
482
|
+
if (options.org) {
|
|
483
|
+
try {
|
|
484
|
+
const config = loadPolicyRuntimeConfig();
|
|
485
|
+
const client = new api_client_1.ApiClient(config);
|
|
486
|
+
const settings = await client.getOrgGovernanceSettings();
|
|
487
|
+
if (!settings) {
|
|
488
|
+
throw new Error('Organization governance settings not found');
|
|
489
|
+
}
|
|
490
|
+
const policyGovernance = settings.policyGovernance || null;
|
|
491
|
+
if (options.json) {
|
|
492
|
+
console.log(JSON.stringify({
|
|
493
|
+
source: 'org',
|
|
494
|
+
settings,
|
|
495
|
+
}, null, 2));
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
console.log(chalk.bold('\n🏢 Org Policy Governance\n'));
|
|
499
|
+
console.log(chalk.dim('Source: Neurcode Cloud (/api/v1/org/governance/settings)'));
|
|
500
|
+
if (!policyGovernance) {
|
|
501
|
+
console.log(chalk.yellow('No org-level policy governance configured.\n'));
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
console.log(chalk.dim(`Exception approvals required: ${policyGovernance.exceptionApprovals?.required ? 'yes' : 'no'}`));
|
|
505
|
+
console.log(chalk.dim(`Minimum approvals: ${policyGovernance.exceptionApprovals?.minApprovals ?? 1}`));
|
|
506
|
+
console.log(chalk.dim(`Disallow self approval: ${policyGovernance.exceptionApprovals?.disallowSelfApproval !== false ? 'yes' : 'no'}`));
|
|
507
|
+
console.log(chalk.dim(`Reason required: ${policyGovernance.exceptionApprovals?.requireReason !== false ? 'yes' : 'no'}`));
|
|
508
|
+
console.log(chalk.dim(`Minimum reason length: ${policyGovernance.exceptionApprovals?.minReasonLength ?? 12}`));
|
|
509
|
+
console.log(chalk.dim(`Maximum exception window (days): ${policyGovernance.exceptionApprovals?.maxExpiryDays ?? 30}`));
|
|
510
|
+
console.log(chalk.dim(`Allowed approvers: ${Array.isArray(policyGovernance.exceptionApprovals?.allowedApprovers)
|
|
511
|
+
&& policyGovernance.exceptionApprovals.allowedApprovers.length > 0
|
|
512
|
+
? policyGovernance.exceptionApprovals.allowedApprovers.join(', ')
|
|
513
|
+
: '(any)'}`));
|
|
514
|
+
console.log(chalk.dim(`Critical rule patterns: ${Array.isArray(policyGovernance.exceptionApprovals?.criticalRulePatterns)
|
|
515
|
+
&& policyGovernance.exceptionApprovals.criticalRulePatterns.length > 0
|
|
516
|
+
? policyGovernance.exceptionApprovals.criticalRulePatterns.join(', ')
|
|
517
|
+
: '(none)'}`));
|
|
518
|
+
console.log(chalk.dim(`Critical minimum approvals: ${policyGovernance.exceptionApprovals?.criticalMinApprovals ?? 2}`));
|
|
519
|
+
console.log(chalk.dim(`Require audit integrity: ${policyGovernance.audit?.requireIntegrity ? 'yes' : 'no'}`));
|
|
520
|
+
console.log(chalk.dim(`Updated at: ${settings.updatedAt || '(unknown)'}`));
|
|
521
|
+
console.log('');
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
catch (error) {
|
|
525
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
526
|
+
if (options.json) {
|
|
527
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
528
|
+
process.exit(1);
|
|
529
|
+
}
|
|
530
|
+
console.error(chalk.red(`\n❌ ${message}\n`));
|
|
531
|
+
process.exit(1);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
471
534
|
const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
|
|
472
535
|
const config = (0, policy_governance_1.readPolicyGovernanceConfig)(cwd);
|
|
473
536
|
if (options.json) {
|
|
@@ -482,37 +545,155 @@ function policyCommand(program) {
|
|
|
482
545
|
console.log(chalk.dim(`Exception approvals required: ${config.exceptionApprovals.required ? 'yes' : 'no'}`));
|
|
483
546
|
console.log(chalk.dim(`Minimum approvals: ${config.exceptionApprovals.minApprovals}`));
|
|
484
547
|
console.log(chalk.dim(`Disallow self approval: ${config.exceptionApprovals.disallowSelfApproval ? 'yes' : 'no'}`));
|
|
548
|
+
console.log(chalk.dim(`Reason required: ${config.exceptionApprovals.requireReason ? 'yes' : 'no'}`));
|
|
549
|
+
console.log(chalk.dim(`Minimum reason length: ${config.exceptionApprovals.minReasonLength}`));
|
|
550
|
+
console.log(chalk.dim(`Maximum exception window (days): ${config.exceptionApprovals.maxExpiryDays}`));
|
|
485
551
|
console.log(chalk.dim(`Allowed approvers: ${config.exceptionApprovals.allowedApprovers.length > 0 ? config.exceptionApprovals.allowedApprovers.join(', ') : '(any)'}`));
|
|
552
|
+
console.log(chalk.dim(`Critical rule patterns: ${config.exceptionApprovals.criticalRulePatterns.length > 0 ? config.exceptionApprovals.criticalRulePatterns.join(', ') : '(none)'}`));
|
|
553
|
+
console.log(chalk.dim(`Critical minimum approvals: ${config.exceptionApprovals.criticalMinApprovals}`));
|
|
486
554
|
console.log(chalk.dim(`Require audit integrity: ${config.audit.requireIntegrity ? 'yes' : 'no'}`));
|
|
487
555
|
console.log('');
|
|
488
556
|
});
|
|
489
557
|
governance
|
|
490
558
|
.command('set')
|
|
491
559
|
.description('Update policy governance settings')
|
|
560
|
+
.option('--org', 'Update centralized organization governance settings in Neurcode Cloud')
|
|
492
561
|
.option('--require-approval', 'Require approvals before exceptions become effective')
|
|
493
562
|
.option('--no-require-approval', 'Do not require approvals for exceptions')
|
|
494
563
|
.option('--min-approvals <n>', 'Minimum approvals required when approval mode is enabled', (value) => parseInt(value, 10))
|
|
495
564
|
.option('--allow-self-approval', 'Allow requester to approve their own exception')
|
|
496
565
|
.option('--restrict-approvers <csv>', 'Comma-separated allow-list of approver identities')
|
|
497
566
|
.option('--clear-approvers', 'Clear approver allow-list (allow any approver)')
|
|
567
|
+
.option('--require-reason', 'Require non-trivial exception reason text')
|
|
568
|
+
.option('--no-require-reason', 'Do not enforce minimum reason text length')
|
|
569
|
+
.option('--min-reason-length <n>', 'Minimum exception reason length (default: 12)', (value) => parseInt(value, 10))
|
|
570
|
+
.option('--max-expiry-days <n>', 'Maximum exception expiry window in days (default: 30)', (value) => parseInt(value, 10))
|
|
571
|
+
.option('--critical-rules <csv>', 'Comma-separated critical rule patterns requiring elevated approvals')
|
|
572
|
+
.option('--clear-critical-rules', 'Clear critical rule patterns')
|
|
573
|
+
.option('--critical-min-approvals <n>', 'Minimum approvals for critical rule exceptions (default: 2)', (value) => parseInt(value, 10))
|
|
498
574
|
.option('--require-audit-integrity', 'Fail verify if policy audit chain integrity is broken')
|
|
499
575
|
.option('--no-require-audit-integrity', 'Do not enforce policy audit integrity in verify')
|
|
500
576
|
.option('--json', 'Output as JSON')
|
|
501
|
-
.action((options) => {
|
|
577
|
+
.action(async (options) => {
|
|
578
|
+
const hasRestrictApprovers = typeof options.restrictApprovers === 'string';
|
|
579
|
+
const hasCriticalRules = typeof options.criticalRules === 'string';
|
|
580
|
+
const parsedApprovers = options.clearApprovers
|
|
581
|
+
? []
|
|
582
|
+
: hasRestrictApprovers
|
|
583
|
+
? options.restrictApprovers
|
|
584
|
+
.split(',')
|
|
585
|
+
.map((item) => item.trim())
|
|
586
|
+
.filter(Boolean)
|
|
587
|
+
: undefined;
|
|
588
|
+
const parsedCriticalRules = options.clearCriticalRules
|
|
589
|
+
? []
|
|
590
|
+
: hasCriticalRules
|
|
591
|
+
? options.criticalRules
|
|
592
|
+
.split(',')
|
|
593
|
+
.map((item) => item.trim())
|
|
594
|
+
.filter(Boolean)
|
|
595
|
+
: undefined;
|
|
596
|
+
if (options.org) {
|
|
597
|
+
try {
|
|
598
|
+
const exceptionApprovalsPatch = {};
|
|
599
|
+
if (typeof options.requireApproval === 'boolean') {
|
|
600
|
+
exceptionApprovalsPatch.required = options.requireApproval;
|
|
601
|
+
}
|
|
602
|
+
if (Number.isFinite(options.minApprovals)) {
|
|
603
|
+
exceptionApprovalsPatch.minApprovals = options.minApprovals;
|
|
604
|
+
}
|
|
605
|
+
if (typeof options.allowSelfApproval === 'boolean') {
|
|
606
|
+
exceptionApprovalsPatch.disallowSelfApproval = !options.allowSelfApproval;
|
|
607
|
+
}
|
|
608
|
+
if (parsedApprovers) {
|
|
609
|
+
exceptionApprovalsPatch.allowedApprovers = parsedApprovers;
|
|
610
|
+
}
|
|
611
|
+
if (typeof options.requireReason === 'boolean') {
|
|
612
|
+
exceptionApprovalsPatch.requireReason = options.requireReason;
|
|
613
|
+
}
|
|
614
|
+
if (Number.isFinite(options.minReasonLength)) {
|
|
615
|
+
exceptionApprovalsPatch.minReasonLength = options.minReasonLength;
|
|
616
|
+
}
|
|
617
|
+
if (Number.isFinite(options.maxExpiryDays)) {
|
|
618
|
+
exceptionApprovalsPatch.maxExpiryDays = options.maxExpiryDays;
|
|
619
|
+
}
|
|
620
|
+
if (parsedCriticalRules) {
|
|
621
|
+
exceptionApprovalsPatch.criticalRulePatterns = parsedCriticalRules;
|
|
622
|
+
}
|
|
623
|
+
if (Number.isFinite(options.criticalMinApprovals)) {
|
|
624
|
+
exceptionApprovalsPatch.criticalMinApprovals = options.criticalMinApprovals;
|
|
625
|
+
}
|
|
626
|
+
const auditPatch = {};
|
|
627
|
+
if (typeof options.requireAuditIntegrity === 'boolean') {
|
|
628
|
+
auditPatch.requireIntegrity = options.requireAuditIntegrity;
|
|
629
|
+
}
|
|
630
|
+
const policyGovernancePatch = {};
|
|
631
|
+
if (Object.keys(exceptionApprovalsPatch).length > 0) {
|
|
632
|
+
policyGovernancePatch.exceptionApprovals = exceptionApprovalsPatch;
|
|
633
|
+
}
|
|
634
|
+
if (Object.keys(auditPatch).length > 0) {
|
|
635
|
+
policyGovernancePatch.audit = auditPatch;
|
|
636
|
+
}
|
|
637
|
+
const config = loadPolicyRuntimeConfig();
|
|
638
|
+
const client = new api_client_1.ApiClient(config);
|
|
639
|
+
const settings = await client.updateOrgGovernanceSettings({
|
|
640
|
+
policyGovernance: policyGovernancePatch,
|
|
641
|
+
});
|
|
642
|
+
if (!settings) {
|
|
643
|
+
throw new Error('Failed to update org governance settings');
|
|
644
|
+
}
|
|
645
|
+
const next = settings.policyGovernance;
|
|
646
|
+
if (options.json) {
|
|
647
|
+
console.log(JSON.stringify({
|
|
648
|
+
source: 'org',
|
|
649
|
+
settings,
|
|
650
|
+
}, null, 2));
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
console.log(chalk.green('\n✅ Organization policy governance updated.\n'));
|
|
654
|
+
console.log(chalk.dim('Source: Neurcode Cloud (/api/v1/org/governance/settings)'));
|
|
655
|
+
if (!next) {
|
|
656
|
+
console.log(chalk.yellow('No org-level policy governance payload returned.\n'));
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
console.log(chalk.dim(`Approval required: ${next.exceptionApprovals?.required ? 'yes' : 'no'}`));
|
|
660
|
+
console.log(chalk.dim(`Min approvals: ${next.exceptionApprovals?.minApprovals ?? 1}`));
|
|
661
|
+
console.log(chalk.dim(`Disallow self approval: ${next.exceptionApprovals?.disallowSelfApproval !== false ? 'yes' : 'no'}`));
|
|
662
|
+
console.log(chalk.dim(`Reason required: ${next.exceptionApprovals?.requireReason !== false ? 'yes' : 'no'}`));
|
|
663
|
+
console.log(chalk.dim(`Min reason length: ${next.exceptionApprovals?.minReasonLength ?? 12}`));
|
|
664
|
+
console.log(chalk.dim(`Max expiry days: ${next.exceptionApprovals?.maxExpiryDays ?? 30}`));
|
|
665
|
+
console.log(chalk.dim(`Critical min approvals: ${next.exceptionApprovals?.criticalMinApprovals ?? 2}`));
|
|
666
|
+
console.log(chalk.dim(`Critical rule patterns: ${Array.isArray(next.exceptionApprovals?.criticalRulePatterns)
|
|
667
|
+
&& next.exceptionApprovals.criticalRulePatterns.length > 0
|
|
668
|
+
? next.exceptionApprovals.criticalRulePatterns.join(', ')
|
|
669
|
+
: '(none)'}`));
|
|
670
|
+
console.log(chalk.dim(`Require audit integrity: ${next.audit?.requireIntegrity ? 'yes' : 'no'}`));
|
|
671
|
+
console.log(chalk.dim(`Updated at: ${settings.updatedAt || '(unknown)'}`));
|
|
672
|
+
console.log('');
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
catch (error) {
|
|
676
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
677
|
+
if (options.json) {
|
|
678
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
679
|
+
process.exit(1);
|
|
680
|
+
}
|
|
681
|
+
console.error(chalk.red(`\n❌ ${message}\n`));
|
|
682
|
+
process.exit(1);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
502
685
|
const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
|
|
503
686
|
try {
|
|
504
687
|
const next = (0, policy_governance_1.updatePolicyGovernanceConfig)(cwd, {
|
|
505
688
|
required: typeof options.requireApproval === 'boolean' ? options.requireApproval : undefined,
|
|
506
689
|
minApprovals: Number.isFinite(options.minApprovals) ? options.minApprovals : undefined,
|
|
507
690
|
disallowSelfApproval: typeof options.allowSelfApproval === 'boolean' ? !options.allowSelfApproval : undefined,
|
|
508
|
-
allowedApprovers:
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
.filter(Boolean)
|
|
515
|
-
: undefined,
|
|
691
|
+
allowedApprovers: parsedApprovers,
|
|
692
|
+
requireReason: typeof options.requireReason === 'boolean' ? options.requireReason : undefined,
|
|
693
|
+
minReasonLength: Number.isFinite(options.minReasonLength) ? options.minReasonLength : undefined,
|
|
694
|
+
maxExpiryDays: Number.isFinite(options.maxExpiryDays) ? options.maxExpiryDays : undefined,
|
|
695
|
+
criticalRulePatterns: parsedCriticalRules,
|
|
696
|
+
criticalMinApprovals: Number.isFinite(options.criticalMinApprovals) ? options.criticalMinApprovals : undefined,
|
|
516
697
|
requireAuditIntegrity: typeof options.requireAuditIntegrity === 'boolean' ? options.requireAuditIntegrity : undefined,
|
|
517
698
|
});
|
|
518
699
|
try {
|
|
@@ -526,6 +707,11 @@ function policyCommand(program) {
|
|
|
526
707
|
minApprovals: next.exceptionApprovals.minApprovals,
|
|
527
708
|
disallowSelfApproval: next.exceptionApprovals.disallowSelfApproval,
|
|
528
709
|
allowedApprovers: next.exceptionApprovals.allowedApprovers,
|
|
710
|
+
requireReason: next.exceptionApprovals.requireReason,
|
|
711
|
+
minReasonLength: next.exceptionApprovals.minReasonLength,
|
|
712
|
+
maxExpiryDays: next.exceptionApprovals.maxExpiryDays,
|
|
713
|
+
criticalRulePatterns: next.exceptionApprovals.criticalRulePatterns,
|
|
714
|
+
criticalMinApprovals: next.exceptionApprovals.criticalMinApprovals,
|
|
529
715
|
requireAuditIntegrity: next.audit.requireIntegrity,
|
|
530
716
|
},
|
|
531
717
|
});
|
|
@@ -542,6 +728,11 @@ function policyCommand(program) {
|
|
|
542
728
|
console.log(chalk.dim(`Approval required: ${next.exceptionApprovals.required ? 'yes' : 'no'}`));
|
|
543
729
|
console.log(chalk.dim(`Min approvals: ${next.exceptionApprovals.minApprovals}`));
|
|
544
730
|
console.log(chalk.dim(`Disallow self approval: ${next.exceptionApprovals.disallowSelfApproval ? 'yes' : 'no'}`));
|
|
731
|
+
console.log(chalk.dim(`Reason required: ${next.exceptionApprovals.requireReason ? 'yes' : 'no'}`));
|
|
732
|
+
console.log(chalk.dim(`Min reason length: ${next.exceptionApprovals.minReasonLength}`));
|
|
733
|
+
console.log(chalk.dim(`Max expiry days: ${next.exceptionApprovals.maxExpiryDays}`));
|
|
734
|
+
console.log(chalk.dim(`Critical min approvals: ${next.exceptionApprovals.criticalMinApprovals}`));
|
|
735
|
+
console.log(chalk.dim(`Critical rule patterns: ${next.exceptionApprovals.criticalRulePatterns.length > 0 ? next.exceptionApprovals.criticalRulePatterns.join(', ') : '(none)'}`));
|
|
545
736
|
console.log(chalk.dim(`Require audit integrity: ${next.audit.requireIntegrity ? 'yes' : 'no'}`));
|
|
546
737
|
console.log(chalk.dim('Commit governance + audit files so CI can enforce approval and integrity rules.'));
|
|
547
738
|
console.log('');
|
|
@@ -609,17 +800,27 @@ function policyCommand(program) {
|
|
|
609
800
|
if (allowedApprovers.size > 0) {
|
|
610
801
|
effectiveApprovals = effectiveApprovals.filter((item) => allowedApprovers.has(item.approver.toLowerCase()));
|
|
611
802
|
}
|
|
803
|
+
const requiredApprovalResolution = (0, policy_governance_1.resolveRequiredApprovalsForRule)(entry.rulePattern, governance);
|
|
804
|
+
const requiredApprovals = governance.exceptionApprovals.required
|
|
805
|
+
? requiredApprovalResolution.requiredApprovals
|
|
806
|
+
: 0;
|
|
807
|
+
const reasonValid = !governance.exceptionApprovals.requireReason
|
|
808
|
+
|| (entry.reason || '').trim().length >= governance.exceptionApprovals.minReasonLength;
|
|
612
809
|
const status = !entry.active || !unexpired
|
|
613
810
|
? 'inactive'
|
|
614
|
-
: !
|
|
615
|
-
? '
|
|
616
|
-
:
|
|
617
|
-
? '
|
|
618
|
-
:
|
|
811
|
+
: !reasonValid
|
|
812
|
+
? 'invalid_reason'
|
|
813
|
+
: !governance.exceptionApprovals.required
|
|
814
|
+
? 'active'
|
|
815
|
+
: effectiveApprovals.length >= requiredApprovals
|
|
816
|
+
? 'approved'
|
|
817
|
+
: 'pending';
|
|
619
818
|
return {
|
|
620
819
|
...entry,
|
|
621
820
|
status,
|
|
622
821
|
effectiveApprovals: effectiveApprovals.length,
|
|
822
|
+
requiredApprovals,
|
|
823
|
+
criticalRule: requiredApprovalResolution.critical,
|
|
623
824
|
};
|
|
624
825
|
});
|
|
625
826
|
const items = options.all ? withStatus : withStatus.filter((entry) => entry.status !== 'inactive');
|
|
@@ -645,7 +846,10 @@ function policyCommand(program) {
|
|
|
645
846
|
console.log(chalk.dim(` status=${entry.status || (entry.active ? 'active' : 'inactive')}`));
|
|
646
847
|
console.log(chalk.dim(` rule=${entry.rulePattern} file=${entry.filePattern}`));
|
|
647
848
|
console.log(chalk.dim(` expires=${entry.expiresAt} active=${entry.active ? 'yes' : 'no'}`));
|
|
648
|
-
console.log(chalk.dim(` approvals=${entry.approvals.length}
|
|
849
|
+
console.log(chalk.dim(` approvals=${entry.approvals.length}` +
|
|
850
|
+
`${typeof entry.effectiveApprovals === 'number' ? ` (effective=${entry.effectiveApprovals})` : ''}` +
|
|
851
|
+
`${typeof entry.requiredApprovals === 'number' && entry.requiredApprovals > 0 ? ` required=${entry.requiredApprovals}` : ''}` +
|
|
852
|
+
`${entry.criticalRule ? ' critical=yes' : ''}`));
|
|
649
853
|
console.log(chalk.dim(` reason=${entry.reason}`));
|
|
650
854
|
console.log(chalk.dim(` requestedBy=${entry.requestedBy || entry.createdBy || 'unknown'}`));
|
|
651
855
|
if (entry.ticket) {
|
|
@@ -682,6 +886,12 @@ function policyCommand(program) {
|
|
|
682
886
|
expiresAt: options.expiresAt,
|
|
683
887
|
expiresInDays: options.expiresInDays,
|
|
684
888
|
});
|
|
889
|
+
const governance = (0, policy_governance_1.readPolicyGovernanceConfig)(cwd);
|
|
890
|
+
if (governance.exceptionApprovals.requireReason
|
|
891
|
+
&& options.reason.trim().length < governance.exceptionApprovals.minReasonLength) {
|
|
892
|
+
throw new Error(`reason must be at least ${governance.exceptionApprovals.minReasonLength} characters (governance policy)`);
|
|
893
|
+
}
|
|
894
|
+
validateExceptionWindowByGovernance(expiresAt, governance.exceptionApprovals.maxExpiryDays);
|
|
685
895
|
const createdBy = resolveActor();
|
|
686
896
|
const created = (0, policy_exceptions_1.addPolicyException)(cwd, {
|
|
687
897
|
rulePattern: options.rule,
|
|
@@ -693,7 +903,7 @@ function policyCommand(program) {
|
|
|
693
903
|
createdBy,
|
|
694
904
|
requestedBy: createdBy,
|
|
695
905
|
});
|
|
696
|
-
const
|
|
906
|
+
const approvalResolution = (0, policy_governance_1.resolveRequiredApprovalsForRule)(created.rulePattern, governance);
|
|
697
907
|
try {
|
|
698
908
|
(0, policy_audit_1.appendPolicyAuditEvent)(cwd, {
|
|
699
909
|
actor: createdBy,
|
|
@@ -705,6 +915,10 @@ function policyCommand(program) {
|
|
|
705
915
|
filePattern: created.filePattern,
|
|
706
916
|
expiresAt: created.expiresAt,
|
|
707
917
|
requireApproval: governance.exceptionApprovals.required,
|
|
918
|
+
requiredApprovals: governance.exceptionApprovals.required
|
|
919
|
+
? approvalResolution.requiredApprovals
|
|
920
|
+
: 0,
|
|
921
|
+
criticalRule: approvalResolution.critical,
|
|
708
922
|
},
|
|
709
923
|
});
|
|
710
924
|
}
|
|
@@ -716,6 +930,10 @@ function policyCommand(program) {
|
|
|
716
930
|
created,
|
|
717
931
|
path: (0, policy_exceptions_1.getPolicyExceptionsPath)(cwd),
|
|
718
932
|
requiresApproval: governance.exceptionApprovals.required,
|
|
933
|
+
requiredApprovals: governance.exceptionApprovals.required
|
|
934
|
+
? approvalResolution.requiredApprovals
|
|
935
|
+
: 0,
|
|
936
|
+
criticalRule: approvalResolution.critical,
|
|
719
937
|
}, null, 2));
|
|
720
938
|
return;
|
|
721
939
|
}
|
|
@@ -726,7 +944,8 @@ function policyCommand(program) {
|
|
|
726
944
|
console.log(chalk.dim(`Expires: ${created.expiresAt}`));
|
|
727
945
|
console.log(chalk.dim(`Reason: ${created.reason}`));
|
|
728
946
|
if (governance.exceptionApprovals.required) {
|
|
729
|
-
|
|
947
|
+
const requiredApprovals = approvalResolution.requiredApprovals;
|
|
948
|
+
console.log(chalk.yellow(`Approval required: ${requiredApprovals} approver(s) before this exception is active${approvalResolution.critical ? ' (critical rule gate)' : ''}.`));
|
|
730
949
|
}
|
|
731
950
|
if (created.ticket) {
|
|
732
951
|
console.log(chalk.dim(`Ticket: ${created.ticket}`));
|
|
@@ -754,6 +973,25 @@ function policyCommand(program) {
|
|
|
754
973
|
const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
|
|
755
974
|
const approver = resolveActor(options.by);
|
|
756
975
|
try {
|
|
976
|
+
const governance = (0, policy_governance_1.readPolicyGovernanceConfig)(cwd);
|
|
977
|
+
const target = (0, policy_exceptions_1.listPolicyExceptions)(cwd).all.find((entry) => entry.id === String(id).trim());
|
|
978
|
+
if (!target) {
|
|
979
|
+
if (options.json) {
|
|
980
|
+
console.log(JSON.stringify({ error: 'Exception not found' }, null, 2));
|
|
981
|
+
process.exit(1);
|
|
982
|
+
}
|
|
983
|
+
console.log(chalk.yellow('\n⚠️ Exception not found.\n'));
|
|
984
|
+
process.exit(1);
|
|
985
|
+
}
|
|
986
|
+
const normalizedApprover = approver.toLowerCase();
|
|
987
|
+
const requestedBy = (target.requestedBy || target.createdBy || '').toLowerCase();
|
|
988
|
+
if (governance.exceptionApprovals.disallowSelfApproval && requestedBy && normalizedApprover === requestedBy) {
|
|
989
|
+
throw new Error('self-approval is disallowed by governance policy');
|
|
990
|
+
}
|
|
991
|
+
if (governance.exceptionApprovals.allowedApprovers.length > 0
|
|
992
|
+
&& !governance.exceptionApprovals.allowedApprovers.map((item) => item.toLowerCase()).includes(normalizedApprover)) {
|
|
993
|
+
throw new Error('approver is not in governance allow-list');
|
|
994
|
+
}
|
|
757
995
|
const updated = (0, policy_exceptions_1.approvePolicyException)(cwd, String(id).trim(), {
|
|
758
996
|
approver,
|
|
759
997
|
comment: options.comment,
|
|
@@ -766,6 +1004,19 @@ function policyCommand(program) {
|
|
|
766
1004
|
console.log(chalk.yellow('\n⚠️ Exception not found.\n'));
|
|
767
1005
|
process.exit(1);
|
|
768
1006
|
}
|
|
1007
|
+
const requiredApprovalResolution = (0, policy_governance_1.resolveRequiredApprovalsForRule)(updated.rulePattern, governance);
|
|
1008
|
+
const acceptedApprovals = updated.approvals.filter((item) => {
|
|
1009
|
+
const actor = item.approver.toLowerCase();
|
|
1010
|
+
if (governance.exceptionApprovals.allowedApprovers.length > 0
|
|
1011
|
+
&& !governance.exceptionApprovals.allowedApprovers.map((entry) => entry.toLowerCase()).includes(actor)) {
|
|
1012
|
+
return false;
|
|
1013
|
+
}
|
|
1014
|
+
if (governance.exceptionApprovals.disallowSelfApproval && requestedBy && actor === requestedBy) {
|
|
1015
|
+
return false;
|
|
1016
|
+
}
|
|
1017
|
+
return true;
|
|
1018
|
+
});
|
|
1019
|
+
const effectiveApprovals = acceptedApprovals.length;
|
|
769
1020
|
try {
|
|
770
1021
|
(0, policy_audit_1.appendPolicyAuditEvent)(cwd, {
|
|
771
1022
|
actor: approver,
|
|
@@ -775,6 +1026,9 @@ function policyCommand(program) {
|
|
|
775
1026
|
metadata: {
|
|
776
1027
|
comment: options.comment || null,
|
|
777
1028
|
approvals: updated.approvals.length,
|
|
1029
|
+
effectiveApprovals,
|
|
1030
|
+
requiredApprovals: requiredApprovalResolution.requiredApprovals,
|
|
1031
|
+
criticalRule: requiredApprovalResolution.critical,
|
|
778
1032
|
},
|
|
779
1033
|
});
|
|
780
1034
|
}
|
|
@@ -785,12 +1039,20 @@ function policyCommand(program) {
|
|
|
785
1039
|
console.log(JSON.stringify({
|
|
786
1040
|
approved: true,
|
|
787
1041
|
exception: updated,
|
|
1042
|
+
effectiveApprovals,
|
|
1043
|
+
requiredApprovals: requiredApprovalResolution.requiredApprovals,
|
|
1044
|
+
approvalSatisfied: !governance.exceptionApprovals.required
|
|
1045
|
+
|| effectiveApprovals >= requiredApprovalResolution.requiredApprovals,
|
|
1046
|
+
criticalRule: requiredApprovalResolution.critical,
|
|
788
1047
|
}, null, 2));
|
|
789
1048
|
return;
|
|
790
1049
|
}
|
|
791
1050
|
console.log(chalk.green('\n✅ Policy exception approval recorded.\n'));
|
|
792
1051
|
console.log(chalk.dim(`ID: ${updated.id}`));
|
|
793
|
-
console.log(chalk.dim(`Approvals: ${updated.approvals.length}`));
|
|
1052
|
+
console.log(chalk.dim(`Approvals: ${updated.approvals.length} (effective=${effectiveApprovals})`));
|
|
1053
|
+
if (governance.exceptionApprovals.required) {
|
|
1054
|
+
console.log(chalk.dim(`Required approvals: ${requiredApprovalResolution.requiredApprovals}${requiredApprovalResolution.critical ? ' (critical rule gate)' : ''}`));
|
|
1055
|
+
}
|
|
794
1056
|
console.log(chalk.dim(`Approver: ${approver}`));
|
|
795
1057
|
console.log('');
|
|
796
1058
|
}
|