@neurcode-ai/cli 0.9.34 → 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/README.md +2 -1
- package/dist/api-client.d.ts +59 -1
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +16 -1
- package/dist/api-client.js.map +1 -1
- package/dist/commands/ask.d.ts.map +1 -1
- package/dist/commands/ask.js +15 -2
- package/dist/commands/ask.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +81 -2
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/policy.d.ts.map +1 -1
- package/dist/commands/policy.js +377 -20
- package/dist/commands/policy.js.map +1 -1
- package/dist/commands/verify.d.ts +8 -0
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +454 -59
- package/dist/commands/verify.js.map +1 -1
- package/dist/index.js +102 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/change-contract.d.ts +55 -0
- package/dist/utils/change-contract.d.ts.map +1 -0
- package/dist/utils/change-contract.js +158 -0
- package/dist/utils/change-contract.js.map +1 -0
- package/dist/utils/policy-audit.d.ts +2 -2
- package/dist/utils/policy-audit.d.ts.map +1 -1
- package/dist/utils/policy-audit.js.map +1 -1
- package/dist/utils/policy-compiler.d.ts +68 -0
- package/dist/utils/policy-compiler.d.ts.map +1 -0
- package/dist/utils/policy-compiler.js +170 -0
- package/dist/utils/policy-compiler.js.map +1 -0
- 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/dist/utils/project-root.d.ts +16 -0
- package/dist/utils/project-root.d.ts.map +1 -1
- package/dist/utils/project-root.js +123 -9
- package/dist/utils/project-root.js.map +1 -1
- package/dist/utils/scope-telemetry.d.ts +21 -0
- package/dist/utils/scope-telemetry.d.ts.map +1 -0
- package/dist/utils/scope-telemetry.js +35 -0
- package/dist/utils/scope-telemetry.js.map +1 -0
- package/package.json +15 -12
- package/LICENSE +0 -201
package/dist/commands/policy.js
CHANGED
|
@@ -9,6 +9,7 @@ const policy_exceptions_1 = require("../utils/policy-exceptions");
|
|
|
9
9
|
const policy_governance_1 = require("../utils/policy-governance");
|
|
10
10
|
const policy_audit_1 = require("../utils/policy-audit");
|
|
11
11
|
const policy_packs_1 = require("../utils/policy-packs");
|
|
12
|
+
const policy_compiler_1 = require("../utils/policy-compiler");
|
|
12
13
|
// Import chalk with fallback
|
|
13
14
|
let chalk;
|
|
14
15
|
try {
|
|
@@ -62,6 +63,16 @@ function resolveActor(explicit) {
|
|
|
62
63
|
return explicit.trim();
|
|
63
64
|
return process.env.NEURCODE_ACTOR || process.env.GITHUB_ACTOR || process.env.USER || 'unknown';
|
|
64
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
|
+
}
|
|
65
76
|
async function resolveCustomPolicies(client, includeDashboardPolicies, requireDashboardPolicies) {
|
|
66
77
|
if (!includeDashboardPolicies) {
|
|
67
78
|
return {
|
|
@@ -152,7 +163,7 @@ function policyCommand(program) {
|
|
|
152
163
|
policy
|
|
153
164
|
.command('install')
|
|
154
165
|
.description('Install a policy pack for this repository')
|
|
155
|
-
.argument('<pack-id>', 'Policy pack ID (
|
|
166
|
+
.argument('<pack-id>', 'Policy pack ID (run `neurcode policy list` for all available stacks)')
|
|
156
167
|
.option('--force', 'Replace any existing installed policy pack')
|
|
157
168
|
.option('--json', 'Output as JSON')
|
|
158
169
|
.action((packId, options) => {
|
|
@@ -365,14 +376,161 @@ function policyCommand(program) {
|
|
|
365
376
|
}
|
|
366
377
|
process.exit(pass ? 0 : 1);
|
|
367
378
|
});
|
|
379
|
+
policy
|
|
380
|
+
.command('compile')
|
|
381
|
+
.description('Compile deterministic policy constraints into a committed artifact')
|
|
382
|
+
.option('--intent <text>', 'Optional intent constraints to compile alongside policy rules')
|
|
383
|
+
.option('--no-dashboard', 'Exclude dashboard custom policies from compiled artifact')
|
|
384
|
+
.option('--require-dashboard', 'Fail if dashboard custom policies cannot be loaded')
|
|
385
|
+
.option('--output <path>', 'Output file path (default: neurcode.policy.compiled.json)')
|
|
386
|
+
.option('--json', 'Output as JSON')
|
|
387
|
+
.action(async (options) => {
|
|
388
|
+
const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
|
|
389
|
+
const config = loadPolicyRuntimeConfig();
|
|
390
|
+
const client = new api_client_1.ApiClient(config);
|
|
391
|
+
const includeDashboard = options.dashboard !== false;
|
|
392
|
+
try {
|
|
393
|
+
const customPolicyResolution = await resolveCustomPolicies(client, includeDashboard, options.requireDashboard === true);
|
|
394
|
+
const installedPack = (0, policy_packs_1.getInstalledPolicyPackRules)(cwd);
|
|
395
|
+
const customRules = customPolicyResolution.includeDashboardPolicies
|
|
396
|
+
? (0, custom_policy_rules_1.mapActiveCustomPoliciesToRules)(customPolicyResolution.customPolicies)
|
|
397
|
+
: [];
|
|
398
|
+
const snapshot = (0, policy_packs_1.buildPolicyStateSnapshot)({
|
|
399
|
+
policyPack: installedPack,
|
|
400
|
+
policyPackRules: installedPack?.rules || [],
|
|
401
|
+
customPolicies: customPolicyResolution.customPolicies,
|
|
402
|
+
customRules,
|
|
403
|
+
includeDashboardPolicies: customPolicyResolution.includeDashboardPolicies,
|
|
404
|
+
});
|
|
405
|
+
const compiled = (0, policy_compiler_1.buildCompiledPolicyArtifact)({
|
|
406
|
+
includeDashboardPolicies: customPolicyResolution.includeDashboardPolicies,
|
|
407
|
+
policyLockPath: (0, policy_packs_1.getPolicyLockPath)(cwd),
|
|
408
|
+
policyLockFingerprint: snapshot.effective.fingerprint,
|
|
409
|
+
policyPack: installedPack
|
|
410
|
+
? {
|
|
411
|
+
id: installedPack.packId,
|
|
412
|
+
name: installedPack.packName,
|
|
413
|
+
version: installedPack.version,
|
|
414
|
+
}
|
|
415
|
+
: null,
|
|
416
|
+
defaultRuleCount: snapshot.defaultRules.count,
|
|
417
|
+
policyPackRuleCount: installedPack?.rules.length || 0,
|
|
418
|
+
customRuleCount: customRules.length,
|
|
419
|
+
effectiveRuleCount: snapshot.effective.ruleCount,
|
|
420
|
+
intentConstraints: options.intent,
|
|
421
|
+
policyRules: customPolicyResolution.customPolicies.map((policy) => policy.rule_text),
|
|
422
|
+
});
|
|
423
|
+
const outputPath = (0, policy_compiler_1.writeCompiledPolicyArtifact)(cwd, compiled, options.output);
|
|
424
|
+
const readBack = (0, policy_compiler_1.readCompiledPolicyArtifact)(cwd, options.output);
|
|
425
|
+
try {
|
|
426
|
+
(0, policy_audit_1.appendPolicyAuditEvent)(cwd, {
|
|
427
|
+
actor: resolveActor(),
|
|
428
|
+
action: 'policy_compiled',
|
|
429
|
+
entityType: 'policy_compiled_artifact',
|
|
430
|
+
entityId: outputPath,
|
|
431
|
+
metadata: {
|
|
432
|
+
fingerprint: compiled.fingerprint,
|
|
433
|
+
deterministicRuleCount: compiled.compilation.deterministicRuleCount,
|
|
434
|
+
unmatchedStatements: compiled.compilation.unmatchedStatements.length,
|
|
435
|
+
dashboardMode: compiled.source.includeDashboardPolicies ? 'dashboard' : 'disabled',
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
// Non-blocking audit write.
|
|
441
|
+
}
|
|
442
|
+
if (options.json) {
|
|
443
|
+
console.log(JSON.stringify({
|
|
444
|
+
artifact: compiled,
|
|
445
|
+
path: outputPath,
|
|
446
|
+
resolvedPath: (0, policy_compiler_1.resolveCompiledPolicyPath)(cwd, options.output),
|
|
447
|
+
verified: readBack.artifact !== null,
|
|
448
|
+
warning: customPolicyResolution.dashboardWarning || null,
|
|
449
|
+
}, null, 2));
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
console.log(chalk.green('\n✅ Policy compilation complete\n'));
|
|
453
|
+
console.log(chalk.cyan(`Path: ${outputPath}`));
|
|
454
|
+
console.log(chalk.dim(`Fingerprint: ${compiled.fingerprint}`));
|
|
455
|
+
console.log(chalk.dim(`Deterministic rules: ${compiled.compilation.deterministicRuleCount}`));
|
|
456
|
+
console.log(chalk.dim(`Unmatched statements: ${compiled.compilation.unmatchedStatements.length}`));
|
|
457
|
+
console.log(chalk.dim(`Policy source: ${compiled.source.includeDashboardPolicies ? 'dashboard + local packs' : 'local packs only'}`));
|
|
458
|
+
if (customPolicyResolution.dashboardWarning) {
|
|
459
|
+
console.log(chalk.yellow(`\n⚠️ ${customPolicyResolution.dashboardWarning}`));
|
|
460
|
+
}
|
|
461
|
+
console.log(chalk.dim('Run `neurcode verify --enforce-change-contract` to enforce this compiled contract.\n'));
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
465
|
+
if (options.json) {
|
|
466
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
467
|
+
process.exit(1);
|
|
468
|
+
}
|
|
469
|
+
console.error(chalk.red(`\n❌ ${message}\n`));
|
|
470
|
+
process.exit(1);
|
|
471
|
+
}
|
|
472
|
+
});
|
|
368
473
|
const governance = policy
|
|
369
474
|
.command('governance')
|
|
370
475
|
.description('Configure exception approval and policy audit governance');
|
|
371
476
|
governance
|
|
372
477
|
.command('status')
|
|
373
478
|
.description('Show policy governance settings for this repository')
|
|
479
|
+
.option('--org', 'Fetch centralized organization governance settings from Neurcode Cloud')
|
|
374
480
|
.option('--json', 'Output as JSON')
|
|
375
|
-
.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
|
+
}
|
|
376
534
|
const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
|
|
377
535
|
const config = (0, policy_governance_1.readPolicyGovernanceConfig)(cwd);
|
|
378
536
|
if (options.json) {
|
|
@@ -387,37 +545,155 @@ function policyCommand(program) {
|
|
|
387
545
|
console.log(chalk.dim(`Exception approvals required: ${config.exceptionApprovals.required ? 'yes' : 'no'}`));
|
|
388
546
|
console.log(chalk.dim(`Minimum approvals: ${config.exceptionApprovals.minApprovals}`));
|
|
389
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}`));
|
|
390
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}`));
|
|
391
554
|
console.log(chalk.dim(`Require audit integrity: ${config.audit.requireIntegrity ? 'yes' : 'no'}`));
|
|
392
555
|
console.log('');
|
|
393
556
|
});
|
|
394
557
|
governance
|
|
395
558
|
.command('set')
|
|
396
559
|
.description('Update policy governance settings')
|
|
560
|
+
.option('--org', 'Update centralized organization governance settings in Neurcode Cloud')
|
|
397
561
|
.option('--require-approval', 'Require approvals before exceptions become effective')
|
|
398
562
|
.option('--no-require-approval', 'Do not require approvals for exceptions')
|
|
399
563
|
.option('--min-approvals <n>', 'Minimum approvals required when approval mode is enabled', (value) => parseInt(value, 10))
|
|
400
564
|
.option('--allow-self-approval', 'Allow requester to approve their own exception')
|
|
401
565
|
.option('--restrict-approvers <csv>', 'Comma-separated allow-list of approver identities')
|
|
402
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))
|
|
403
574
|
.option('--require-audit-integrity', 'Fail verify if policy audit chain integrity is broken')
|
|
404
575
|
.option('--no-require-audit-integrity', 'Do not enforce policy audit integrity in verify')
|
|
405
576
|
.option('--json', 'Output as JSON')
|
|
406
|
-
.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
|
+
}
|
|
407
685
|
const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
|
|
408
686
|
try {
|
|
409
687
|
const next = (0, policy_governance_1.updatePolicyGovernanceConfig)(cwd, {
|
|
410
688
|
required: typeof options.requireApproval === 'boolean' ? options.requireApproval : undefined,
|
|
411
689
|
minApprovals: Number.isFinite(options.minApprovals) ? options.minApprovals : undefined,
|
|
412
690
|
disallowSelfApproval: typeof options.allowSelfApproval === 'boolean' ? !options.allowSelfApproval : undefined,
|
|
413
|
-
allowedApprovers:
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
.filter(Boolean)
|
|
420
|
-
: 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,
|
|
421
697
|
requireAuditIntegrity: typeof options.requireAuditIntegrity === 'boolean' ? options.requireAuditIntegrity : undefined,
|
|
422
698
|
});
|
|
423
699
|
try {
|
|
@@ -431,6 +707,11 @@ function policyCommand(program) {
|
|
|
431
707
|
minApprovals: next.exceptionApprovals.minApprovals,
|
|
432
708
|
disallowSelfApproval: next.exceptionApprovals.disallowSelfApproval,
|
|
433
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,
|
|
434
715
|
requireAuditIntegrity: next.audit.requireIntegrity,
|
|
435
716
|
},
|
|
436
717
|
});
|
|
@@ -447,6 +728,11 @@ function policyCommand(program) {
|
|
|
447
728
|
console.log(chalk.dim(`Approval required: ${next.exceptionApprovals.required ? 'yes' : 'no'}`));
|
|
448
729
|
console.log(chalk.dim(`Min approvals: ${next.exceptionApprovals.minApprovals}`));
|
|
449
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)'}`));
|
|
450
736
|
console.log(chalk.dim(`Require audit integrity: ${next.audit.requireIntegrity ? 'yes' : 'no'}`));
|
|
451
737
|
console.log(chalk.dim('Commit governance + audit files so CI can enforce approval and integrity rules.'));
|
|
452
738
|
console.log('');
|
|
@@ -514,17 +800,27 @@ function policyCommand(program) {
|
|
|
514
800
|
if (allowedApprovers.size > 0) {
|
|
515
801
|
effectiveApprovals = effectiveApprovals.filter((item) => allowedApprovers.has(item.approver.toLowerCase()));
|
|
516
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;
|
|
517
809
|
const status = !entry.active || !unexpired
|
|
518
810
|
? 'inactive'
|
|
519
|
-
: !
|
|
520
|
-
? '
|
|
521
|
-
:
|
|
522
|
-
? '
|
|
523
|
-
:
|
|
811
|
+
: !reasonValid
|
|
812
|
+
? 'invalid_reason'
|
|
813
|
+
: !governance.exceptionApprovals.required
|
|
814
|
+
? 'active'
|
|
815
|
+
: effectiveApprovals.length >= requiredApprovals
|
|
816
|
+
? 'approved'
|
|
817
|
+
: 'pending';
|
|
524
818
|
return {
|
|
525
819
|
...entry,
|
|
526
820
|
status,
|
|
527
821
|
effectiveApprovals: effectiveApprovals.length,
|
|
822
|
+
requiredApprovals,
|
|
823
|
+
criticalRule: requiredApprovalResolution.critical,
|
|
528
824
|
};
|
|
529
825
|
});
|
|
530
826
|
const items = options.all ? withStatus : withStatus.filter((entry) => entry.status !== 'inactive');
|
|
@@ -550,7 +846,10 @@ function policyCommand(program) {
|
|
|
550
846
|
console.log(chalk.dim(` status=${entry.status || (entry.active ? 'active' : 'inactive')}`));
|
|
551
847
|
console.log(chalk.dim(` rule=${entry.rulePattern} file=${entry.filePattern}`));
|
|
552
848
|
console.log(chalk.dim(` expires=${entry.expiresAt} active=${entry.active ? 'yes' : 'no'}`));
|
|
553
|
-
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' : ''}`));
|
|
554
853
|
console.log(chalk.dim(` reason=${entry.reason}`));
|
|
555
854
|
console.log(chalk.dim(` requestedBy=${entry.requestedBy || entry.createdBy || 'unknown'}`));
|
|
556
855
|
if (entry.ticket) {
|
|
@@ -587,6 +886,12 @@ function policyCommand(program) {
|
|
|
587
886
|
expiresAt: options.expiresAt,
|
|
588
887
|
expiresInDays: options.expiresInDays,
|
|
589
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);
|
|
590
895
|
const createdBy = resolveActor();
|
|
591
896
|
const created = (0, policy_exceptions_1.addPolicyException)(cwd, {
|
|
592
897
|
rulePattern: options.rule,
|
|
@@ -598,7 +903,7 @@ function policyCommand(program) {
|
|
|
598
903
|
createdBy,
|
|
599
904
|
requestedBy: createdBy,
|
|
600
905
|
});
|
|
601
|
-
const
|
|
906
|
+
const approvalResolution = (0, policy_governance_1.resolveRequiredApprovalsForRule)(created.rulePattern, governance);
|
|
602
907
|
try {
|
|
603
908
|
(0, policy_audit_1.appendPolicyAuditEvent)(cwd, {
|
|
604
909
|
actor: createdBy,
|
|
@@ -610,6 +915,10 @@ function policyCommand(program) {
|
|
|
610
915
|
filePattern: created.filePattern,
|
|
611
916
|
expiresAt: created.expiresAt,
|
|
612
917
|
requireApproval: governance.exceptionApprovals.required,
|
|
918
|
+
requiredApprovals: governance.exceptionApprovals.required
|
|
919
|
+
? approvalResolution.requiredApprovals
|
|
920
|
+
: 0,
|
|
921
|
+
criticalRule: approvalResolution.critical,
|
|
613
922
|
},
|
|
614
923
|
});
|
|
615
924
|
}
|
|
@@ -621,6 +930,10 @@ function policyCommand(program) {
|
|
|
621
930
|
created,
|
|
622
931
|
path: (0, policy_exceptions_1.getPolicyExceptionsPath)(cwd),
|
|
623
932
|
requiresApproval: governance.exceptionApprovals.required,
|
|
933
|
+
requiredApprovals: governance.exceptionApprovals.required
|
|
934
|
+
? approvalResolution.requiredApprovals
|
|
935
|
+
: 0,
|
|
936
|
+
criticalRule: approvalResolution.critical,
|
|
624
937
|
}, null, 2));
|
|
625
938
|
return;
|
|
626
939
|
}
|
|
@@ -631,7 +944,8 @@ function policyCommand(program) {
|
|
|
631
944
|
console.log(chalk.dim(`Expires: ${created.expiresAt}`));
|
|
632
945
|
console.log(chalk.dim(`Reason: ${created.reason}`));
|
|
633
946
|
if (governance.exceptionApprovals.required) {
|
|
634
|
-
|
|
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)' : ''}.`));
|
|
635
949
|
}
|
|
636
950
|
if (created.ticket) {
|
|
637
951
|
console.log(chalk.dim(`Ticket: ${created.ticket}`));
|
|
@@ -659,6 +973,25 @@ function policyCommand(program) {
|
|
|
659
973
|
const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
|
|
660
974
|
const approver = resolveActor(options.by);
|
|
661
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
|
+
}
|
|
662
995
|
const updated = (0, policy_exceptions_1.approvePolicyException)(cwd, String(id).trim(), {
|
|
663
996
|
approver,
|
|
664
997
|
comment: options.comment,
|
|
@@ -671,6 +1004,19 @@ function policyCommand(program) {
|
|
|
671
1004
|
console.log(chalk.yellow('\n⚠️ Exception not found.\n'));
|
|
672
1005
|
process.exit(1);
|
|
673
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;
|
|
674
1020
|
try {
|
|
675
1021
|
(0, policy_audit_1.appendPolicyAuditEvent)(cwd, {
|
|
676
1022
|
actor: approver,
|
|
@@ -680,6 +1026,9 @@ function policyCommand(program) {
|
|
|
680
1026
|
metadata: {
|
|
681
1027
|
comment: options.comment || null,
|
|
682
1028
|
approvals: updated.approvals.length,
|
|
1029
|
+
effectiveApprovals,
|
|
1030
|
+
requiredApprovals: requiredApprovalResolution.requiredApprovals,
|
|
1031
|
+
criticalRule: requiredApprovalResolution.critical,
|
|
683
1032
|
},
|
|
684
1033
|
});
|
|
685
1034
|
}
|
|
@@ -690,12 +1039,20 @@ function policyCommand(program) {
|
|
|
690
1039
|
console.log(JSON.stringify({
|
|
691
1040
|
approved: true,
|
|
692
1041
|
exception: updated,
|
|
1042
|
+
effectiveApprovals,
|
|
1043
|
+
requiredApprovals: requiredApprovalResolution.requiredApprovals,
|
|
1044
|
+
approvalSatisfied: !governance.exceptionApprovals.required
|
|
1045
|
+
|| effectiveApprovals >= requiredApprovalResolution.requiredApprovals,
|
|
1046
|
+
criticalRule: requiredApprovalResolution.critical,
|
|
693
1047
|
}, null, 2));
|
|
694
1048
|
return;
|
|
695
1049
|
}
|
|
696
1050
|
console.log(chalk.green('\n✅ Policy exception approval recorded.\n'));
|
|
697
1051
|
console.log(chalk.dim(`ID: ${updated.id}`));
|
|
698
|
-
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
|
+
}
|
|
699
1056
|
console.log(chalk.dim(`Approver: ${approver}`));
|
|
700
1057
|
console.log('');
|
|
701
1058
|
}
|