log-llm-config 1.3.6 → 1.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compliance_check_runner.js +3 -3
- package/dist/compliance_prompt_gate.js +13 -2
- package/dist/log_config_files/readers/vscdb_reader.js +15 -5
- package/dist/log_config_files/runtime/compliance_check.js +12 -3
- package/dist/log_config_files/runtime/hook_logger.js +45 -5
- package/dist/log_config_files/runtime/remediation_sync.js +1 -7
- package/package.json +1 -1
|
@@ -2,9 +2,9 @@ import { appendFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { OPT_AI_SEC_MANAGEMENT_REL } from './bootstrap_constants.js';
|
|
5
|
-
import {
|
|
5
|
+
import { hookLogAppendSection } from './log_config_files/runtime/hook_logger.js';
|
|
6
6
|
import { runComplianceCheck } from './log_config_files/runtime/compliance_check.js';
|
|
7
|
-
/** Append-only log for compliance runner lifecycle; hook_log.txt
|
|
7
|
+
/** Append-only log for compliance runner lifecycle; hook_log.txt uses a fresh session from the gate then this section. */
|
|
8
8
|
function runnerFileLog(message) {
|
|
9
9
|
try {
|
|
10
10
|
const dir = join(homedir(), OPT_AI_SEC_MANAGEMENT_REL);
|
|
@@ -18,7 +18,7 @@ function runnerFileLog(message) {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
(async () => {
|
|
21
|
-
|
|
21
|
+
hookLogAppendSection('compliance_check_runner (background sync + check)');
|
|
22
22
|
runnerFileLog('compliance_check_runner: start');
|
|
23
23
|
try {
|
|
24
24
|
await runComplianceCheck();
|
|
@@ -10,7 +10,7 @@ import { existsSync, statSync } from 'node:fs';
|
|
|
10
10
|
import { pathToFileURL } from 'node:url';
|
|
11
11
|
import { resolve } from 'node:path';
|
|
12
12
|
import { getRemediationInstructionsPath } from './log_config_files/runtime/management_storage.js';
|
|
13
|
-
import { hookLogSessionBanner } from './log_config_files/runtime/hook_logger.js';
|
|
13
|
+
import { hookLogSessionBanner, hookRunLog } from './log_config_files/runtime/hook_logger.js';
|
|
14
14
|
const MANIFEST_STALE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
15
15
|
function parseIde() {
|
|
16
16
|
const eq = process.argv.find((a) => a.startsWith('--ide='));
|
|
@@ -111,7 +111,18 @@ export async function runCompliancePromptGate() {
|
|
|
111
111
|
}
|
|
112
112
|
const recheck = runLocalRemediationComplianceCheck();
|
|
113
113
|
const recheckOk = recheck.status === 'ok' || recheck.violations.length === 0;
|
|
114
|
-
|
|
114
|
+
// Cursor: tolerate a failing recheck only when SQLite updates are deferred (apply after restart).
|
|
115
|
+
// Claude Code: JSON remediations are written immediately; merge/verify timing can still leave the
|
|
116
|
+
// in-process recheck red for the same UUID — allow in that case only for --ide=claude.
|
|
117
|
+
const appliedUuids = new Set(appliedViolations.map((v) => v.uuid));
|
|
118
|
+
const claudeRecheckStaleAfterImmediateApply = ide === 'claude' &&
|
|
119
|
+
!recheckOk &&
|
|
120
|
+
recheck.violations.length > 0 &&
|
|
121
|
+
recheck.violations.every((v) => appliedUuids.has(v.uuid));
|
|
122
|
+
if (claudeRecheckStaleAfterImmediateApply) {
|
|
123
|
+
hookRunLog('compliance_prompt_gate: Claude — autofix wrote JSON; recheck still flags same UUID(s) — proceeding (immediate apply)');
|
|
124
|
+
}
|
|
125
|
+
if (deferredSqlitePending || recheckOk || claudeRecheckStaleAfterImmediateApply) {
|
|
115
126
|
const autofixMessage = `Optimus Security auto-fixed ${fixed} policy violation(s):\n${appliedViolations.map((v) => autofixDialogLine(v)).join('\n')}`;
|
|
116
127
|
const payload = { __optimus_autofix: true, autofix_message: autofixMessage };
|
|
117
128
|
if (restartCommands.length > 0)
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
3
|
import { readFileCollectionVscdbContract, writeFileCollectionVscdbContract, } from '../runtime/management_storage.js';
|
|
4
|
+
/**
|
|
5
|
+
* ItemTable keys that store a bare JSON boolean; compliance paths use `${key}.${field}`.
|
|
6
|
+
* Kept in sync with `coerceItemTableValueToObjectRoot` / `serializeItemTableValueForWrite` in remediation_sync.
|
|
7
|
+
*/
|
|
8
|
+
export const CURSOR_SCALAR_ITEMTABLE_FIELDS = {
|
|
9
|
+
'cursor/thirdPartyExtensibilityEnabled': 'thirdPartyExtensibilityEnabled',
|
|
10
|
+
'cursorai/donotchange/privacyMode': 'privacyMode',
|
|
11
|
+
'cursor/autoOpenLocalhostUrls': 'autoOpenLocalhostUrls',
|
|
12
|
+
};
|
|
4
13
|
function querySqlite(dbPath, key) {
|
|
5
14
|
const safe = key.replace(/'/g, "''");
|
|
6
15
|
return execSync(`sqlite3 "${dbPath}" "SELECT value FROM ItemTable WHERE key='${safe}'"`, {
|
|
@@ -90,12 +99,13 @@ export function readVscdbItemTableJson(dbPath, itemKey) {
|
|
|
90
99
|
if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
91
100
|
return { [itemKey]: parsed };
|
|
92
101
|
}
|
|
93
|
-
// Bare JSON primitives (
|
|
102
|
+
// Bare JSON primitives. Scalar toggles must be wrapped so getByPath(key.field) works in compliance checks.
|
|
94
103
|
if (typeof parsed === 'boolean' || typeof parsed === 'number' || typeof parsed === 'string') {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
104
|
+
if (typeof parsed === 'boolean') {
|
|
105
|
+
const field = CURSOR_SCALAR_ITEMTABLE_FIELDS[itemKey];
|
|
106
|
+
if (field) {
|
|
107
|
+
return { [itemKey]: { [field]: parsed } };
|
|
108
|
+
}
|
|
99
109
|
}
|
|
100
110
|
return { [itemKey]: parsed };
|
|
101
111
|
}
|
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
* ComplianceStatus; the prompt gate and tests use that return value. Autofix may write config files
|
|
6
6
|
* and remediation_instructions.json via applyAutofixViolations / enforceRemediation.
|
|
7
7
|
*
|
|
8
|
-
* Prompt gate: compliance_prompt_gate runs runLocalRemediationComplianceCheck
|
|
8
|
+
* Prompt gate: compliance_prompt_gate runs runLocalRemediationComplianceCheck then applyAutofixViolations.
|
|
9
9
|
* Background: compliance_check_runner runs syncRemediations (network) then the same local check.
|
|
10
|
+
* Apply (autofix) is intentionally left to the gate on the next prompt — the background pass only
|
|
11
|
+
* downloads the latest manifest so the gate has fresh data to act on.
|
|
10
12
|
*/
|
|
11
13
|
import { existsSync, readFileSync } from 'node:fs';
|
|
12
14
|
import { readVscdbItemTableJson } from '../readers/vscdb_reader.js';
|
|
@@ -433,7 +435,8 @@ export function pruneSatisfiedOneTimeRemediations() {
|
|
|
433
435
|
}
|
|
434
436
|
/**
|
|
435
437
|
* Background refresh: server sync for latest instructions, then the same local evaluation as the hook.
|
|
436
|
-
*
|
|
438
|
+
* Apply (autofix) is intentionally deferred to the gate on the next prompt — this pass only downloads
|
|
439
|
+
* a fresh manifest so the gate has up-to-date data when it runs.
|
|
437
440
|
*/
|
|
438
441
|
export async function runComplianceCheck() {
|
|
439
442
|
try {
|
|
@@ -442,5 +445,11 @@ export async function runComplianceCheck() {
|
|
|
442
445
|
catch (err) {
|
|
443
446
|
hookRunLog(`compliance_check: remediation_sync unexpected error: ${err instanceof Error ? err.message : String(err)}`);
|
|
444
447
|
}
|
|
445
|
-
runLocalRemediationComplianceCheck();
|
|
448
|
+
const status = runLocalRemediationComplianceCheck();
|
|
449
|
+
if (status.status === 'ok' || status.violations.length === 0) {
|
|
450
|
+
const pruned = pruneSatisfiedOneTimeRemediations();
|
|
451
|
+
if (pruned.removed > 0) {
|
|
452
|
+
await Promise.allSettled(pruned.reportPromises);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
446
455
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, appendFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, mkdirSync, appendFileSync, writeFileSync, statSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { OPT_AI_SEC_MANAGEMENT_REL } from '../../bootstrap_constants.js';
|
|
4
4
|
const HOOK_LOG_FILENAME = 'hook_log.txt';
|
|
5
5
|
const COMPLIANCE_RUNNER_LOG_FILENAME = 'compliance_runner.log';
|
|
6
|
+
/** Hard cap so a single upload/sync session cannot grow hook_log.txt without bound. */
|
|
7
|
+
const MAX_HOOK_LOG_BYTES = 2 * 1024 * 1024;
|
|
6
8
|
function getHookLogPath() {
|
|
7
9
|
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
8
10
|
if (!homeDir)
|
|
@@ -33,7 +35,24 @@ function complianceRunnerDiag(message) {
|
|
|
33
35
|
// best-effort
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
|
-
|
|
38
|
+
function ensureHookLogUnderCap() {
|
|
39
|
+
const logPath = getHookLogPath();
|
|
40
|
+
if (!logPath || !existsSync(logPath))
|
|
41
|
+
return;
|
|
42
|
+
try {
|
|
43
|
+
if (statSync(logPath).size <= MAX_HOOK_LOG_BYTES)
|
|
44
|
+
return;
|
|
45
|
+
const ts = new Date().toISOString();
|
|
46
|
+
writeFileSync(logPath, `${'='.repeat(72)}\n${ts} hook_log.txt truncated (exceeded ${MAX_HOOK_LOG_BYTES} bytes)\n${'='.repeat(72)}\n`, 'utf8');
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// best-effort
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Start a new hook_log.txt session (replaces the file). Use once per logical run
|
|
54
|
+
* (e.g. compliance_prompt_gate, log_config_files upload via hookLogReplace).
|
|
55
|
+
*/
|
|
37
56
|
function hookLogSessionBanner(label) {
|
|
38
57
|
const logPath = getHookLogPath();
|
|
39
58
|
if (!logPath)
|
|
@@ -44,15 +63,34 @@ function hookLogSessionBanner(label) {
|
|
|
44
63
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
45
64
|
const ts = new Date().toISOString();
|
|
46
65
|
const banner = `\n${'='.repeat(72)}\n${ts} session: ${label}\n${'='.repeat(72)}\n`;
|
|
47
|
-
|
|
66
|
+
writeFileSync(logPath, banner, 'utf8');
|
|
48
67
|
}
|
|
49
68
|
catch {
|
|
50
69
|
// best-effort
|
|
51
70
|
}
|
|
52
71
|
}
|
|
53
72
|
/**
|
|
54
|
-
*
|
|
73
|
+
* Append a subsection after an existing session (e.g. compliance_check_runner after the gate)
|
|
74
|
+
* without replacing hook_log.txt.
|
|
55
75
|
*/
|
|
76
|
+
function hookLogAppendSection(label) {
|
|
77
|
+
const logPath = getHookLogPath();
|
|
78
|
+
if (!logPath)
|
|
79
|
+
return;
|
|
80
|
+
try {
|
|
81
|
+
ensureHookLogUnderCap();
|
|
82
|
+
const dir = path.dirname(logPath);
|
|
83
|
+
if (!existsSync(dir))
|
|
84
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
85
|
+
const ts = new Date().toISOString();
|
|
86
|
+
const banner = `\n${'-'.repeat(72)}\n${ts} section: ${label}\n${'-'.repeat(72)}\n`;
|
|
87
|
+
appendFileSync(logPath, banner, 'utf8');
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// best-effort
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Begins a log_config_files upload session (replaces hook_log.txt, same as hookLogSessionBanner). */
|
|
56
94
|
function hookLogReplace() {
|
|
57
95
|
hookLogSessionBanner('log_config_files (config upload)');
|
|
58
96
|
}
|
|
@@ -62,6 +100,7 @@ function hookRunLog(message) {
|
|
|
62
100
|
if (!logPath)
|
|
63
101
|
return;
|
|
64
102
|
try {
|
|
103
|
+
ensureHookLogUnderCap();
|
|
65
104
|
const ts = new Date().toISOString();
|
|
66
105
|
appendFileSync(logPath, `${ts} ${message}\n`, 'utf8');
|
|
67
106
|
}
|
|
@@ -75,10 +114,11 @@ function hookLogLine(message) {
|
|
|
75
114
|
if (!logPath)
|
|
76
115
|
return;
|
|
77
116
|
try {
|
|
117
|
+
ensureHookLogUnderCap();
|
|
78
118
|
appendFileSync(logPath, `${message}\n`, 'utf8');
|
|
79
119
|
}
|
|
80
120
|
catch {
|
|
81
121
|
// best-effort
|
|
82
122
|
}
|
|
83
123
|
}
|
|
84
|
-
export { getHookLogPath, getComplianceRunnerLogPath, hookLogReplace, hookLogSessionBanner, hookRunLog, hookLogLine, complianceRunnerDiag, };
|
|
124
|
+
export { getHookLogPath, getComplianceRunnerLogPath, hookLogReplace, hookLogSessionBanner, hookLogAppendSection, hookRunLog, hookLogLine, complianceRunnerDiag, };
|
|
@@ -8,7 +8,7 @@ import { readStoredAuthKey } from '../auth/auth_key_store.js';
|
|
|
8
8
|
import { createSignature } from '../sender/signing.js';
|
|
9
9
|
import { loadEndpointBase } from '../sender/endpoint_config.js';
|
|
10
10
|
import { tryResolveHardwareUuid } from './hardware_uuid.js';
|
|
11
|
-
import { persistVscdbComposerContractFromPatternsResponse, readVscdbItemTableJson, } from '../readers/vscdb_reader.js';
|
|
11
|
+
import { CURSOR_SCALAR_ITEMTABLE_FIELDS, persistVscdbComposerContractFromPatternsResponse, readVscdbItemTableJson, } from '../readers/vscdb_reader.js';
|
|
12
12
|
import { sendConfigFile } from '../sender/batch_sender.js';
|
|
13
13
|
import { getFileCollectionPatterns } from '../../endpoint_client/registry_api.js';
|
|
14
14
|
function reactiveStorageItemKeyFromContract() {
|
|
@@ -410,12 +410,6 @@ function resolveCursorComposerSqliteOp(dbPath, sqliteOp) {
|
|
|
410
410
|
return sqliteOp;
|
|
411
411
|
}
|
|
412
412
|
/** Apply sqlite merge: dot-path, or array match where `json_path` is `…container.arrayKey` (e.g. `modes4` or `composerState.modes4`). */
|
|
413
|
-
/** ItemTable keys that store a JSON primitive; map to one field for merge + serialize. */
|
|
414
|
-
const CURSOR_SCALAR_ITEMTABLE_FIELDS = {
|
|
415
|
-
'cursor/thirdPartyExtensibilityEnabled': 'thirdPartyExtensibilityEnabled',
|
|
416
|
-
'cursorai/donotchange/privacyMode': 'privacyMode',
|
|
417
|
-
'cursor/autoOpenLocalhostUrls': 'autoOpenLocalhostUrls',
|
|
418
|
-
};
|
|
419
413
|
function coerceScalarForItemTableField(parsed) {
|
|
420
414
|
if (typeof parsed === 'boolean')
|
|
421
415
|
return parsed;
|