log-llm-config 1.3.6 → 1.3.7

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.
@@ -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
- if (deferredSqlitePending || recheckOk) {
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 (e.g. cursor/thirdPartyExtensibilityEnabled stores `true`/`false`).
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
- const leaf = itemKey.split('/').pop() ?? '';
96
- // Legacy cursorai/donotchange/privacyMode row is bare true/false; compliance paths use …/privacyMode.privacyMode.
97
- if (typeof parsed === 'boolean' && leaf === 'privacyMode') {
98
- return { [itemKey]: { privacyMode: parsed } };
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,12 +5,15 @@
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 (local files only).
9
- * Background: compliance_check_runner runs syncRemediations (network) then the same local check.
8
+ * Prompt gate: compliance_prompt_gate runs runLocalRemediationComplianceCheck then applyAutofixViolations.
9
+ * Background: compliance_check_runner runs syncRemediations (network), the same local check, then
10
+ * applyAutofixViolations + executeTrustedRestartCommands so remediations apply even when only the
11
+ * background path runs (sqlite/vscdb updates remain Cursor-specific; restarts use the shared allowlist).
10
12
  */
11
- import { existsSync, readFileSync } from 'node:fs';
13
+ import { existsSync, readFileSync, statSync } from 'node:fs';
12
14
  import { readVscdbItemTableJson } from '../readers/vscdb_reader.js';
13
- import { readRemediationInstructionsFile, writeRemediationInstructionsFile } from './management_storage.js';
15
+ import { getRemediationInstructionsPath, readRemediationInstructionsFile, writeRemediationInstructionsFile, } from './management_storage.js';
16
+ import { executeTrustedRestartCommands } from './trusted_restarts.js';
14
17
  import { complianceRunnerDiag, hookRunLog } from './hook_logger.js';
15
18
  import { loadEndpointBase } from '../sender/endpoint_config.js';
16
19
  import { resolveHardwareUuid, tryResolveHardwareUuid } from './hardware_uuid.js';
@@ -431,9 +434,22 @@ export function pruneSatisfiedOneTimeRemediations() {
431
434
  }
432
435
  return { removed, reportPromises };
433
436
  }
437
+ /** Same staleness window as compliance_prompt_gate: avoid applying very old local manifests. */
438
+ const MANIFEST_STALE_MS = 7 * 24 * 60 * 60 * 1000;
439
+ function getManifestStalenessMs() {
440
+ try {
441
+ const p = getRemediationInstructionsPath();
442
+ if (!existsSync(p))
443
+ return null;
444
+ return Date.now() - statSync(p).mtimeMs;
445
+ }
446
+ catch {
447
+ return null;
448
+ }
449
+ }
434
450
  /**
435
- * Background refresh: server sync for latest instructions, then the same local evaluation as the hook.
436
- * Does not persist compliance state to disk.
451
+ * Background refresh: server sync, local evaluation, then autofix + trusted restarts (same enforcement
452
+ * as the prompt gate, without blocking stdin/stdout for the IDE).
437
453
  */
438
454
  export async function runComplianceCheck() {
439
455
  try {
@@ -442,5 +458,37 @@ export async function runComplianceCheck() {
442
458
  catch (err) {
443
459
  hookRunLog(`compliance_check: remediation_sync unexpected error: ${err instanceof Error ? err.message : String(err)}`);
444
460
  }
445
- runLocalRemediationComplianceCheck();
461
+ const status = runLocalRemediationComplianceCheck();
462
+ if (status.status === 'ok' || status.violations.length === 0) {
463
+ const pruned = pruneSatisfiedOneTimeRemediations();
464
+ if (pruned.removed > 0) {
465
+ await Promise.allSettled(pruned.reportPromises);
466
+ }
467
+ return;
468
+ }
469
+ const staleMs = getManifestStalenessMs();
470
+ if (staleMs !== null && staleMs > MANIFEST_STALE_MS) {
471
+ const staleDays = Math.floor(staleMs / (24 * 60 * 60 * 1000));
472
+ hookRunLog(`compliance_check_runner: skip autofix — local remediation manifest is stale (${staleDays} days old)`);
473
+ return;
474
+ }
475
+ const { fixed, restartCommands, failedViolations, reportPromises } = applyAutofixViolations(status.violations);
476
+ if (fixed === 0) {
477
+ if (failedViolations.length > 0) {
478
+ hookRunLog(`compliance_check_runner: autofix failed for ${failedViolations.length} violation(s) (see autofix logs above)`);
479
+ }
480
+ return;
481
+ }
482
+ await Promise.allSettled(reportPromises);
483
+ if (failedViolations.length > 0) {
484
+ hookRunLog(`compliance_check_runner: autofix partial failure — ${failedViolations.length} violation(s) still unresolved`);
485
+ }
486
+ if (restartCommands.length > 0) {
487
+ hookRunLog(`compliance_check_runner: executing ${restartCommands.length} trusted restart command(s)`);
488
+ executeTrustedRestartCommands(restartCommands);
489
+ }
490
+ const pruned = pruneSatisfiedOneTimeRemediations();
491
+ if (pruned.removed > 0) {
492
+ await Promise.allSettled(pruned.reportPromises);
493
+ }
446
494
  }
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "log-llm-config",
3
- "version": "1.3.6",
3
+ "version": "1.3.7",
4
4
  "description": "CLI helpers for logging hardware UUIDs and posting startup payloads to Optimus Security.",
5
5
  "type": "module",
6
6
  "bin": {