@neurcode-ai/cli 0.9.61 → 0.9.62
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/LICENSE +201 -0
- package/dist/commands/control-plane.d.ts +3 -0
- package/dist/commands/control-plane.d.ts.map +1 -0
- package/dist/commands/control-plane.js +163 -0
- package/dist/commands/control-plane.js.map +1 -0
- package/dist/commands/export.d.ts +7 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +72 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/fix.d.ts +1 -0
- package/dist/commands/fix.d.ts.map +1 -1
- package/dist/commands/fix.js +3 -0
- package/dist/commands/fix.js.map +1 -1
- package/dist/commands/generate.d.ts +1 -0
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +63 -48
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/patch-apply.d.ts.map +1 -1
- package/dist/commands/patch-apply.js +1 -0
- package/dist/commands/patch-apply.js.map +1 -1
- package/dist/commands/replay.d.ts +3 -0
- package/dist/commands/replay.d.ts.map +1 -0
- package/dist/commands/replay.js +267 -0
- package/dist/commands/replay.js.map +1 -0
- package/dist/commands/verify.d.ts +7 -1
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +281 -149
- package/dist/commands/verify.js.map +1 -1
- package/dist/commands/workspace.d.ts +3 -0
- package/dist/commands/workspace.d.ts.map +1 -0
- package/dist/commands/workspace.js +407 -0
- package/dist/commands/workspace.js.map +1 -0
- package/dist/daemon/server.d.ts +0 -17
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +1038 -53
- package/dist/daemon/server.js.map +1 -1
- package/dist/index.js +296 -12
- package/dist/index.js.map +1 -1
- package/dist/utils/cli-json.d.ts +1 -0
- package/dist/utils/cli-json.d.ts.map +1 -1
- package/dist/utils/cli-json.js +1 -0
- package/dist/utils/cli-json.js.map +1 -1
- package/dist/utils/control-plane.d.ts +171 -0
- package/dist/utils/control-plane.d.ts.map +1 -0
- package/dist/utils/control-plane.js +684 -0
- package/dist/utils/control-plane.js.map +1 -0
- package/dist/utils/execution-bus.d.ts +205 -0
- package/dist/utils/execution-bus.d.ts.map +1 -0
- package/dist/utils/execution-bus.js +1346 -0
- package/dist/utils/execution-bus.js.map +1 -0
- package/dist/utils/gitignore.d.ts +2 -2
- package/dist/utils/gitignore.d.ts.map +1 -1
- package/dist/utils/gitignore.js +27 -14
- package/dist/utils/gitignore.js.map +1 -1
- package/dist/utils/replay-runtime.d.ts +295 -0
- package/dist/utils/replay-runtime.d.ts.map +1 -0
- package/dist/utils/replay-runtime.js +1080 -0
- package/dist/utils/replay-runtime.js.map +1 -0
- package/dist/utils/runtime-events.d.ts +44 -0
- package/dist/utils/runtime-events.d.ts.map +1 -0
- package/dist/utils/runtime-events.js +213 -0
- package/dist/utils/runtime-events.js.map +1 -0
- package/dist/utils/verification-evidence.d.ts +22 -0
- package/dist/utils/verification-evidence.d.ts.map +1 -0
- package/dist/utils/verification-evidence.js +233 -0
- package/dist/utils/verification-evidence.js.map +1 -0
- package/dist/utils/workspace-runtime.d.ts +267 -0
- package/dist/utils/workspace-runtime.d.ts.map +1 -0
- package/dist/utils/workspace-runtime.js +1415 -0
- package/dist/utils/workspace-runtime.js.map +1 -0
- package/package.json +7 -8
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readControlPlaneState = readControlPlaneState;
|
|
4
|
+
exports.previewControlPlaneUpdate = previewControlPlaneUpdate;
|
|
5
|
+
exports.applyControlPlaneUpdate = applyControlPlaneUpdate;
|
|
6
|
+
exports.readControlPlaneSnapshotHistory = readControlPlaneSnapshotHistory;
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
const policy_governance_1 = require("./policy-governance");
|
|
11
|
+
const secret_masking_1 = require("./secret-masking");
|
|
12
|
+
const execution_bus_1 = require("./execution-bus");
|
|
13
|
+
const runtime_events_1 = require("./runtime-events");
|
|
14
|
+
const CONTROL_PLANE_SCHEMA = 'neurcode.control-plane.v1';
|
|
15
|
+
const CONTROL_PLANE_SNAPSHOT_SCHEMA = 'neurcode.control-plane.snapshot.v1';
|
|
16
|
+
const RUNTIME_SCHEMA = 'neurcode.control-plane.runtime.v1';
|
|
17
|
+
const REMEDIATION_SCHEMA = 'neurcode.control-plane.remediation.v1';
|
|
18
|
+
const EVIDENCE_SCHEMA = 'neurcode.control-plane.evidence.v1';
|
|
19
|
+
const EVENT_RUNTIME_SCHEMA = 'neurcode.control-plane.event-runtime.v1';
|
|
20
|
+
const CI_GOVERNANCE_SCHEMA = 'neurcode.control-plane.ci-governance.v1';
|
|
21
|
+
const DEFAULT_CONTROL_PLANE_ROOT = '.neurcode/control-plane';
|
|
22
|
+
const DEFAULT_SNAPSHOT_RETENTION = 120;
|
|
23
|
+
function nowIso() {
|
|
24
|
+
return new Date().toISOString();
|
|
25
|
+
}
|
|
26
|
+
function clampInt(value, fallback, min, max) {
|
|
27
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
28
|
+
return Math.max(min, Math.min(max, Math.floor(value)));
|
|
29
|
+
}
|
|
30
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
31
|
+
const parsed = Number.parseInt(value.trim(), 10);
|
|
32
|
+
if (Number.isFinite(parsed)) {
|
|
33
|
+
return Math.max(min, Math.min(max, Math.floor(parsed)));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return fallback;
|
|
37
|
+
}
|
|
38
|
+
function asBoolean(value, fallback) {
|
|
39
|
+
if (typeof value === 'boolean')
|
|
40
|
+
return value;
|
|
41
|
+
if (typeof value === 'string') {
|
|
42
|
+
const normalized = value.trim().toLowerCase();
|
|
43
|
+
if (normalized === 'true' || normalized === '1')
|
|
44
|
+
return true;
|
|
45
|
+
if (normalized === 'false' || normalized === '0')
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
return fallback;
|
|
49
|
+
}
|
|
50
|
+
function asObject(value) {
|
|
51
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
function asString(value, fallback) {
|
|
57
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
58
|
+
return value.trim();
|
|
59
|
+
}
|
|
60
|
+
return fallback;
|
|
61
|
+
}
|
|
62
|
+
function normalizeRelativePath(value, fallback) {
|
|
63
|
+
const candidate = asString(value, fallback).replace(/\\/g, '/');
|
|
64
|
+
if (candidate.startsWith('/') || /^[A-Za-z]:\//.test(candidate) || candidate.includes('..')) {
|
|
65
|
+
return fallback;
|
|
66
|
+
}
|
|
67
|
+
return candidate;
|
|
68
|
+
}
|
|
69
|
+
function normalizeList(values) {
|
|
70
|
+
if (!Array.isArray(values))
|
|
71
|
+
return [];
|
|
72
|
+
return Array.from(new Set(values
|
|
73
|
+
.filter((entry) => typeof entry === 'string')
|
|
74
|
+
.map((entry) => entry.trim())
|
|
75
|
+
.filter(Boolean))).sort((left, right) => left.localeCompare(right));
|
|
76
|
+
}
|
|
77
|
+
function normalizeRuntimeConfig(raw) {
|
|
78
|
+
const source = asObject(raw);
|
|
79
|
+
const execution = asObject(source.execution);
|
|
80
|
+
const verification = asObject(source.verification);
|
|
81
|
+
const retention = asObject(source.retention);
|
|
82
|
+
return {
|
|
83
|
+
schemaVersion: RUNTIME_SCHEMA,
|
|
84
|
+
execution: {
|
|
85
|
+
duplicateSuppression: asBoolean(execution.duplicateSuppression, false),
|
|
86
|
+
dedupeWindowMs: clampInt(execution.dedupeWindowMs, 0, 0, 60_000),
|
|
87
|
+
maxConcurrentExecutions: clampInt(execution.maxConcurrentExecutions, 1, 1, 12),
|
|
88
|
+
replayEnabled: asBoolean(execution.replayEnabled, true),
|
|
89
|
+
lockTimeoutMs: clampInt(execution.lockTimeoutMs, 90_000, 10_000, 300_000),
|
|
90
|
+
},
|
|
91
|
+
verification: {
|
|
92
|
+
autoReverify: asBoolean(verification.autoReverify, true),
|
|
93
|
+
deterministicOnlyInCi: asBoolean(verification.deterministicOnlyInCi, true),
|
|
94
|
+
allowPolicyOnlyFallback: asBoolean(verification.allowPolicyOnlyFallback, true),
|
|
95
|
+
ciEnforcement: verification.ciEnforcement === 'advisory' ? 'advisory' : 'strict',
|
|
96
|
+
},
|
|
97
|
+
retention: {
|
|
98
|
+
executionRecords: clampInt(retention.executionRecords, 250, 25, 5000),
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function normalizeRemediationConfig(raw) {
|
|
103
|
+
const source = asObject(raw);
|
|
104
|
+
const automation = asObject(source.automation);
|
|
105
|
+
const safety = asObject(source.safety);
|
|
106
|
+
const manualApproval = asString(automation.requireManualApprovalAtRisk, 'high').toLowerCase();
|
|
107
|
+
return {
|
|
108
|
+
schemaVersion: REMEDIATION_SCHEMA,
|
|
109
|
+
automation: {
|
|
110
|
+
autonomousApplySafe: asBoolean(automation.autonomousApplySafe, true),
|
|
111
|
+
maxAutoPatchesPerExecution: clampInt(automation.maxAutoPatchesPerExecution, 5, 1, 200),
|
|
112
|
+
requireManualApprovalAtRisk: manualApproval === 'none' || manualApproval === 'critical' ? manualApproval : 'high',
|
|
113
|
+
},
|
|
114
|
+
safety: {
|
|
115
|
+
rollbackOnRegression: asBoolean(safety.rollbackOnRegression, true),
|
|
116
|
+
requireCleanWorkingTree: asBoolean(safety.requireCleanWorkingTree, true),
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function normalizeEvidenceConfig(raw) {
|
|
121
|
+
const source = asObject(raw);
|
|
122
|
+
const collection = asObject(source.collection);
|
|
123
|
+
const redaction = asObject(source.redaction);
|
|
124
|
+
return {
|
|
125
|
+
schemaVersion: EVIDENCE_SCHEMA,
|
|
126
|
+
collection: {
|
|
127
|
+
enabledByDefault: asBoolean(collection.enabledByDefault, false),
|
|
128
|
+
directory: normalizeRelativePath(collection.directory, '.neurcode/evidence'),
|
|
129
|
+
retentionMaxArtifacts: clampInt(collection.retentionMaxArtifacts, 50, 10, 2000),
|
|
130
|
+
},
|
|
131
|
+
redaction: {
|
|
132
|
+
maskSecrets: asBoolean(redaction.maskSecrets, true),
|
|
133
|
+
maskSensitivePaths: asBoolean(redaction.maskSensitivePaths, true),
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function normalizeEventRuntimeConfig(raw) {
|
|
138
|
+
const source = asObject(raw);
|
|
139
|
+
const stream = asObject(source.stream);
|
|
140
|
+
const retention = asObject(source.retention);
|
|
141
|
+
return {
|
|
142
|
+
schemaVersion: EVENT_RUNTIME_SCHEMA,
|
|
143
|
+
stream: {
|
|
144
|
+
enabled: asBoolean(stream.enabled, true),
|
|
145
|
+
transport: 'sse',
|
|
146
|
+
heartbeatMs: clampInt(stream.heartbeatMs, 15_000, 5_000, 120_000),
|
|
147
|
+
replayBatchSize: clampInt(stream.replayBatchSize, 200, 20, 1000),
|
|
148
|
+
},
|
|
149
|
+
retention: {
|
|
150
|
+
maxEvents: clampInt(retention.maxEvents, 5000, 500, 100_000),
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function normalizeCiGovernanceConfig(raw) {
|
|
155
|
+
const source = asObject(raw);
|
|
156
|
+
const mode = asObject(source.mode);
|
|
157
|
+
const enforcement = asObject(source.enforcement);
|
|
158
|
+
return {
|
|
159
|
+
schemaVersion: CI_GOVERNANCE_SCHEMA,
|
|
160
|
+
mode: {
|
|
161
|
+
verifyCiMode: asBoolean(mode.verifyCiMode, true),
|
|
162
|
+
deterministicOnly: asBoolean(mode.deterministicOnly, true),
|
|
163
|
+
nonInteractiveOnly: asBoolean(mode.nonInteractiveOnly, true),
|
|
164
|
+
},
|
|
165
|
+
enforcement: {
|
|
166
|
+
strictness: enforcement.strictness === 'advisory' ? 'advisory' : 'strict',
|
|
167
|
+
allowMissingIntent: asBoolean(enforcement.allowMissingIntent, true),
|
|
168
|
+
allowMissingLocalRuntimeState: asBoolean(enforcement.allowMissingLocalRuntimeState, true),
|
|
169
|
+
requireDeterministicArtifacts: asBoolean(enforcement.requireDeterministicArtifacts, true),
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function toPaths(cwd) {
|
|
174
|
+
const rootDir = (0, path_1.resolve)(cwd, DEFAULT_CONTROL_PLANE_ROOT);
|
|
175
|
+
const snapshotsDir = (0, path_1.join)(rootDir, 'snapshots');
|
|
176
|
+
(0, fs_1.mkdirSync)(rootDir, { recursive: true });
|
|
177
|
+
(0, fs_1.mkdirSync)(snapshotsDir, { recursive: true });
|
|
178
|
+
return {
|
|
179
|
+
rootDir,
|
|
180
|
+
runtimePath: (0, path_1.join)(rootDir, 'runtime.json'),
|
|
181
|
+
remediationPath: (0, path_1.join)(rootDir, 'remediation.json'),
|
|
182
|
+
evidencePath: (0, path_1.join)(rootDir, 'evidence.json'),
|
|
183
|
+
eventRuntimePath: (0, path_1.join)(rootDir, 'event-runtime.json'),
|
|
184
|
+
ciGovernancePath: (0, path_1.join)(rootDir, 'ci-governance.json'),
|
|
185
|
+
snapshotsDir,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function readJsonFile(pathValue) {
|
|
189
|
+
if (!(0, fs_1.existsSync)(pathValue))
|
|
190
|
+
return {};
|
|
191
|
+
try {
|
|
192
|
+
const parsed = JSON.parse((0, fs_1.readFileSync)(pathValue, 'utf-8'));
|
|
193
|
+
return asObject(parsed);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return {};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function hashPayload(value) {
|
|
200
|
+
const normalized = JSON.stringify(sortObject(value));
|
|
201
|
+
return (0, crypto_1.createHash)('sha256').update(normalized, 'utf-8').digest('hex');
|
|
202
|
+
}
|
|
203
|
+
function sortObject(value) {
|
|
204
|
+
if (Array.isArray(value)) {
|
|
205
|
+
return value.map((entry) => sortObject(entry));
|
|
206
|
+
}
|
|
207
|
+
if (!value || typeof value !== 'object') {
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
const source = value;
|
|
211
|
+
const sorted = {};
|
|
212
|
+
for (const key of Object.keys(source).sort((left, right) => left.localeCompare(right))) {
|
|
213
|
+
sorted[key] = sortObject(source[key]);
|
|
214
|
+
}
|
|
215
|
+
return sorted;
|
|
216
|
+
}
|
|
217
|
+
function flattenKeys(value, prefix = '') {
|
|
218
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
219
|
+
return prefix ? [prefix] : [];
|
|
220
|
+
}
|
|
221
|
+
const source = value;
|
|
222
|
+
const keys = [];
|
|
223
|
+
for (const key of Object.keys(source).sort((left, right) => left.localeCompare(right))) {
|
|
224
|
+
const nextPrefix = prefix ? `${prefix}.${key}` : key;
|
|
225
|
+
const nested = flattenKeys(source[key], nextPrefix);
|
|
226
|
+
if (nested.length === 0) {
|
|
227
|
+
keys.push(nextPrefix);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
keys.push(...nested);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return keys;
|
|
234
|
+
}
|
|
235
|
+
function parseSnapshotRetention() {
|
|
236
|
+
const env = process.env.NEURCODE_CONTROL_PLANE_SNAPSHOT_RETENTION;
|
|
237
|
+
if (!env)
|
|
238
|
+
return DEFAULT_SNAPSHOT_RETENTION;
|
|
239
|
+
const parsed = Number.parseInt(env, 10);
|
|
240
|
+
if (!Number.isFinite(parsed) || parsed < 1)
|
|
241
|
+
return DEFAULT_SNAPSHOT_RETENTION;
|
|
242
|
+
return Math.max(10, Math.min(2000, parsed));
|
|
243
|
+
}
|
|
244
|
+
function listSnapshots(paths) {
|
|
245
|
+
if (!(0, fs_1.existsSync)(paths.snapshotsDir))
|
|
246
|
+
return [];
|
|
247
|
+
return (0, fs_1.readdirSync)(paths.snapshotsDir)
|
|
248
|
+
.filter((name) => name.startsWith('snapshot-') && name.endsWith('.json'))
|
|
249
|
+
.sort();
|
|
250
|
+
}
|
|
251
|
+
function pruneSnapshots(paths, keepLatest) {
|
|
252
|
+
const entries = listSnapshots(paths);
|
|
253
|
+
if (entries.length <= keepLatest)
|
|
254
|
+
return;
|
|
255
|
+
const toDelete = entries.slice(0, entries.length - keepLatest);
|
|
256
|
+
for (const fileName of toDelete) {
|
|
257
|
+
(0, fs_1.rmSync)((0, path_1.join)(paths.snapshotsDir, fileName), { force: true });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function sanitizeForSnapshot(value) {
|
|
261
|
+
if (value === null || typeof value === 'undefined')
|
|
262
|
+
return value;
|
|
263
|
+
if (typeof value === 'string') {
|
|
264
|
+
const masked = (0, secret_masking_1.maskSecretsInText)(value).masked;
|
|
265
|
+
if (masked.startsWith('/') || /^[A-Za-z]:\//.test(masked)) {
|
|
266
|
+
return '[REDACTED_PATH]';
|
|
267
|
+
}
|
|
268
|
+
return masked;
|
|
269
|
+
}
|
|
270
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
271
|
+
return value;
|
|
272
|
+
if (Array.isArray(value))
|
|
273
|
+
return value.map((entry) => sanitizeForSnapshot(entry));
|
|
274
|
+
if (typeof value === 'object') {
|
|
275
|
+
const source = value;
|
|
276
|
+
const next = {};
|
|
277
|
+
for (const key of Object.keys(source)) {
|
|
278
|
+
if (/(token|secret|password|api[_-]?key|authorization|cookie|private[_-]?key)/i.test(key)) {
|
|
279
|
+
next[key] = '[REDACTED_SECRET]';
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
next[key] = sanitizeForSnapshot(source[key]);
|
|
283
|
+
}
|
|
284
|
+
return next;
|
|
285
|
+
}
|
|
286
|
+
return String(value);
|
|
287
|
+
}
|
|
288
|
+
function mergeTopLevel(base, patch) {
|
|
289
|
+
const merged = { ...base };
|
|
290
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
291
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
292
|
+
const current = asObject(merged[key]);
|
|
293
|
+
merged[key] = mergeTopLevel(current, value);
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
merged[key] = value;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return merged;
|
|
300
|
+
}
|
|
301
|
+
function buildImpact(previous, current, requestedPatch) {
|
|
302
|
+
const items = [];
|
|
303
|
+
const pushImpact = (item) => {
|
|
304
|
+
items.push(item);
|
|
305
|
+
};
|
|
306
|
+
if (previous.runtime.execution.duplicateSuppression !== current.runtime.execution.duplicateSuppression) {
|
|
307
|
+
pushImpact({
|
|
308
|
+
id: 'runtime-duplicate-suppression',
|
|
309
|
+
severity: current.runtime.execution.duplicateSuppression ? 'medium' : 'low',
|
|
310
|
+
title: 'Duplicate suppression behavior changed',
|
|
311
|
+
summary: current.runtime.execution.duplicateSuppression
|
|
312
|
+
? 'Runtime now suppresses duplicate executions within the configured dedupe window.'
|
|
313
|
+
: 'Runtime now permits repeated executions without suppression.',
|
|
314
|
+
affectedSystems: ['runtime', 'daemon', 'dashboard', 'vscode', 'cli', 'ci'],
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
if (previous.runtime.retention.executionRecords !== current.runtime.retention.executionRecords) {
|
|
318
|
+
pushImpact({
|
|
319
|
+
id: 'runtime-execution-retention',
|
|
320
|
+
severity: current.runtime.retention.executionRecords < previous.runtime.retention.executionRecords ? 'medium' : 'low',
|
|
321
|
+
title: 'Execution retention changed',
|
|
322
|
+
summary: `Execution history retention updated from ${previous.runtime.retention.executionRecords} to ${current.runtime.retention.executionRecords}.`,
|
|
323
|
+
affectedSystems: ['runtime', 'dashboard', 'daemon'],
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
if (previous.eventRuntime.retention.maxEvents !== current.eventRuntime.retention.maxEvents) {
|
|
327
|
+
pushImpact({
|
|
328
|
+
id: 'event-retention',
|
|
329
|
+
severity: current.eventRuntime.retention.maxEvents < previous.eventRuntime.retention.maxEvents ? 'medium' : 'low',
|
|
330
|
+
title: 'Runtime event retention changed',
|
|
331
|
+
summary: `Event retention updated from ${previous.eventRuntime.retention.maxEvents} to ${current.eventRuntime.retention.maxEvents}.`,
|
|
332
|
+
affectedSystems: ['events', 'dashboard', 'vscode', 'daemon'],
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
if (previous.remediation.automation.autonomousApplySafe !== current.remediation.automation.autonomousApplySafe) {
|
|
336
|
+
pushImpact({
|
|
337
|
+
id: 'remediation-autonomy',
|
|
338
|
+
severity: current.remediation.automation.autonomousApplySafe ? 'high' : 'low',
|
|
339
|
+
title: 'Autonomous remediation permission changed',
|
|
340
|
+
summary: current.remediation.automation.autonomousApplySafe
|
|
341
|
+
? 'Safe patch application can run autonomously when triggered by governance actions.'
|
|
342
|
+
: 'Safe patch application now requires explicit manual action.',
|
|
343
|
+
affectedSystems: ['remediation', 'runtime', 'dashboard', 'vscode', 'ci'],
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
if (previous.ciGovernance.enforcement.strictness !== current.ciGovernance.enforcement.strictness) {
|
|
347
|
+
pushImpact({
|
|
348
|
+
id: 'ci-strictness',
|
|
349
|
+
severity: current.ciGovernance.enforcement.strictness === 'strict' ? 'high' : 'medium',
|
|
350
|
+
title: 'CI governance strictness changed',
|
|
351
|
+
summary: current.ciGovernance.enforcement.strictness === 'strict'
|
|
352
|
+
? 'CI will fail builds on blocking governance findings.'
|
|
353
|
+
: 'CI allows advisory posture where some governance failures become warnings.',
|
|
354
|
+
affectedSystems: ['ci', 'runtime', 'dashboard', 'cli'],
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
if (previous.evidence.collection.enabledByDefault !== current.evidence.collection.enabledByDefault) {
|
|
358
|
+
pushImpact({
|
|
359
|
+
id: 'evidence-default',
|
|
360
|
+
severity: current.evidence.collection.enabledByDefault ? 'low' : 'medium',
|
|
361
|
+
title: 'Evidence default behavior changed',
|
|
362
|
+
summary: current.evidence.collection.enabledByDefault
|
|
363
|
+
? 'Verification evidence artifacts are enabled by default.'
|
|
364
|
+
: 'Verification evidence artifacts now require explicit opt-in.',
|
|
365
|
+
affectedSystems: ['evidence', 'runtime', 'ci', 'dashboard'],
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
if (requestedPatch.policyGovernance) {
|
|
369
|
+
pushImpact({
|
|
370
|
+
id: 'policy-governance-update',
|
|
371
|
+
severity: 'medium',
|
|
372
|
+
title: 'Policy governance settings updated',
|
|
373
|
+
summary: 'Exception approvals and policy audit requirements may change verification and remediation behavior.',
|
|
374
|
+
affectedSystems: ['policy', 'runtime', 'dashboard', 'cli', 'ci'],
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
const changedSections = Object.keys(requestedPatch).filter((key) => {
|
|
378
|
+
const value = requestedPatch[key];
|
|
379
|
+
return value && typeof value === 'object' && Object.keys(value).length > 0;
|
|
380
|
+
});
|
|
381
|
+
const changedKeys = flattenKeys(requestedPatch);
|
|
382
|
+
const riskLevel = items.some((item) => item.severity === 'high')
|
|
383
|
+
? 'high'
|
|
384
|
+
: items.some((item) => item.severity === 'medium')
|
|
385
|
+
? 'medium'
|
|
386
|
+
: 'low';
|
|
387
|
+
return {
|
|
388
|
+
schemaVersion: 'neurcode.control-plane.impact.v1',
|
|
389
|
+
generatedAt: nowIso(),
|
|
390
|
+
riskLevel,
|
|
391
|
+
changedSections,
|
|
392
|
+
changedKeys,
|
|
393
|
+
items,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function readState(cwd) {
|
|
397
|
+
const projectRoot = (0, path_1.resolve)(cwd);
|
|
398
|
+
const paths = toPaths(projectRoot);
|
|
399
|
+
const runtime = normalizeRuntimeConfig(readJsonFile(paths.runtimePath));
|
|
400
|
+
const remediation = normalizeRemediationConfig(readJsonFile(paths.remediationPath));
|
|
401
|
+
const evidence = normalizeEvidenceConfig(readJsonFile(paths.evidencePath));
|
|
402
|
+
const eventRuntime = normalizeEventRuntimeConfig(readJsonFile(paths.eventRuntimePath));
|
|
403
|
+
const ciGovernance = normalizeCiGovernanceConfig(readJsonFile(paths.ciGovernancePath));
|
|
404
|
+
const policyGovernance = (0, policy_governance_1.readPolicyGovernanceConfig)(projectRoot);
|
|
405
|
+
const snapshots = listSnapshots(paths);
|
|
406
|
+
const latestSnapshotName = snapshots.length > 0 ? snapshots[snapshots.length - 1] : null;
|
|
407
|
+
let latestSnapshotId = null;
|
|
408
|
+
let latestSnapshotAt = null;
|
|
409
|
+
if (latestSnapshotName) {
|
|
410
|
+
const fullPath = (0, path_1.join)(paths.snapshotsDir, latestSnapshotName);
|
|
411
|
+
const parsed = readJsonFile(fullPath);
|
|
412
|
+
latestSnapshotId = typeof parsed.snapshotId === 'string' ? parsed.snapshotId : null;
|
|
413
|
+
latestSnapshotAt = typeof parsed.createdAt === 'string' ? parsed.createdAt : null;
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
schemaVersion: CONTROL_PLANE_SCHEMA,
|
|
417
|
+
generatedAt: nowIso(),
|
|
418
|
+
rootDir: paths.rootDir,
|
|
419
|
+
runtime,
|
|
420
|
+
remediation,
|
|
421
|
+
evidence,
|
|
422
|
+
eventRuntime,
|
|
423
|
+
ciGovernance,
|
|
424
|
+
policyGovernance,
|
|
425
|
+
metadata: {
|
|
426
|
+
files: {
|
|
427
|
+
runtime: paths.runtimePath,
|
|
428
|
+
remediation: paths.remediationPath,
|
|
429
|
+
evidence: paths.evidencePath,
|
|
430
|
+
eventRuntime: paths.eventRuntimePath,
|
|
431
|
+
ciGovernance: paths.ciGovernancePath,
|
|
432
|
+
policyGovernance: (0, path_1.resolve)(projectRoot, 'neurcode.policy.governance.json'),
|
|
433
|
+
},
|
|
434
|
+
snapshots: {
|
|
435
|
+
directory: paths.snapshotsDir,
|
|
436
|
+
retentionLimit: parseSnapshotRetention(),
|
|
437
|
+
count: snapshots.length,
|
|
438
|
+
latestPath: latestSnapshotName ? (0, path_1.join)(paths.snapshotsDir, latestSnapshotName) : null,
|
|
439
|
+
latestId: latestSnapshotId,
|
|
440
|
+
latestAt: latestSnapshotAt,
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function writeConfigFile(pathValue, payload) {
|
|
446
|
+
(0, fs_1.writeFileSync)(pathValue, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8');
|
|
447
|
+
}
|
|
448
|
+
function writeSnapshot(cwd, actor, source, reason, before, after, impact) {
|
|
449
|
+
const paths = toPaths(cwd);
|
|
450
|
+
const createdAt = nowIso();
|
|
451
|
+
const digest = (0, crypto_1.createHash)('sha256')
|
|
452
|
+
.update(JSON.stringify({ createdAt, actor, source, reason, impact }))
|
|
453
|
+
.digest('hex')
|
|
454
|
+
.slice(0, 12);
|
|
455
|
+
const snapshotId = `cps-${digest}`;
|
|
456
|
+
const fileName = `snapshot-${createdAt.replace(/[.:]/g, '-')}-${snapshotId}.json`;
|
|
457
|
+
const snapshotPath = (0, path_1.join)(paths.snapshotsDir, fileName);
|
|
458
|
+
const beforeState = {
|
|
459
|
+
runtime: before.runtime,
|
|
460
|
+
remediation: before.remediation,
|
|
461
|
+
evidence: before.evidence,
|
|
462
|
+
eventRuntime: before.eventRuntime,
|
|
463
|
+
ciGovernance: before.ciGovernance,
|
|
464
|
+
policyGovernance: before.policyGovernance,
|
|
465
|
+
};
|
|
466
|
+
const afterState = {
|
|
467
|
+
runtime: after.runtime,
|
|
468
|
+
remediation: after.remediation,
|
|
469
|
+
evidence: after.evidence,
|
|
470
|
+
eventRuntime: after.eventRuntime,
|
|
471
|
+
ciGovernance: after.ciGovernance,
|
|
472
|
+
policyGovernance: after.policyGovernance,
|
|
473
|
+
};
|
|
474
|
+
const record = {
|
|
475
|
+
schemaVersion: CONTROL_PLANE_SNAPSHOT_SCHEMA,
|
|
476
|
+
snapshotId,
|
|
477
|
+
createdAt,
|
|
478
|
+
actor,
|
|
479
|
+
source,
|
|
480
|
+
reason,
|
|
481
|
+
impact,
|
|
482
|
+
beforeHash: hashPayload(beforeState),
|
|
483
|
+
afterHash: hashPayload(afterState),
|
|
484
|
+
state: sanitizeForSnapshot(afterState),
|
|
485
|
+
};
|
|
486
|
+
(0, fs_1.writeFileSync)(snapshotPath, `${JSON.stringify(record, null, 2)}\n`, 'utf-8');
|
|
487
|
+
pruneSnapshots(paths, parseSnapshotRetention());
|
|
488
|
+
return { snapshotPath, snapshotId };
|
|
489
|
+
}
|
|
490
|
+
function mergePatchIntoState(current, patch) {
|
|
491
|
+
const runtime = patch.runtime
|
|
492
|
+
? normalizeRuntimeConfig(mergeTopLevel(current.runtime, patch.runtime))
|
|
493
|
+
: current.runtime;
|
|
494
|
+
const remediation = patch.remediation
|
|
495
|
+
? normalizeRemediationConfig(mergeTopLevel(current.remediation, patch.remediation))
|
|
496
|
+
: current.remediation;
|
|
497
|
+
const evidence = patch.evidence
|
|
498
|
+
? normalizeEvidenceConfig(mergeTopLevel(current.evidence, patch.evidence))
|
|
499
|
+
: current.evidence;
|
|
500
|
+
const eventRuntime = patch.eventRuntime
|
|
501
|
+
? normalizeEventRuntimeConfig(mergeTopLevel(current.eventRuntime, patch.eventRuntime))
|
|
502
|
+
: current.eventRuntime;
|
|
503
|
+
const ciGovernance = patch.ciGovernance
|
|
504
|
+
? normalizeCiGovernanceConfig(mergeTopLevel(current.ciGovernance, patch.ciGovernance))
|
|
505
|
+
: current.ciGovernance;
|
|
506
|
+
let policyGovernance = current.policyGovernance;
|
|
507
|
+
if (patch.policyGovernance) {
|
|
508
|
+
const source = patch.policyGovernance;
|
|
509
|
+
policyGovernance = {
|
|
510
|
+
...policyGovernance,
|
|
511
|
+
exceptionApprovals: {
|
|
512
|
+
...policyGovernance.exceptionApprovals,
|
|
513
|
+
...(typeof source.required === 'boolean' ? { required: source.required } : {}),
|
|
514
|
+
...(Number.isFinite(source.minApprovals) ? { minApprovals: source.minApprovals } : {}),
|
|
515
|
+
...(typeof source.disallowSelfApproval === 'boolean' ? { disallowSelfApproval: source.disallowSelfApproval } : {}),
|
|
516
|
+
...(Array.isArray(source.allowedApprovers) ? { allowedApprovers: normalizeList(source.allowedApprovers) } : {}),
|
|
517
|
+
...(typeof source.requireReason === 'boolean' ? { requireReason: source.requireReason } : {}),
|
|
518
|
+
...(Number.isFinite(source.minReasonLength) ? { minReasonLength: source.minReasonLength } : {}),
|
|
519
|
+
...(Number.isFinite(source.maxExpiryDays) ? { maxExpiryDays: source.maxExpiryDays } : {}),
|
|
520
|
+
...(Array.isArray(source.criticalRulePatterns)
|
|
521
|
+
? { criticalRulePatterns: normalizeList(source.criticalRulePatterns) }
|
|
522
|
+
: {}),
|
|
523
|
+
...(Number.isFinite(source.criticalMinApprovals)
|
|
524
|
+
? { criticalMinApprovals: source.criticalMinApprovals }
|
|
525
|
+
: {}),
|
|
526
|
+
},
|
|
527
|
+
audit: {
|
|
528
|
+
...policyGovernance.audit,
|
|
529
|
+
...(typeof source.requireAuditIntegrity === 'boolean'
|
|
530
|
+
? { requireIntegrity: source.requireAuditIntegrity }
|
|
531
|
+
: {}),
|
|
532
|
+
},
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
...current,
|
|
537
|
+
generatedAt: nowIso(),
|
|
538
|
+
runtime,
|
|
539
|
+
remediation,
|
|
540
|
+
evidence,
|
|
541
|
+
eventRuntime,
|
|
542
|
+
ciGovernance,
|
|
543
|
+
policyGovernance,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
function persistState(cwd, state, patch) {
|
|
547
|
+
const paths = toPaths(cwd);
|
|
548
|
+
if (patch.runtime) {
|
|
549
|
+
writeConfigFile(paths.runtimePath, state.runtime);
|
|
550
|
+
}
|
|
551
|
+
if (patch.remediation) {
|
|
552
|
+
writeConfigFile(paths.remediationPath, state.remediation);
|
|
553
|
+
}
|
|
554
|
+
if (patch.evidence) {
|
|
555
|
+
writeConfigFile(paths.evidencePath, state.evidence);
|
|
556
|
+
}
|
|
557
|
+
if (patch.eventRuntime) {
|
|
558
|
+
writeConfigFile(paths.eventRuntimePath, state.eventRuntime);
|
|
559
|
+
}
|
|
560
|
+
if (patch.ciGovernance) {
|
|
561
|
+
writeConfigFile(paths.ciGovernancePath, state.ciGovernance);
|
|
562
|
+
}
|
|
563
|
+
if (patch.policyGovernance) {
|
|
564
|
+
(0, policy_governance_1.updatePolicyGovernanceConfig)(cwd, patch.policyGovernance);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function readControlPlaneState(cwd = process.cwd()) {
|
|
568
|
+
return readState((0, path_1.resolve)(cwd));
|
|
569
|
+
}
|
|
570
|
+
function previewControlPlaneUpdate(patch, cwd = process.cwd()) {
|
|
571
|
+
const projectRoot = (0, path_1.resolve)(cwd);
|
|
572
|
+
const previous = readState(projectRoot);
|
|
573
|
+
const current = mergePatchIntoState(previous, patch);
|
|
574
|
+
const impact = buildImpact(previous, current, patch);
|
|
575
|
+
return { previous, current, impact };
|
|
576
|
+
}
|
|
577
|
+
function applyControlPlaneUpdate(patch, options) {
|
|
578
|
+
const projectRoot = (0, path_1.resolve)(options?.cwd || process.cwd());
|
|
579
|
+
const actor = options?.actor && options.actor.trim().length > 0 ? options.actor.trim() : 'control-plane-user';
|
|
580
|
+
const source = options?.source || 'unknown';
|
|
581
|
+
const reason = options?.reason && options.reason.trim().length > 0
|
|
582
|
+
? (0, secret_masking_1.maskSecretsInText)(options.reason.trim()).masked
|
|
583
|
+
: 'Control plane update';
|
|
584
|
+
const previous = readState(projectRoot);
|
|
585
|
+
const current = mergePatchIntoState(previous, patch);
|
|
586
|
+
const impact = buildImpact(previous, current, patch);
|
|
587
|
+
persistState(projectRoot, current, patch);
|
|
588
|
+
const persisted = readState(projectRoot);
|
|
589
|
+
const snapshot = writeSnapshot(projectRoot, actor, source, reason, previous, persisted, impact);
|
|
590
|
+
const syntheticExecution = (0, execution_bus_1.recordSyntheticExecution)({
|
|
591
|
+
cwd: projectRoot,
|
|
592
|
+
type: 'policy-sync',
|
|
593
|
+
source,
|
|
594
|
+
actor,
|
|
595
|
+
target: null,
|
|
596
|
+
status: 'completed',
|
|
597
|
+
success: true,
|
|
598
|
+
message: reason,
|
|
599
|
+
payload: {
|
|
600
|
+
schemaVersion: CONTROL_PLANE_SCHEMA,
|
|
601
|
+
action: 'control-plane.update',
|
|
602
|
+
changedSections: impact.changedSections,
|
|
603
|
+
changedKeys: impact.changedKeys,
|
|
604
|
+
riskLevel: impact.riskLevel,
|
|
605
|
+
snapshotId: snapshot.snapshotId,
|
|
606
|
+
},
|
|
607
|
+
verification: {
|
|
608
|
+
verdict: impact.riskLevel === 'high' ? 'WARN' : 'PASS',
|
|
609
|
+
counts: {
|
|
610
|
+
blocking: impact.riskLevel === 'high' ? 1 : 0,
|
|
611
|
+
advisory: impact.riskLevel === 'low' ? 0 : impact.items.length,
|
|
612
|
+
},
|
|
613
|
+
},
|
|
614
|
+
evidenceReferences: [snapshot.snapshotPath],
|
|
615
|
+
narrative: {
|
|
616
|
+
status: impact.riskLevel === 'high' ? 'warning' : 'success',
|
|
617
|
+
summary: `Control plane updated (${impact.changedSections.join(', ') || 'no changes detected'})`,
|
|
618
|
+
why: reason,
|
|
619
|
+
riskLevel: impact.riskLevel,
|
|
620
|
+
recommendedAction: impact.riskLevel === 'high'
|
|
621
|
+
? 'Review governance impact preview and re-run verify in CI mode.'
|
|
622
|
+
: 'Run verify to confirm updated governance posture.',
|
|
623
|
+
expectedImprovement: 'Runtime governance behavior now aligns with the updated control-plane configuration.',
|
|
624
|
+
},
|
|
625
|
+
eventDetails: {
|
|
626
|
+
stage: 'narrating',
|
|
627
|
+
reason,
|
|
628
|
+
},
|
|
629
|
+
});
|
|
630
|
+
try {
|
|
631
|
+
(0, runtime_events_1.emitRuntimeEvent)(projectRoot, {
|
|
632
|
+
type: 'governance.config.updated',
|
|
633
|
+
executionId: syntheticExecution.id,
|
|
634
|
+
source,
|
|
635
|
+
actor,
|
|
636
|
+
severity: impact.riskLevel,
|
|
637
|
+
payload: {
|
|
638
|
+
changedSections: impact.changedSections,
|
|
639
|
+
changedKeys: impact.changedKeys,
|
|
640
|
+
riskLevel: impact.riskLevel,
|
|
641
|
+
snapshotId: snapshot.snapshotId,
|
|
642
|
+
snapshotPath: snapshot.snapshotPath,
|
|
643
|
+
},
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
catch {
|
|
647
|
+
// Best-effort runtime event emission only.
|
|
648
|
+
}
|
|
649
|
+
return {
|
|
650
|
+
previous,
|
|
651
|
+
current: persisted,
|
|
652
|
+
impact,
|
|
653
|
+
snapshotPath: snapshot.snapshotPath,
|
|
654
|
+
snapshotId: snapshot.snapshotId,
|
|
655
|
+
executionId: syntheticExecution.id,
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
function readControlPlaneSnapshotHistory(cwd = process.cwd(), limit = 25) {
|
|
659
|
+
const projectRoot = (0, path_1.resolve)(cwd);
|
|
660
|
+
const paths = toPaths(projectRoot);
|
|
661
|
+
const entries = listSnapshots(paths).reverse().slice(0, Math.max(1, Math.min(250, Math.floor(limit))));
|
|
662
|
+
const results = [];
|
|
663
|
+
for (const fileName of entries) {
|
|
664
|
+
const snapshotPath = (0, path_1.join)(paths.snapshotsDir, fileName);
|
|
665
|
+
const payload = readJsonFile(snapshotPath);
|
|
666
|
+
const impact = asObject(payload.impact);
|
|
667
|
+
const sections = Array.isArray(impact.changedSections)
|
|
668
|
+
? impact.changedSections.filter((entry) => typeof entry === 'string')
|
|
669
|
+
: [];
|
|
670
|
+
const riskLevel = impact.riskLevel === 'high' || impact.riskLevel === 'medium' ? impact.riskLevel : 'low';
|
|
671
|
+
const source = asString(payload.source, 'unknown');
|
|
672
|
+
results.push({
|
|
673
|
+
snapshotId: asString(payload.snapshotId, fileName),
|
|
674
|
+
createdAt: asString(payload.createdAt, nowIso()),
|
|
675
|
+
actor: asString(payload.actor, 'control-plane-user'),
|
|
676
|
+
source,
|
|
677
|
+
riskLevel,
|
|
678
|
+
snapshotPath,
|
|
679
|
+
changedSections: sections,
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
return results;
|
|
683
|
+
}
|
|
684
|
+
//# sourceMappingURL=control-plane.js.map
|