@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/verify.js
CHANGED
|
@@ -43,6 +43,7 @@ const child_process_1 = require("child_process");
|
|
|
43
43
|
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
|
+
const governance_runtime_1 = require("@neurcode-ai/governance-runtime");
|
|
46
47
|
const config_1 = require("../config");
|
|
47
48
|
const api_client_1 = require("../api-client");
|
|
48
49
|
const path_1 = require("path");
|
|
@@ -53,12 +54,15 @@ const box_1 = require("../utils/box");
|
|
|
53
54
|
const ignore_1 = require("../utils/ignore");
|
|
54
55
|
const project_root_1 = require("../utils/project-root");
|
|
55
56
|
const brain_context_1 = require("../utils/brain-context");
|
|
57
|
+
const scope_telemetry_1 = require("../utils/scope-telemetry");
|
|
56
58
|
const policy_packs_1 = require("../utils/policy-packs");
|
|
57
59
|
const custom_policy_rules_1 = require("../utils/custom-policy-rules");
|
|
58
60
|
const policy_exceptions_1 = require("../utils/policy-exceptions");
|
|
59
61
|
const policy_governance_1 = require("../utils/policy-governance");
|
|
60
62
|
const policy_audit_1 = require("../utils/policy-audit");
|
|
61
63
|
const governance_1 = require("../utils/governance");
|
|
64
|
+
const policy_compiler_1 = require("../utils/policy-compiler");
|
|
65
|
+
const change_contract_1 = require("../utils/change-contract");
|
|
62
66
|
const policy_1 = require("@neurcode-ai/policy");
|
|
63
67
|
// Import chalk with fallback
|
|
64
68
|
let chalk;
|
|
@@ -80,6 +84,7 @@ catch {
|
|
|
80
84
|
white: (str) => str,
|
|
81
85
|
};
|
|
82
86
|
}
|
|
87
|
+
;
|
|
83
88
|
/**
|
|
84
89
|
* Check if a file path should be excluded from verification analysis
|
|
85
90
|
* Excludes internal/system files that should not count towards plan adherence
|
|
@@ -234,6 +239,43 @@ async function buildEffectivePolicyRules(client, projectRoot, useDashboardPolici
|
|
|
234
239
|
includeDashboardPolicies: useDashboardPolicies,
|
|
235
240
|
};
|
|
236
241
|
}
|
|
242
|
+
function resolveCompiledPolicyMetadata(artifact, path) {
|
|
243
|
+
if (!artifact || !path) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
fingerprint: artifact.fingerprint,
|
|
248
|
+
deterministicRuleCount: artifact.compilation.deterministicRuleCount,
|
|
249
|
+
unmatchedStatements: artifact.compilation.unmatchedStatements.length,
|
|
250
|
+
sourcePath: path,
|
|
251
|
+
policyLockFingerprint: artifact.source.policyLockFingerprint,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function buildCompiledPolicyFromEffectiveRules(input) {
|
|
255
|
+
const policyStatements = [
|
|
256
|
+
...input.effectiveRules.customPolicies.map((policy) => policy.rule_text),
|
|
257
|
+
];
|
|
258
|
+
return (0, policy_compiler_1.buildCompiledPolicyArtifact)({
|
|
259
|
+
includeDashboardPolicies: input.effectiveRules.includeDashboardPolicies,
|
|
260
|
+
policyLockPath: input.policyLockEvaluation.lockPath,
|
|
261
|
+
policyLockFingerprint: input.policyLockEvaluation.lockPresent
|
|
262
|
+
? (0, policy_packs_1.readPolicyLockFile)(input.projectRoot).lock?.effective.fingerprint || null
|
|
263
|
+
: null,
|
|
264
|
+
policyPack: input.effectiveRules.policyPack
|
|
265
|
+
? {
|
|
266
|
+
id: input.effectiveRules.policyPack.packId,
|
|
267
|
+
name: input.effectiveRules.policyPack.packName,
|
|
268
|
+
version: input.effectiveRules.policyPack.version,
|
|
269
|
+
}
|
|
270
|
+
: null,
|
|
271
|
+
defaultRuleCount: (0, policy_engine_1.createDefaultPolicy)().rules.length,
|
|
272
|
+
policyPackRuleCount: input.effectiveRules.policyPackRules.length,
|
|
273
|
+
customRuleCount: input.effectiveRules.customRules.length,
|
|
274
|
+
effectiveRuleCount: input.effectiveRules.allRules.length,
|
|
275
|
+
intentConstraints: input.intentConstraints,
|
|
276
|
+
policyRules: policyStatements,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
237
279
|
const POLICY_AUDIT_FILE = 'neurcode.policy.audit.log.jsonl';
|
|
238
280
|
function isEnabledFlag(value) {
|
|
239
281
|
if (!value)
|
|
@@ -321,8 +363,14 @@ function resolvePolicyDecisionFromViolations(violations) {
|
|
|
321
363
|
}
|
|
322
364
|
function explainExceptionEligibilityReason(reason) {
|
|
323
365
|
switch (reason) {
|
|
366
|
+
case 'reason_required':
|
|
367
|
+
return 'exception reason does not meet governance minimum length';
|
|
368
|
+
case 'duration_exceeds_max':
|
|
369
|
+
return 'exception expiry window exceeds governance maximum duration';
|
|
324
370
|
case 'approval_required':
|
|
325
371
|
return 'exception exists but approvals are required';
|
|
372
|
+
case 'critical_approvals_required':
|
|
373
|
+
return 'critical rule exception requires additional independent approvals';
|
|
326
374
|
case 'insufficient_approvals':
|
|
327
375
|
return 'exception exists but approval threshold is not met';
|
|
328
376
|
case 'self_approval_only':
|
|
@@ -354,13 +402,30 @@ async function recordVerificationIfRequested(options, config, payload) {
|
|
|
354
402
|
}
|
|
355
403
|
return;
|
|
356
404
|
}
|
|
357
|
-
await reportVerification(payload.grade, payload.violations, payload.verifyResult, config.apiKey, config.apiUrl || 'https://api.neurcode.com', payload.projectId, payload.jsonMode, payload.governance);
|
|
405
|
+
await reportVerification(payload.grade, payload.violations, payload.verifyResult, config.apiKey, config.apiUrl || 'https://api.neurcode.com', payload.projectId, payload.jsonMode, payload.governance, payload.verificationSource);
|
|
358
406
|
}
|
|
359
407
|
/**
|
|
360
408
|
* Execute policy-only verification (General Governance mode)
|
|
361
409
|
* Returns the exit code to use
|
|
362
410
|
*/
|
|
363
|
-
async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRoot, config, client, source, projectId, orgGovernanceSettings, aiLogSigningKey, aiLogSigningKeyId, aiLogSigningKeys, aiLogSigner) {
|
|
411
|
+
async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRoot, config, client, source, scopeTelemetry, projectId, orgGovernanceSettings, aiLogSigningKey, aiLogSigningKeyId, aiLogSigningKeys, aiLogSigner, compiledPolicyMetadata, changeContractSummary) {
|
|
412
|
+
const emitPolicyOnlyJson = (payload) => {
|
|
413
|
+
console.log(JSON.stringify({
|
|
414
|
+
...payload,
|
|
415
|
+
...(compiledPolicyMetadata ? { policyCompilation: compiledPolicyMetadata } : {}),
|
|
416
|
+
changeContract: changeContractSummary,
|
|
417
|
+
scope: scopeTelemetry,
|
|
418
|
+
}, null, 2));
|
|
419
|
+
};
|
|
420
|
+
const policyOnlyVerificationSource = 'policy_only';
|
|
421
|
+
const recordPolicyOnlyVerification = async (payload) => recordVerificationIfRequested(options, config, {
|
|
422
|
+
...payload,
|
|
423
|
+
verificationSource: policyOnlyVerificationSource,
|
|
424
|
+
verifyResult: {
|
|
425
|
+
...payload.verifyResult,
|
|
426
|
+
verificationSource: policyOnlyVerificationSource,
|
|
427
|
+
},
|
|
428
|
+
});
|
|
364
429
|
if (!options.json) {
|
|
365
430
|
console.log(chalk.cyan('🛡️ General Governance mode (policy only, no plan linked)\n'));
|
|
366
431
|
}
|
|
@@ -376,13 +441,16 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
376
441
|
signingKeys: aiLogSigningKeys,
|
|
377
442
|
signer: aiLogSigner,
|
|
378
443
|
});
|
|
379
|
-
const governancePayload = buildGovernancePayload(governanceAnalysis, orgGovernanceSettings
|
|
444
|
+
const governancePayload = buildGovernancePayload(governanceAnalysis, orgGovernanceSettings, {
|
|
445
|
+
compiledPolicy: compiledPolicyMetadata,
|
|
446
|
+
changeContract: changeContractSummary,
|
|
447
|
+
});
|
|
380
448
|
const contextPolicyViolations = governanceAnalysis.contextPolicy.violations.filter((item) => !ignoreFilter(item.file));
|
|
381
449
|
const signedLogsRequired = isSignedAiLogsRequired(orgGovernanceSettings);
|
|
382
450
|
if (signedLogsRequired && !governanceAnalysis.aiChangeLogIntegrity.valid) {
|
|
383
451
|
const message = `AI change-log integrity check failed: ${governanceAnalysis.aiChangeLogIntegrity.issues.join('; ') || 'unknown issue'}`;
|
|
384
452
|
if (options.json) {
|
|
385
|
-
|
|
453
|
+
emitPolicyOnlyJson({
|
|
386
454
|
grade: 'F',
|
|
387
455
|
score: 0,
|
|
388
456
|
verdict: 'FAIL',
|
|
@@ -405,13 +473,13 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
405
473
|
policyOnly: true,
|
|
406
474
|
policyOnlySource: source,
|
|
407
475
|
...governancePayload,
|
|
408
|
-
}
|
|
476
|
+
});
|
|
409
477
|
}
|
|
410
478
|
else {
|
|
411
479
|
console.log(chalk.red('❌ AI change-log integrity validation failed (policy-only mode).'));
|
|
412
480
|
console.log(chalk.red(` ${message}`));
|
|
413
481
|
}
|
|
414
|
-
await
|
|
482
|
+
await recordPolicyOnlyVerification({
|
|
415
483
|
grade: 'F',
|
|
416
484
|
violations: [
|
|
417
485
|
{
|
|
@@ -439,7 +507,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
439
507
|
|| 'Governance decision matrix returned BLOCK.';
|
|
440
508
|
const reasonCodes = governanceAnalysis.governanceDecision.reasonCodes || [];
|
|
441
509
|
if (options.json) {
|
|
442
|
-
|
|
510
|
+
emitPolicyOnlyJson({
|
|
443
511
|
grade: 'F',
|
|
444
512
|
score: 0,
|
|
445
513
|
verdict: 'FAIL',
|
|
@@ -462,7 +530,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
462
530
|
policyOnly: true,
|
|
463
531
|
policyOnlySource: source,
|
|
464
532
|
...governancePayload,
|
|
465
|
-
}
|
|
533
|
+
});
|
|
466
534
|
}
|
|
467
535
|
else {
|
|
468
536
|
console.log(chalk.red('❌ Governance decision blocked this change set (policy-only mode).'));
|
|
@@ -471,7 +539,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
471
539
|
}
|
|
472
540
|
console.log(chalk.red(` ${message}`));
|
|
473
541
|
}
|
|
474
|
-
await
|
|
542
|
+
await recordPolicyOnlyVerification({
|
|
475
543
|
grade: 'F',
|
|
476
544
|
violations: [
|
|
477
545
|
{
|
|
@@ -503,7 +571,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
503
571
|
message: item.reason,
|
|
504
572
|
}));
|
|
505
573
|
if (options.json) {
|
|
506
|
-
|
|
574
|
+
emitPolicyOnlyJson({
|
|
507
575
|
grade: 'F',
|
|
508
576
|
score: 0,
|
|
509
577
|
verdict: 'FAIL',
|
|
@@ -519,7 +587,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
519
587
|
policyOnly: true,
|
|
520
588
|
policyOnlySource: source,
|
|
521
589
|
...governancePayload,
|
|
522
|
-
}
|
|
590
|
+
});
|
|
523
591
|
}
|
|
524
592
|
else {
|
|
525
593
|
console.log(chalk.red('❌ Context policy violation detected (policy-only mode).'));
|
|
@@ -528,7 +596,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
528
596
|
});
|
|
529
597
|
console.log(chalk.dim(`\n${message}`));
|
|
530
598
|
}
|
|
531
|
-
await
|
|
599
|
+
await recordPolicyOnlyVerification({
|
|
532
600
|
grade: 'F',
|
|
533
601
|
violations: contextPolicyViolationItems,
|
|
534
602
|
verifyResult: {
|
|
@@ -617,7 +685,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
617
685
|
const message = policyLockMismatchMessage(policyLockEvaluation.mismatches);
|
|
618
686
|
const lockViolationItems = toPolicyLockViolations(policyLockEvaluation.mismatches);
|
|
619
687
|
if (options.json) {
|
|
620
|
-
|
|
688
|
+
emitPolicyOnlyJson({
|
|
621
689
|
grade: 'F',
|
|
622
690
|
score: 0,
|
|
623
691
|
verdict: 'FAIL',
|
|
@@ -639,7 +707,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
639
707
|
path: policyLockEvaluation.lockPath,
|
|
640
708
|
mismatches: policyLockEvaluation.mismatches,
|
|
641
709
|
},
|
|
642
|
-
}
|
|
710
|
+
});
|
|
643
711
|
}
|
|
644
712
|
else {
|
|
645
713
|
console.log(chalk.red('❌ Policy lock baseline mismatch.'));
|
|
@@ -649,7 +717,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
649
717
|
});
|
|
650
718
|
console.log(chalk.dim('\n If drift is intentional, regenerate baseline with `neurcode policy lock`.\n'));
|
|
651
719
|
}
|
|
652
|
-
await
|
|
720
|
+
await recordPolicyOnlyVerification({
|
|
653
721
|
grade: 'F',
|
|
654
722
|
violations: lockViolationItems,
|
|
655
723
|
verifyResult: {
|
|
@@ -687,6 +755,11 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
687
755
|
minApprovals: governance.exceptionApprovals.minApprovals,
|
|
688
756
|
disallowSelfApproval: governance.exceptionApprovals.disallowSelfApproval,
|
|
689
757
|
allowedApprovers: governance.exceptionApprovals.allowedApprovers,
|
|
758
|
+
requireReason: governance.exceptionApprovals.requireReason,
|
|
759
|
+
minReasonLength: governance.exceptionApprovals.minReasonLength,
|
|
760
|
+
maxExpiryDays: governance.exceptionApprovals.maxExpiryDays,
|
|
761
|
+
criticalRulePatterns: governance.exceptionApprovals.criticalRulePatterns,
|
|
762
|
+
criticalMinApprovals: governance.exceptionApprovals.criticalMinApprovals,
|
|
690
763
|
});
|
|
691
764
|
const suppressedViolations = exceptionDecision.suppressedViolations.filter((item) => !ignoreFilter(item.file));
|
|
692
765
|
const blockedViolations = exceptionDecision.blockedViolations
|
|
@@ -695,7 +768,10 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
695
768
|
file: item.file,
|
|
696
769
|
rule: item.rule,
|
|
697
770
|
severity: 'block',
|
|
698
|
-
message: `Exception ${item.exceptionId} cannot be applied: ${explainExceptionEligibilityReason(item.eligibilityReason)}
|
|
771
|
+
message: `Exception ${item.exceptionId} cannot be applied: ${explainExceptionEligibilityReason(item.eligibilityReason)}` +
|
|
772
|
+
(item.requiredApprovals > 0
|
|
773
|
+
? ` (approvals ${item.effectiveApprovals}/${item.requiredApprovals}${item.critical ? ', critical rule gate' : ''})`
|
|
774
|
+
: ''),
|
|
699
775
|
...(item.line != null ? { line: item.line } : {}),
|
|
700
776
|
}));
|
|
701
777
|
policyViolations = [
|
|
@@ -763,7 +839,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
763
839
|
},
|
|
764
840
|
};
|
|
765
841
|
if (options.json) {
|
|
766
|
-
|
|
842
|
+
emitPolicyOnlyJson({
|
|
767
843
|
grade,
|
|
768
844
|
score,
|
|
769
845
|
verdict: effectiveVerdict,
|
|
@@ -797,7 +873,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
797
873
|
},
|
|
798
874
|
}
|
|
799
875
|
: {}),
|
|
800
|
-
}
|
|
876
|
+
});
|
|
801
877
|
}
|
|
802
878
|
else {
|
|
803
879
|
if (effectiveVerdict === 'PASS') {
|
|
@@ -821,7 +897,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
821
897
|
displayGovernanceInsights(governanceAnalysis, { explain: options.explain });
|
|
822
898
|
console.log(chalk.dim(`\n${message}`));
|
|
823
899
|
}
|
|
824
|
-
await
|
|
900
|
+
await recordPolicyOnlyVerification({
|
|
825
901
|
grade,
|
|
826
902
|
violations: violationsOutput,
|
|
827
903
|
verifyResult: {
|
|
@@ -839,7 +915,108 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
839
915
|
}
|
|
840
916
|
async function verifyCommand(options) {
|
|
841
917
|
try {
|
|
842
|
-
const
|
|
918
|
+
const rootResolution = (0, project_root_1.resolveNeurcodeProjectRootWithTrace)(process.cwd());
|
|
919
|
+
const projectRoot = rootResolution.projectRoot;
|
|
920
|
+
const scopeTelemetry = (0, scope_telemetry_1.buildScopeTelemetryPayload)(rootResolution);
|
|
921
|
+
const emitVerifyJson = (payload) => {
|
|
922
|
+
const jsonPayload = {
|
|
923
|
+
...payload,
|
|
924
|
+
scope: scopeTelemetry,
|
|
925
|
+
};
|
|
926
|
+
console.log(JSON.stringify(jsonPayload, null, 2));
|
|
927
|
+
};
|
|
928
|
+
const enforceChangeContract = options.enforceChangeContract === true ||
|
|
929
|
+
isEnabledFlag(process.env.NEURCODE_VERIFY_ENFORCE_CHANGE_CONTRACT);
|
|
930
|
+
const strictArtifactMode = options.strictArtifacts === true ||
|
|
931
|
+
isEnabledFlag(process.env.NEURCODE_VERIFY_STRICT_ARTIFACTS) ||
|
|
932
|
+
isEnabledFlag(process.env.NEURCODE_ENTERPRISE_MODE);
|
|
933
|
+
const changeContractRead = (0, change_contract_1.readChangeContract)(projectRoot, options.changeContract);
|
|
934
|
+
const compiledPolicyRead = (0, policy_compiler_1.readCompiledPolicyArtifact)(projectRoot, options.compiledPolicy);
|
|
935
|
+
let compiledPolicyMetadata = resolveCompiledPolicyMetadata(compiledPolicyRead.artifact, compiledPolicyRead.exists ? compiledPolicyRead.path : null);
|
|
936
|
+
let changeContractSummary = {
|
|
937
|
+
path: changeContractRead.path,
|
|
938
|
+
exists: changeContractRead.exists,
|
|
939
|
+
enforced: enforceChangeContract,
|
|
940
|
+
valid: changeContractRead.contract ? null : changeContractRead.exists ? false : null,
|
|
941
|
+
planId: changeContractRead.contract?.planId || null,
|
|
942
|
+
contractId: changeContractRead.contract?.contractId || null,
|
|
943
|
+
violations: changeContractRead.error
|
|
944
|
+
? [
|
|
945
|
+
{
|
|
946
|
+
code: 'CHANGE_CONTRACT_PARSE_ERROR',
|
|
947
|
+
message: changeContractRead.error,
|
|
948
|
+
},
|
|
949
|
+
]
|
|
950
|
+
: [],
|
|
951
|
+
};
|
|
952
|
+
if (strictArtifactMode) {
|
|
953
|
+
const strictErrors = [];
|
|
954
|
+
if (!compiledPolicyRead.artifact) {
|
|
955
|
+
strictErrors.push(compiledPolicyRead.error
|
|
956
|
+
? `Compiled policy artifact invalid (${compiledPolicyRead.error})`
|
|
957
|
+
: `Compiled policy artifact missing (${compiledPolicyRead.path})`);
|
|
958
|
+
}
|
|
959
|
+
if (!changeContractRead.contract) {
|
|
960
|
+
strictErrors.push(changeContractRead.error
|
|
961
|
+
? `Change contract artifact invalid (${changeContractRead.error})`
|
|
962
|
+
: `Change contract artifact missing (${changeContractRead.path})`);
|
|
963
|
+
}
|
|
964
|
+
if (strictErrors.length > 0) {
|
|
965
|
+
const message = `Strict artifact mode requires deterministic compiled-policy + change-contract artifacts.\n- ${strictErrors.join('\n- ')}`;
|
|
966
|
+
if (options.json) {
|
|
967
|
+
emitVerifyJson({
|
|
968
|
+
grade: 'F',
|
|
969
|
+
score: 0,
|
|
970
|
+
verdict: 'FAIL',
|
|
971
|
+
violations: strictErrors.map((entry) => ({
|
|
972
|
+
file: entry.toLowerCase().includes('compiled policy') ? compiledPolicyRead.path : changeContractRead.path,
|
|
973
|
+
rule: 'deterministic_artifacts_required',
|
|
974
|
+
severity: 'block',
|
|
975
|
+
message: entry,
|
|
976
|
+
})),
|
|
977
|
+
adherenceScore: 0,
|
|
978
|
+
bloatCount: 0,
|
|
979
|
+
bloatFiles: [],
|
|
980
|
+
plannedFilesModified: 0,
|
|
981
|
+
totalPlannedFiles: 0,
|
|
982
|
+
message,
|
|
983
|
+
scopeGuardPassed: false,
|
|
984
|
+
mode: 'strict_artifacts_required',
|
|
985
|
+
policyOnly: options.policyOnly === true,
|
|
986
|
+
changeContract: changeContractSummary,
|
|
987
|
+
...(compiledPolicyMetadata ? { policyCompilation: compiledPolicyMetadata } : {}),
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
else {
|
|
991
|
+
(0, scope_telemetry_1.printScopeTelemetry)(chalk, scopeTelemetry, {
|
|
992
|
+
includeBlockedWarning: true,
|
|
993
|
+
});
|
|
994
|
+
console.log(chalk.red('\n⛔ Deterministic Artifact Requirements Failed'));
|
|
995
|
+
strictErrors.forEach((entry) => {
|
|
996
|
+
console.log(chalk.red(` • ${entry}`));
|
|
997
|
+
});
|
|
998
|
+
console.log(chalk.dim('\nSet --compiled-policy and --change-contract with valid artifacts before verify.\n'));
|
|
999
|
+
}
|
|
1000
|
+
process.exit(2);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
if (!options.json) {
|
|
1004
|
+
(0, scope_telemetry_1.printScopeTelemetry)(chalk, scopeTelemetry, {
|
|
1005
|
+
includeBlockedWarning: true,
|
|
1006
|
+
});
|
|
1007
|
+
if (compiledPolicyRead.error) {
|
|
1008
|
+
console.log(chalk.yellow(` Compiled policy artifact unavailable (${compiledPolicyRead.error}); falling back to runtime compilation`));
|
|
1009
|
+
}
|
|
1010
|
+
else if (compiledPolicyRead.artifact) {
|
|
1011
|
+
console.log(chalk.dim(` Compiled policy loaded: ${compiledPolicyRead.path} (${compiledPolicyRead.artifact.compilation.deterministicRuleCount} deterministic rules)`));
|
|
1012
|
+
}
|
|
1013
|
+
if (changeContractRead.error) {
|
|
1014
|
+
console.log(chalk.yellow(` Change contract unavailable (${changeContractRead.error})`));
|
|
1015
|
+
}
|
|
1016
|
+
else if (changeContractRead.contract) {
|
|
1017
|
+
console.log(chalk.dim(` Change contract loaded: ${changeContractRead.path}`));
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
843
1020
|
// Load configuration
|
|
844
1021
|
const config = (0, config_1.loadConfig)();
|
|
845
1022
|
// 🛑 FORCE PRIORITY: Env Var > Config
|
|
@@ -903,6 +1080,9 @@ async function verifyCommand(options) {
|
|
|
903
1080
|
requireSignedAiLogs: remoteSettings.requireSignedAiLogs === true,
|
|
904
1081
|
requireManualApproval: remoteSettings.requireManualApproval !== false,
|
|
905
1082
|
minimumManualApprovals: Math.max(1, Math.min(5, Math.floor(remoteSettings.minimumManualApprovals || 1))),
|
|
1083
|
+
...(remoteSettings.policyGovernance && typeof remoteSettings.policyGovernance === 'object'
|
|
1084
|
+
? { policyGovernance: remoteSettings.policyGovernance }
|
|
1085
|
+
: {}),
|
|
906
1086
|
updatedAt: remoteSettings.updatedAt,
|
|
907
1087
|
};
|
|
908
1088
|
}
|
|
@@ -941,7 +1121,7 @@ async function verifyCommand(options) {
|
|
|
941
1121
|
const message = 'Signed AI change-logs are required but no signing key is configured. Set NEURCODE_GOVERNANCE_SIGNING_KEY or NEURCODE_GOVERNANCE_SIGNING_KEYS.';
|
|
942
1122
|
recordVerifyEvent('FAIL', 'missing_signing_key_material');
|
|
943
1123
|
if (options.json) {
|
|
944
|
-
|
|
1124
|
+
emitVerifyJson({
|
|
945
1125
|
grade: 'F',
|
|
946
1126
|
score: 0,
|
|
947
1127
|
verdict: 'FAIL',
|
|
@@ -962,7 +1142,7 @@ async function verifyCommand(options) {
|
|
|
962
1142
|
scopeGuardPassed: false,
|
|
963
1143
|
mode: 'plan_enforced',
|
|
964
1144
|
policyOnly: false,
|
|
965
|
-
}
|
|
1145
|
+
});
|
|
966
1146
|
}
|
|
967
1147
|
else {
|
|
968
1148
|
console.log(chalk.red('\n⛔ Governance Signing Key Missing'));
|
|
@@ -1021,7 +1201,7 @@ async function verifyCommand(options) {
|
|
|
1021
1201
|
console.log(chalk.dim(' Make sure you have staged or unstaged changes to verify'));
|
|
1022
1202
|
}
|
|
1023
1203
|
else {
|
|
1024
|
-
|
|
1204
|
+
emitVerifyJson({
|
|
1025
1205
|
grade: 'F',
|
|
1026
1206
|
score: 0,
|
|
1027
1207
|
verdict: 'FAIL',
|
|
@@ -1033,7 +1213,7 @@ async function verifyCommand(options) {
|
|
|
1033
1213
|
totalPlannedFiles: 0,
|
|
1034
1214
|
message: 'No changes detected',
|
|
1035
1215
|
scopeGuardPassed: false,
|
|
1036
|
-
}
|
|
1216
|
+
});
|
|
1037
1217
|
}
|
|
1038
1218
|
recordVerifyEvent('NO_CHANGES', 'diff=empty');
|
|
1039
1219
|
process.exit(0);
|
|
@@ -1064,7 +1244,7 @@ async function verifyCommand(options) {
|
|
|
1064
1244
|
console.log(chalk.yellow('⚠️ No file changes detected in diff'));
|
|
1065
1245
|
}
|
|
1066
1246
|
else {
|
|
1067
|
-
|
|
1247
|
+
emitVerifyJson({
|
|
1068
1248
|
grade: 'F',
|
|
1069
1249
|
score: 0,
|
|
1070
1250
|
verdict: 'FAIL',
|
|
@@ -1076,7 +1256,7 @@ async function verifyCommand(options) {
|
|
|
1076
1256
|
totalPlannedFiles: 0,
|
|
1077
1257
|
message: 'No file changes detected in diff',
|
|
1078
1258
|
scopeGuardPassed: false,
|
|
1079
|
-
}
|
|
1259
|
+
});
|
|
1080
1260
|
}
|
|
1081
1261
|
recordVerifyEvent('NO_CHANGES', 'diff_files=0');
|
|
1082
1262
|
process.exit(0);
|
|
@@ -1106,7 +1286,10 @@ async function verifyCommand(options) {
|
|
|
1106
1286
|
signingKeys: aiLogSigningKeys,
|
|
1107
1287
|
signer: aiLogSigner,
|
|
1108
1288
|
});
|
|
1109
|
-
const baselineGovernancePayload = buildGovernancePayload(baselineGovernance, orgGovernanceSettings
|
|
1289
|
+
const baselineGovernancePayload = buildGovernancePayload(baselineGovernance, orgGovernanceSettings, {
|
|
1290
|
+
changeContract: changeContractSummary,
|
|
1291
|
+
compiledPolicy: compiledPolicyMetadata,
|
|
1292
|
+
});
|
|
1110
1293
|
const message = `Context access policy violation: ${baselineContextViolations.map((item) => item.file).join(', ')}`;
|
|
1111
1294
|
const baselineContextViolationItems = baselineContextViolations.map((item) => ({
|
|
1112
1295
|
file: item.file,
|
|
@@ -1116,7 +1299,7 @@ async function verifyCommand(options) {
|
|
|
1116
1299
|
}));
|
|
1117
1300
|
recordVerifyEvent('FAIL', `context_policy_violations=${baselineContextViolations.length}`, diffFiles.map((f) => f.path));
|
|
1118
1301
|
if (options.json) {
|
|
1119
|
-
|
|
1302
|
+
emitVerifyJson({
|
|
1120
1303
|
grade: 'F',
|
|
1121
1304
|
score: 0,
|
|
1122
1305
|
verdict: 'FAIL',
|
|
@@ -1131,7 +1314,7 @@ async function verifyCommand(options) {
|
|
|
1131
1314
|
...baselineGovernancePayload,
|
|
1132
1315
|
mode: 'policy_violation',
|
|
1133
1316
|
policyOnly: false,
|
|
1134
|
-
}
|
|
1317
|
+
});
|
|
1135
1318
|
}
|
|
1136
1319
|
else {
|
|
1137
1320
|
console.log(chalk.red('\n⛔ Context Policy Violation'));
|
|
@@ -1163,7 +1346,7 @@ async function verifyCommand(options) {
|
|
|
1163
1346
|
console.log(chalk.dim(` ${summary.totalAdded} lines added, ${summary.totalRemoved} lines removed\n`));
|
|
1164
1347
|
}
|
|
1165
1348
|
const runPolicyOnlyModeAndExit = async (source) => {
|
|
1166
|
-
const exitCode = await executePolicyOnlyMode(options, diffFiles, shouldIgnore, projectRoot, config, client, source, projectId || undefined, orgGovernanceSettings, aiLogSigningKey, aiLogSigningKeyId, aiLogSigningKeys, aiLogSigner);
|
|
1349
|
+
const exitCode = await executePolicyOnlyMode(options, diffFiles, shouldIgnore, projectRoot, config, client, source, scopeTelemetry, projectId || undefined, orgGovernanceSettings, aiLogSigningKey, aiLogSigningKeyId, aiLogSigningKeys, aiLogSigner, compiledPolicyMetadata, changeContractSummary);
|
|
1167
1350
|
const changedFiles = diffFiles.map((f) => f.path);
|
|
1168
1351
|
const verdict = exitCode === 2 ? 'FAIL' : exitCode === 1 ? 'WARN' : 'PASS';
|
|
1169
1352
|
recordVerifyEvent(verdict, `policy_only_source=${source};exit=${exitCode}`, changedFiles);
|
|
@@ -1215,7 +1398,7 @@ async function verifyCommand(options) {
|
|
|
1215
1398
|
const message = 'Plan ID is required in strict mode. Run "neurcode plan" first or pass --plan-id.';
|
|
1216
1399
|
recordVerifyEvent('FAIL', 'missing_plan_id;require_plan=true', changedFiles);
|
|
1217
1400
|
if (options.json) {
|
|
1218
|
-
|
|
1401
|
+
emitVerifyJson({
|
|
1219
1402
|
grade: 'F',
|
|
1220
1403
|
score: 0,
|
|
1221
1404
|
verdict: 'FAIL',
|
|
@@ -1229,7 +1412,7 @@ async function verifyCommand(options) {
|
|
|
1229
1412
|
scopeGuardPassed: false,
|
|
1230
1413
|
mode: 'plan_required',
|
|
1231
1414
|
policyOnly: false,
|
|
1232
|
-
}
|
|
1415
|
+
});
|
|
1233
1416
|
}
|
|
1234
1417
|
else {
|
|
1235
1418
|
console.log(chalk.red('❌ Plan ID is required in strict mode.'));
|
|
@@ -1269,6 +1452,8 @@ async function verifyCommand(options) {
|
|
|
1269
1452
|
// Track if scope guard passed - this takes priority over AI grading
|
|
1270
1453
|
let scopeGuardPassed = false;
|
|
1271
1454
|
let governanceResult = null;
|
|
1455
|
+
let planFilesForVerification = [];
|
|
1456
|
+
let intentConstraintsForVerification;
|
|
1272
1457
|
try {
|
|
1273
1458
|
// Step A: Get Modified Files (already have from diffFiles)
|
|
1274
1459
|
const modifiedFiles = diffFiles.map(f => f.path);
|
|
@@ -1285,6 +1470,8 @@ async function verifyCommand(options) {
|
|
|
1285
1470
|
const planFiles = planData.content.files
|
|
1286
1471
|
.filter(f => f.action === 'CREATE' || f.action === 'MODIFY')
|
|
1287
1472
|
.map(f => f.path);
|
|
1473
|
+
planFilesForVerification = [...planFiles];
|
|
1474
|
+
intentConstraintsForVerification = originalIntent || undefined;
|
|
1288
1475
|
const planDependencies = Array.isArray(planData.content.dependencies)
|
|
1289
1476
|
? planData.content.dependencies.filter((item) => typeof item === 'string')
|
|
1290
1477
|
: [];
|
|
@@ -1371,11 +1558,14 @@ async function verifyCommand(options) {
|
|
|
1371
1558
|
mode: 'plan_enforced',
|
|
1372
1559
|
policyOnly: false,
|
|
1373
1560
|
...(governanceResult
|
|
1374
|
-
? buildGovernancePayload(governanceResult, orgGovernanceSettings
|
|
1561
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
|
|
1562
|
+
changeContract: changeContractSummary,
|
|
1563
|
+
compiledPolicy: compiledPolicyMetadata,
|
|
1564
|
+
})
|
|
1375
1565
|
: {}),
|
|
1376
1566
|
};
|
|
1377
1567
|
// CRITICAL: Print JSON first, then exit
|
|
1378
|
-
|
|
1568
|
+
emitVerifyJson(jsonOutput);
|
|
1379
1569
|
await recordVerificationIfRequested(options, config, {
|
|
1380
1570
|
grade: 'F',
|
|
1381
1571
|
violations: scopeViolationItems,
|
|
@@ -1389,7 +1579,10 @@ async function verifyCommand(options) {
|
|
|
1389
1579
|
projectId: projectId || undefined,
|
|
1390
1580
|
jsonMode: true,
|
|
1391
1581
|
governance: governanceResult
|
|
1392
|
-
? buildGovernancePayload(governanceResult, orgGovernanceSettings
|
|
1582
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
|
|
1583
|
+
changeContract: changeContractSummary,
|
|
1584
|
+
compiledPolicy: compiledPolicyMetadata,
|
|
1585
|
+
})
|
|
1393
1586
|
: undefined,
|
|
1394
1587
|
});
|
|
1395
1588
|
process.exit(1);
|
|
@@ -1425,7 +1618,10 @@ async function verifyCommand(options) {
|
|
|
1425
1618
|
projectId: projectId || undefined,
|
|
1426
1619
|
jsonMode: false,
|
|
1427
1620
|
governance: governanceResult
|
|
1428
|
-
? buildGovernancePayload(governanceResult, orgGovernanceSettings
|
|
1621
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
|
|
1622
|
+
changeContract: changeContractSummary,
|
|
1623
|
+
compiledPolicy: compiledPolicyMetadata,
|
|
1624
|
+
})
|
|
1429
1625
|
: undefined,
|
|
1430
1626
|
});
|
|
1431
1627
|
process.exit(1);
|
|
@@ -1532,8 +1728,13 @@ async function verifyCommand(options) {
|
|
|
1532
1728
|
scopeGuardPassed,
|
|
1533
1729
|
mode: 'plan_enforced',
|
|
1534
1730
|
policyOnly: false,
|
|
1731
|
+
changeContract: changeContractSummary,
|
|
1732
|
+
...(compiledPolicyMetadata ? { policyCompilation: compiledPolicyMetadata } : {}),
|
|
1535
1733
|
...(governanceResult
|
|
1536
|
-
? buildGovernancePayload(governanceResult, orgGovernanceSettings
|
|
1734
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
|
|
1735
|
+
changeContract: changeContractSummary,
|
|
1736
|
+
compiledPolicy: compiledPolicyMetadata,
|
|
1737
|
+
})
|
|
1537
1738
|
: {}),
|
|
1538
1739
|
policyLock: {
|
|
1539
1740
|
enforced: true,
|
|
@@ -1564,14 +1765,41 @@ async function verifyCommand(options) {
|
|
|
1564
1765
|
projectId: projectId || undefined,
|
|
1565
1766
|
jsonMode: Boolean(options.json),
|
|
1566
1767
|
governance: governanceResult
|
|
1567
|
-
? buildGovernancePayload(governanceResult, orgGovernanceSettings
|
|
1768
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
|
|
1769
|
+
changeContract: changeContractSummary,
|
|
1770
|
+
compiledPolicy: compiledPolicyMetadata,
|
|
1771
|
+
})
|
|
1568
1772
|
: undefined,
|
|
1569
1773
|
});
|
|
1570
1774
|
process.exit(2);
|
|
1571
1775
|
}
|
|
1572
1776
|
}
|
|
1777
|
+
let effectiveCompiledPolicy = compiledPolicyRead.artifact;
|
|
1778
|
+
if (!effectiveCompiledPolicy) {
|
|
1779
|
+
effectiveCompiledPolicy = buildCompiledPolicyFromEffectiveRules({
|
|
1780
|
+
projectRoot,
|
|
1781
|
+
policyLockEvaluation,
|
|
1782
|
+
effectiveRules,
|
|
1783
|
+
intentConstraints: intentConstraintsForVerification,
|
|
1784
|
+
});
|
|
1785
|
+
compiledPolicyMetadata = resolveCompiledPolicyMetadata(effectiveCompiledPolicy, (0, policy_compiler_1.resolveCompiledPolicyPath)(projectRoot, options.compiledPolicy));
|
|
1786
|
+
}
|
|
1787
|
+
const hydratedCompiledPolicyRules = effectiveCompiledPolicy
|
|
1788
|
+
? (0, policy_compiler_1.hydrateCompiledPolicyRules)(effectiveCompiledPolicy)
|
|
1789
|
+
: [];
|
|
1790
|
+
if (effectiveCompiledPolicy?.source.policyLockFingerprint &&
|
|
1791
|
+
policyLockEvaluation.lockPresent &&
|
|
1792
|
+
effectiveCompiledPolicy.source.policyLockFingerprint !== (0, policy_packs_1.readPolicyLockFile)(projectRoot).lock?.effective.fingerprint) {
|
|
1793
|
+
if (!options.json) {
|
|
1794
|
+
console.log(chalk.yellow(' Compiled policy lock fingerprint differs from current lock; runtime checks will continue with latest lock state'));
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1573
1797
|
// Check user tier - Policy Compliance and A-F Grading are PRO features
|
|
1574
|
-
const
|
|
1798
|
+
const localPolicyGovernance = (0, policy_governance_1.readPolicyGovernanceConfig)(projectRoot);
|
|
1799
|
+
const governance = (0, policy_governance_1.mergePolicyGovernanceWithOrgOverrides)(localPolicyGovernance, orgGovernanceSettings?.policyGovernance);
|
|
1800
|
+
if (!options.json && orgGovernanceSettings?.policyGovernance) {
|
|
1801
|
+
console.log(chalk.dim(' Org policy governance controls active: local config merged with org-level enforcement floor'));
|
|
1802
|
+
}
|
|
1575
1803
|
const auditIntegrity = (0, policy_audit_1.verifyPolicyAuditIntegrity)(projectRoot);
|
|
1576
1804
|
const auditIntegrityStatus = resolveAuditIntegrityStatus(governance.audit.requireIntegrity, auditIntegrity);
|
|
1577
1805
|
const { getUserTier } = await Promise.resolve().then(() => __importStar(require('../utils/tier')));
|
|
@@ -1589,7 +1817,7 @@ async function verifyCommand(options) {
|
|
|
1589
1817
|
console.log(chalk.dim(' Upgrade at: https://www.neurcode.com/dashboard/purchase-plan\n'));
|
|
1590
1818
|
}
|
|
1591
1819
|
else {
|
|
1592
|
-
|
|
1820
|
+
emitVerifyJson({
|
|
1593
1821
|
grade: 'N/A',
|
|
1594
1822
|
score: 0,
|
|
1595
1823
|
verdict: 'INFO',
|
|
@@ -1604,8 +1832,13 @@ async function verifyCommand(options) {
|
|
|
1604
1832
|
mode: 'plan_enforced',
|
|
1605
1833
|
policyOnly: false,
|
|
1606
1834
|
tier: 'FREE',
|
|
1835
|
+
changeContract: changeContractSummary,
|
|
1836
|
+
...(compiledPolicyMetadata ? { policyCompilation: compiledPolicyMetadata } : {}),
|
|
1607
1837
|
...(governanceResult
|
|
1608
|
-
? buildGovernancePayload(governanceResult, orgGovernanceSettings
|
|
1838
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
|
|
1839
|
+
changeContract: changeContractSummary,
|
|
1840
|
+
compiledPolicy: compiledPolicyMetadata,
|
|
1841
|
+
})
|
|
1609
1842
|
: {}),
|
|
1610
1843
|
policyLock: {
|
|
1611
1844
|
enforced: policyLockEvaluation.enforced,
|
|
@@ -1623,7 +1856,7 @@ async function verifyCommand(options) {
|
|
|
1623
1856
|
eventCount: auditIntegrity.count,
|
|
1624
1857
|
},
|
|
1625
1858
|
},
|
|
1626
|
-
}
|
|
1859
|
+
});
|
|
1627
1860
|
}
|
|
1628
1861
|
process.exit(0);
|
|
1629
1862
|
}
|
|
@@ -1638,6 +1871,11 @@ async function verifyCommand(options) {
|
|
|
1638
1871
|
minApprovals: governance.exceptionApprovals.minApprovals,
|
|
1639
1872
|
disallowSelfApproval: governance.exceptionApprovals.disallowSelfApproval,
|
|
1640
1873
|
allowedApprovers: governance.exceptionApprovals.allowedApprovers,
|
|
1874
|
+
requireReason: governance.exceptionApprovals.requireReason,
|
|
1875
|
+
minReasonLength: governance.exceptionApprovals.minReasonLength,
|
|
1876
|
+
maxExpiryDays: governance.exceptionApprovals.maxExpiryDays,
|
|
1877
|
+
criticalRulePatterns: governance.exceptionApprovals.criticalRulePatterns,
|
|
1878
|
+
criticalMinApprovals: governance.exceptionApprovals.criticalMinApprovals,
|
|
1641
1879
|
});
|
|
1642
1880
|
const suppressedPolicyViolations = exceptionDecision.suppressedViolations.filter((item) => !shouldIgnore(item.file));
|
|
1643
1881
|
const blockedPolicyViolations = exceptionDecision.blockedViolations
|
|
@@ -1646,7 +1884,10 @@ async function verifyCommand(options) {
|
|
|
1646
1884
|
file: item.file,
|
|
1647
1885
|
rule: item.rule,
|
|
1648
1886
|
severity: 'block',
|
|
1649
|
-
message: `Exception ${item.exceptionId} cannot be applied: ${explainExceptionEligibilityReason(item.eligibilityReason)}
|
|
1887
|
+
message: `Exception ${item.exceptionId} cannot be applied: ${explainExceptionEligibilityReason(item.eligibilityReason)}` +
|
|
1888
|
+
(item.requiredApprovals > 0
|
|
1889
|
+
? ` (approvals ${item.effectiveApprovals}/${item.requiredApprovals}${item.critical ? ', critical rule gate' : ''})`
|
|
1890
|
+
: ''),
|
|
1650
1891
|
...(item.line != null ? { line: item.line } : {}),
|
|
1651
1892
|
}));
|
|
1652
1893
|
policyViolations = [
|
|
@@ -1729,22 +1970,146 @@ async function verifyCommand(options) {
|
|
|
1729
1970
|
})),
|
|
1730
1971
|
})),
|
|
1731
1972
|
}));
|
|
1973
|
+
const changeContractEvaluation = changeContractRead.contract
|
|
1974
|
+
? (0, change_contract_1.evaluateChangeContract)(changeContractRead.contract, {
|
|
1975
|
+
planId: finalPlanId,
|
|
1976
|
+
changedFiles: changedFiles.map((file) => file.path),
|
|
1977
|
+
policyLockFingerprint: (0, policy_packs_1.readPolicyLockFile)(projectRoot).lock?.effective.fingerprint || null,
|
|
1978
|
+
compiledPolicyFingerprint: effectiveCompiledPolicy?.fingerprint || null,
|
|
1979
|
+
})
|
|
1980
|
+
: null;
|
|
1981
|
+
if (changeContractEvaluation) {
|
|
1982
|
+
changeContractSummary = {
|
|
1983
|
+
path: changeContractRead.path,
|
|
1984
|
+
exists: true,
|
|
1985
|
+
enforced: enforceChangeContract,
|
|
1986
|
+
valid: changeContractEvaluation.valid,
|
|
1987
|
+
planId: changeContractRead.contract?.planId || null,
|
|
1988
|
+
contractId: changeContractRead.contract?.contractId || null,
|
|
1989
|
+
coverage: changeContractEvaluation.coverage,
|
|
1990
|
+
violations: changeContractEvaluation.violations.map((item) => ({
|
|
1991
|
+
code: item.code,
|
|
1992
|
+
message: item.message,
|
|
1993
|
+
file: item.file,
|
|
1994
|
+
expected: item.expected,
|
|
1995
|
+
actual: item.actual,
|
|
1996
|
+
})),
|
|
1997
|
+
};
|
|
1998
|
+
if (!changeContractEvaluation.valid && enforceChangeContract) {
|
|
1999
|
+
const violations = changeContractEvaluation.violations.map((item) => ({
|
|
2000
|
+
file: item.file || '.neurcode/change-contract.json',
|
|
2001
|
+
rule: `change_contract:${item.code.toLowerCase()}`,
|
|
2002
|
+
severity: 'block',
|
|
2003
|
+
message: item.message,
|
|
2004
|
+
}));
|
|
2005
|
+
const message = `Change contract enforcement failed: ${changeContractEvaluation.violations
|
|
2006
|
+
.map((item) => item.message)
|
|
2007
|
+
.join('; ')}`;
|
|
2008
|
+
if (options.json) {
|
|
2009
|
+
emitVerifyJson({
|
|
2010
|
+
grade: 'F',
|
|
2011
|
+
score: 0,
|
|
2012
|
+
verdict: 'FAIL',
|
|
2013
|
+
violations,
|
|
2014
|
+
adherenceScore: 0,
|
|
2015
|
+
bloatCount: 0,
|
|
2016
|
+
bloatFiles: [],
|
|
2017
|
+
plannedFilesModified: 0,
|
|
2018
|
+
totalPlannedFiles: 0,
|
|
2019
|
+
message,
|
|
2020
|
+
scopeGuardPassed: false,
|
|
2021
|
+
mode: 'plan_enforced',
|
|
2022
|
+
policyOnly: false,
|
|
2023
|
+
changeContract: changeContractSummary,
|
|
2024
|
+
...(compiledPolicyMetadata ? { policyCompilation: compiledPolicyMetadata } : {}),
|
|
2025
|
+
});
|
|
2026
|
+
}
|
|
2027
|
+
else {
|
|
2028
|
+
console.log(chalk.red('\n⛔ Change contract enforcement failed'));
|
|
2029
|
+
changeContractEvaluation.violations.forEach((item) => {
|
|
2030
|
+
console.log(chalk.red(` • ${item.message}`));
|
|
2031
|
+
});
|
|
2032
|
+
console.log(chalk.dim(` Contract path: ${changeContractRead.path}`));
|
|
2033
|
+
}
|
|
2034
|
+
await recordVerificationIfRequested(options, config, {
|
|
2035
|
+
grade: 'F',
|
|
2036
|
+
violations,
|
|
2037
|
+
verifyResult: {
|
|
2038
|
+
adherenceScore: 0,
|
|
2039
|
+
verdict: 'FAIL',
|
|
2040
|
+
bloatCount: 0,
|
|
2041
|
+
bloatFiles: [],
|
|
2042
|
+
message,
|
|
2043
|
+
},
|
|
2044
|
+
projectId: projectId || undefined,
|
|
2045
|
+
jsonMode: Boolean(options.json),
|
|
2046
|
+
governance: governanceResult
|
|
2047
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
|
|
2048
|
+
changeContract: changeContractSummary,
|
|
2049
|
+
compiledPolicy: compiledPolicyMetadata,
|
|
2050
|
+
})
|
|
2051
|
+
: undefined,
|
|
2052
|
+
});
|
|
2053
|
+
process.exit(2);
|
|
2054
|
+
}
|
|
2055
|
+
else if (!changeContractEvaluation.valid && !options.json) {
|
|
2056
|
+
console.log(chalk.yellow('\n⚠️ Change contract drift detected (advisory mode)'));
|
|
2057
|
+
changeContractEvaluation.violations.slice(0, 5).forEach((item) => {
|
|
2058
|
+
console.log(chalk.yellow(` • ${item.message}`));
|
|
2059
|
+
});
|
|
2060
|
+
if (changeContractEvaluation.violations.length > 5) {
|
|
2061
|
+
console.log(chalk.dim(` ... ${changeContractEvaluation.violations.length - 5} more violation(s)`));
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
1732
2065
|
// Call verify API
|
|
1733
2066
|
if (!options.json) {
|
|
1734
2067
|
console.log(chalk.dim(' Sending to Neurcode API...\n'));
|
|
1735
2068
|
}
|
|
1736
2069
|
try {
|
|
1737
|
-
|
|
1738
|
-
let
|
|
2070
|
+
let verifySource = 'api';
|
|
2071
|
+
let verifyResult;
|
|
2072
|
+
const deterministicPolicyRules = effectiveCompiledPolicy
|
|
2073
|
+
? [...effectiveCompiledPolicy.statements.policyRules]
|
|
2074
|
+
: effectiveRules.customPolicies
|
|
2075
|
+
.map((policy) => policy.rule_text)
|
|
2076
|
+
.filter((ruleText) => typeof ruleText === 'string' && ruleText.trim().length > 0);
|
|
1739
2077
|
try {
|
|
1740
|
-
|
|
1741
|
-
intentConstraints = planData.intent || undefined;
|
|
2078
|
+
verifyResult = await client.verifyPlan(finalPlanId, diffStats, changedFiles, projectId, intentConstraintsForVerification, deterministicPolicyRules, 'api', compiledPolicyMetadata);
|
|
1742
2079
|
}
|
|
1743
|
-
catch {
|
|
1744
|
-
|
|
2080
|
+
catch (verifyApiError) {
|
|
2081
|
+
if (planFilesForVerification.length === 0) {
|
|
2082
|
+
throw verifyApiError;
|
|
2083
|
+
}
|
|
2084
|
+
verifySource = 'local_fallback';
|
|
2085
|
+
if (!options.json) {
|
|
2086
|
+
const fallbackReason = verifyApiError instanceof Error ? verifyApiError.message : String(verifyApiError);
|
|
2087
|
+
console.log(chalk.yellow('⚠️ Verify API unavailable, using local deterministic fallback.'));
|
|
2088
|
+
console.log(chalk.dim(` Reason: ${fallbackReason}`));
|
|
2089
|
+
}
|
|
2090
|
+
const localEvaluation = (0, governance_runtime_1.evaluatePlanVerification)({
|
|
2091
|
+
planFiles: planFilesForVerification.map((path) => ({
|
|
2092
|
+
path,
|
|
2093
|
+
action: 'MODIFY',
|
|
2094
|
+
})),
|
|
2095
|
+
changedFiles,
|
|
2096
|
+
diffStats,
|
|
2097
|
+
intentConstraints: intentConstraintsForVerification,
|
|
2098
|
+
policyRules: deterministicPolicyRules,
|
|
2099
|
+
extraConstraintRules: hydratedCompiledPolicyRules.length > 0 ? hydratedCompiledPolicyRules : undefined,
|
|
2100
|
+
});
|
|
2101
|
+
verifyResult = {
|
|
2102
|
+
verificationId: `local-fallback-${Date.now()}`,
|
|
2103
|
+
adherenceScore: localEvaluation.adherenceScore,
|
|
2104
|
+
bloatCount: localEvaluation.bloatCount,
|
|
2105
|
+
bloatFiles: localEvaluation.bloatFiles,
|
|
2106
|
+
plannedFilesModified: localEvaluation.plannedFilesModified,
|
|
2107
|
+
totalPlannedFiles: localEvaluation.totalPlannedFiles,
|
|
2108
|
+
verdict: localEvaluation.verdict,
|
|
2109
|
+
diffSummary: localEvaluation.diffSummary,
|
|
2110
|
+
message: localEvaluation.message,
|
|
2111
|
+
};
|
|
1745
2112
|
}
|
|
1746
|
-
// Call verifyPlan with intentConstraints
|
|
1747
|
-
const verifyResult = await client.verifyPlan(finalPlanId, diffStats, changedFiles, projectId, intentConstraints);
|
|
1748
2113
|
// Apply custom policy verdict: block from dashboard overrides API verdict
|
|
1749
2114
|
const policyBlock = policyDecision === 'block' && policyViolations.length > 0;
|
|
1750
2115
|
const governanceDecisionBlock = governanceResult?.governanceDecision?.decision === 'block';
|
|
@@ -1846,10 +2211,16 @@ async function verifyCommand(options) {
|
|
|
1846
2211
|
bloatFiles: filteredBloatFiles,
|
|
1847
2212
|
plannedFilesModified: verifyResult.plannedFilesModified,
|
|
1848
2213
|
totalPlannedFiles: verifyResult.totalPlannedFiles,
|
|
2214
|
+
verificationSource: verifySource,
|
|
1849
2215
|
mode: 'plan_enforced',
|
|
1850
2216
|
policyOnly: false,
|
|
2217
|
+
changeContract: changeContractSummary,
|
|
2218
|
+
...(compiledPolicyMetadata ? { policyCompilation: compiledPolicyMetadata } : {}),
|
|
1851
2219
|
...(governanceResult
|
|
1852
|
-
? buildGovernancePayload(governanceResult, orgGovernanceSettings
|
|
2220
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
|
|
2221
|
+
changeContract: changeContractSummary,
|
|
2222
|
+
compiledPolicy: compiledPolicyMetadata,
|
|
2223
|
+
})
|
|
1853
2224
|
: {}),
|
|
1854
2225
|
policyLock: {
|
|
1855
2226
|
enforced: policyLockEvaluation.enforced,
|
|
@@ -1871,7 +2242,7 @@ async function verifyCommand(options) {
|
|
|
1871
2242
|
}
|
|
1872
2243
|
: {}),
|
|
1873
2244
|
};
|
|
1874
|
-
|
|
2245
|
+
emitVerifyJson(jsonOutput);
|
|
1875
2246
|
await recordVerificationIfRequested(options, config, {
|
|
1876
2247
|
grade,
|
|
1877
2248
|
violations: violations,
|
|
@@ -1881,11 +2252,16 @@ async function verifyCommand(options) {
|
|
|
1881
2252
|
bloatCount: filteredBloatFiles.length,
|
|
1882
2253
|
bloatFiles: filteredBloatFiles,
|
|
1883
2254
|
message: effectiveMessage,
|
|
2255
|
+
verificationSource: verifySource,
|
|
1884
2256
|
},
|
|
1885
2257
|
projectId: projectId || undefined,
|
|
1886
2258
|
jsonMode: true,
|
|
2259
|
+
verificationSource: verifySource,
|
|
1887
2260
|
governance: governanceResult
|
|
1888
|
-
? buildGovernancePayload(governanceResult, orgGovernanceSettings
|
|
2261
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
|
|
2262
|
+
changeContract: changeContractSummary,
|
|
2263
|
+
compiledPolicy: compiledPolicyMetadata,
|
|
2264
|
+
})
|
|
1889
2265
|
: undefined,
|
|
1890
2266
|
});
|
|
1891
2267
|
// Exit based on effective verdict (same logic as below)
|
|
@@ -1960,11 +2336,16 @@ async function verifyCommand(options) {
|
|
|
1960
2336
|
bloatCount: filteredBloatForReport.length,
|
|
1961
2337
|
bloatFiles: filteredBloatForReport,
|
|
1962
2338
|
message: effectiveMessage,
|
|
2339
|
+
verificationSource: verifySource,
|
|
1963
2340
|
},
|
|
1964
2341
|
projectId: projectId || undefined,
|
|
1965
2342
|
jsonMode: false,
|
|
2343
|
+
verificationSource: verifySource,
|
|
1966
2344
|
governance: governanceResult
|
|
1967
|
-
? buildGovernancePayload(governanceResult, orgGovernanceSettings
|
|
2345
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings, {
|
|
2346
|
+
changeContract: changeContractSummary,
|
|
2347
|
+
compiledPolicy: compiledPolicyMetadata,
|
|
2348
|
+
})
|
|
1968
2349
|
: undefined,
|
|
1969
2350
|
});
|
|
1970
2351
|
// Governance override: keep PASS only when scope guard passes and failure is due
|
|
@@ -2001,7 +2382,7 @@ async function verifyCommand(options) {
|
|
|
2001
2382
|
recordVerifyEvent('FAIL', `verify_api_error=${error instanceof Error ? error.message : 'unknown'}`, changedFiles, finalPlanId);
|
|
2002
2383
|
if (options.json) {
|
|
2003
2384
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
2004
|
-
|
|
2385
|
+
emitVerifyJson({
|
|
2005
2386
|
grade: 'F',
|
|
2006
2387
|
score: 0,
|
|
2007
2388
|
verdict: 'FAIL',
|
|
@@ -2013,7 +2394,7 @@ async function verifyCommand(options) {
|
|
|
2013
2394
|
totalPlannedFiles: 0,
|
|
2014
2395
|
message: `Error: ${errorMessage}`,
|
|
2015
2396
|
scopeGuardPassed: false,
|
|
2016
|
-
}
|
|
2397
|
+
});
|
|
2017
2398
|
}
|
|
2018
2399
|
else {
|
|
2019
2400
|
if (error instanceof Error) {
|
|
@@ -2048,6 +2429,14 @@ async function verifyCommand(options) {
|
|
|
2048
2429
|
totalPlannedFiles: 0,
|
|
2049
2430
|
message: `Unexpected error: ${errorMessage}`,
|
|
2050
2431
|
scopeGuardPassed: false,
|
|
2432
|
+
scope: {
|
|
2433
|
+
scanRoot: process.cwd(),
|
|
2434
|
+
startDir: process.cwd(),
|
|
2435
|
+
gitRoot: null,
|
|
2436
|
+
linkedRepoOverrideUsed: false,
|
|
2437
|
+
linkedRepos: [],
|
|
2438
|
+
blockedOverride: null,
|
|
2439
|
+
},
|
|
2051
2440
|
}, null, 2));
|
|
2052
2441
|
}
|
|
2053
2442
|
else {
|
|
@@ -2212,7 +2601,7 @@ function buildCompactVerificationPayload(payload) {
|
|
|
2212
2601
|
/**
|
|
2213
2602
|
* Report verification results to Neurcode Cloud
|
|
2214
2603
|
*/
|
|
2215
|
-
async function reportVerification(grade, violations, verifyResult, apiKey, apiUrl, projectId, jsonMode, governance) {
|
|
2604
|
+
async function reportVerification(grade, violations, verifyResult, apiKey, apiUrl, projectId, jsonMode, governance, verificationSource) {
|
|
2216
2605
|
try {
|
|
2217
2606
|
const ciContext = collectCIContext();
|
|
2218
2607
|
const payload = {
|
|
@@ -2229,6 +2618,7 @@ async function reportVerification(grade, violations, verifyResult, apiKey, apiUr
|
|
|
2229
2618
|
workflowRunId: ciContext.workflowRunId,
|
|
2230
2619
|
projectId,
|
|
2231
2620
|
governance,
|
|
2621
|
+
verificationSource: verificationSource || verifyResult.verificationSource || 'api',
|
|
2232
2622
|
};
|
|
2233
2623
|
const postPayload = async (requestPayload) => fetch(`${apiUrl}/api/v1/action/verifications`, {
|
|
2234
2624
|
method: 'POST',
|
|
@@ -2271,7 +2661,7 @@ async function reportVerification(grade, violations, verifyResult, apiKey, apiUr
|
|
|
2271
2661
|
}
|
|
2272
2662
|
}
|
|
2273
2663
|
}
|
|
2274
|
-
function buildGovernancePayload(governance, orgGovernanceSettings) {
|
|
2664
|
+
function buildGovernancePayload(governance, orgGovernanceSettings, options) {
|
|
2275
2665
|
return {
|
|
2276
2666
|
contextPolicy: governance.contextPolicy,
|
|
2277
2667
|
blastRadius: governance.blastRadius,
|
|
@@ -2289,9 +2679,14 @@ function buildGovernancePayload(governance, orgGovernanceSettings) {
|
|
|
2289
2679
|
requireSignedAiLogs: orgGovernanceSettings.requireSignedAiLogs,
|
|
2290
2680
|
requireManualApproval: orgGovernanceSettings.requireManualApproval,
|
|
2291
2681
|
minimumManualApprovals: orgGovernanceSettings.minimumManualApprovals,
|
|
2682
|
+
...(orgGovernanceSettings.policyGovernance
|
|
2683
|
+
? { policyGovernance: orgGovernanceSettings.policyGovernance }
|
|
2684
|
+
: {}),
|
|
2292
2685
|
updatedAt: orgGovernanceSettings.updatedAt || null,
|
|
2293
2686
|
}
|
|
2294
2687
|
: null,
|
|
2688
|
+
...(options?.compiledPolicy ? { policyCompilation: options.compiledPolicy } : {}),
|
|
2689
|
+
...(options?.changeContract ? { changeContract: options.changeContract } : {}),
|
|
2295
2690
|
};
|
|
2296
2691
|
}
|
|
2297
2692
|
function displayGovernanceInsights(governance, options = {}) {
|