@jagilber-org/index-server 1.22.1 → 1.26.4
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/CHANGELOG.md +91 -2
- package/CODE_OF_CONDUCT.md +2 -0
- package/CONTRIBUTING.md +32 -2
- package/README.md +82 -19
- package/SECURITY.md +17 -5
- package/dist/config/dashboardConfig.d.ts +3 -0
- package/dist/config/dashboardConfig.js +3 -0
- package/dist/config/defaultValues.d.ts +1 -1
- package/dist/config/defaultValues.js +1 -1
- package/dist/config/featureConfig.d.ts +2 -0
- package/dist/config/featureConfig.js +6 -1
- package/dist/config/runtimeConfig.d.ts +1 -1
- package/dist/config/runtimeConfig.js +8 -9
- package/dist/dashboard/client/admin.html +170 -53
- package/dist/dashboard/client/css/admin.css +132 -0
- package/dist/dashboard/client/js/admin.auth.js +25 -11
- package/dist/dashboard/client/js/admin.config.js +1 -1
- package/dist/dashboard/client/js/admin.feedback.js +328 -0
- package/dist/dashboard/client/js/admin.graph.js +120 -18
- package/dist/dashboard/client/js/admin.instructions.js +27 -13
- package/dist/dashboard/client/js/admin.logs.js +1 -5
- package/dist/dashboard/client/js/admin.maintenance.js +53 -8
- package/dist/dashboard/client/js/admin.messaging.js +1 -4
- package/dist/dashboard/client/js/admin.overview.js +5 -1
- package/dist/dashboard/client/js/admin.sessions.js +1 -1
- package/dist/dashboard/client/js/admin.utils.js +43 -1
- package/dist/dashboard/client/js/mermaid.min.js +813 -537
- package/dist/dashboard/export/DataExporter.js +2 -1
- package/dist/dashboard/server/AdminPanel.d.ts +3 -0
- package/dist/dashboard/server/AdminPanel.js +132 -35
- package/dist/dashboard/server/ApiRoutes.js +40 -9
- package/dist/dashboard/server/DashboardServer.js +1 -1
- package/dist/dashboard/server/FileMetricsStorage.d.ts +19 -0
- package/dist/dashboard/server/FileMetricsStorage.js +52 -5
- package/dist/dashboard/server/HttpTransport.js +6 -0
- package/dist/dashboard/server/InstanceManager.js +7 -2
- package/dist/dashboard/server/KnowledgeStore.js +7 -2
- package/dist/dashboard/server/MetricsCollector.d.ts +16 -0
- package/dist/dashboard/server/MetricsCollector.js +113 -17
- package/dist/dashboard/server/legacyDashboardHtml.js +7 -2
- package/dist/dashboard/server/middleware/ensureLoadedMiddleware.d.ts +1 -1
- package/dist/dashboard/server/middleware/ensureLoadedMiddleware.js +8 -3
- package/dist/dashboard/server/routes/admin.feedback.routes.d.ts +15 -0
- package/dist/dashboard/server/routes/admin.feedback.routes.js +188 -0
- package/dist/dashboard/server/routes/admin.routes.js +35 -27
- package/dist/dashboard/server/routes/alerts.routes.js +4 -3
- package/dist/dashboard/server/routes/api.feedback.routes.js +2 -1
- package/dist/dashboard/server/routes/api.usage.routes.js +8 -7
- package/dist/dashboard/server/routes/embeddings.routes.d.ts +2 -1
- package/dist/dashboard/server/routes/embeddings.routes.js +18 -9
- package/dist/dashboard/server/routes/graph.routes.js +10 -13
- package/dist/dashboard/server/routes/index.d.ts +1 -0
- package/dist/dashboard/server/routes/index.js +74 -39
- package/dist/dashboard/server/routes/instances.routes.js +2 -1
- package/dist/dashboard/server/routes/instructions.routes.js +46 -27
- package/dist/dashboard/server/routes/knowledge.routes.js +4 -3
- package/dist/dashboard/server/routes/logs.routes.js +5 -4
- package/dist/dashboard/server/routes/messaging.routes.js +15 -14
- package/dist/dashboard/server/routes/metrics.routes.js +14 -13
- package/dist/dashboard/server/routes/scripts.routes.js +6 -3
- package/dist/dashboard/server/routes/status.routes.js +5 -4
- package/dist/dashboard/server/routes/synthetic.routes.js +3 -2
- package/dist/dashboard/server/routes/usage.routes.js +2 -1
- package/dist/dashboard/server/utils/escapeHtml.d.ts +1 -0
- package/dist/dashboard/server/utils/escapeHtml.js +11 -0
- package/dist/dashboard/server/utils/pathContainment.d.ts +1 -0
- package/dist/dashboard/server/utils/pathContainment.js +15 -0
- package/dist/dashboard/server/wsInit.js +2 -2
- package/dist/lib/mcpStdioLogging.d.ts +165 -0
- package/dist/lib/mcpStdioLogging.js +287 -0
- package/dist/schemas/index.d.ts +37 -2
- package/dist/schemas/index.js +27 -3
- package/dist/server/backgroundServicesStartup.d.ts +7 -1
- package/dist/server/backgroundServicesStartup.js +25 -8
- package/dist/server/certInit.d.ts +97 -0
- package/dist/server/certInit.js +359 -0
- package/dist/server/certInit.types.d.ts +92 -0
- package/dist/server/certInit.types.js +34 -0
- package/dist/server/handshake/fallbackFrames.d.ts +31 -0
- package/dist/server/handshake/fallbackFrames.js +38 -0
- package/dist/server/handshake/initializeDetector.d.ts +31 -0
- package/dist/server/handshake/initializeDetector.js +88 -0
- package/dist/server/handshake/protocol.d.ts +15 -0
- package/dist/server/handshake/protocol.js +37 -0
- package/dist/server/handshake/readyEmitter.d.ts +6 -0
- package/dist/server/handshake/readyEmitter.js +88 -0
- package/dist/server/handshake/safetyFallbacks.d.ts +1 -0
- package/dist/server/handshake/safetyFallbacks.js +134 -0
- package/dist/server/handshake/stdinSniffer.d.ts +1 -0
- package/dist/server/handshake/stdinSniffer.js +260 -0
- package/dist/server/handshake/tracing.d.ts +16 -0
- package/dist/server/handshake/tracing.js +95 -0
- package/dist/server/handshakeManager.d.ts +23 -23
- package/dist/server/handshakeManager.js +36 -466
- package/dist/server/index-server.d.ts +23 -0
- package/dist/server/index-server.js +194 -9
- package/dist/server/mcpReadOnlySurfaces.d.ts +44 -0
- package/dist/server/mcpReadOnlySurfaces.js +297 -0
- package/dist/server/sdkServer.js +69 -7
- package/dist/server/transport.d.ts +5 -6
- package/dist/server/transport.js +46 -64
- package/dist/server/transportFactory.d.ts +3 -9
- package/dist/server/transportFactory.js +18 -380
- package/dist/services/atomicFs.d.ts +3 -0
- package/dist/services/atomicFs.js +171 -13
- package/dist/services/auditLog.d.ts +17 -2
- package/dist/services/auditLog.js +75 -14
- package/dist/services/bootstrapGating.js +1 -1
- package/dist/services/categoryRules.d.ts +10 -0
- package/dist/services/categoryRules.js +17 -0
- package/dist/services/classificationService.js +7 -5
- package/dist/services/embeddingService.d.ts +27 -11
- package/dist/services/embeddingService.js +51 -14
- package/dist/services/feedbackStorage.d.ts +39 -0
- package/dist/services/feedbackStorage.js +88 -0
- package/dist/services/handlers/instructions.add.js +429 -317
- package/dist/services/handlers/instructions.groom.js +128 -31
- package/dist/services/handlers/instructions.import.js +56 -23
- package/dist/services/handlers/instructions.patch.js +43 -32
- package/dist/services/handlers/instructions.query.js +20 -29
- package/dist/services/handlers/instructions.shared.d.ts +54 -0
- package/dist/services/handlers/instructions.shared.js +126 -1
- package/dist/services/handlers.activation.js +83 -81
- package/dist/services/handlers.dashboardConfig.d.ts +2 -2
- package/dist/services/handlers.dashboardConfig.js +1 -2
- package/dist/services/handlers.diagnostics.js +75 -54
- package/dist/services/handlers.feedback.d.ts +4 -11
- package/dist/services/handlers.feedback.js +11 -333
- package/dist/services/handlers.gates.js +69 -37
- package/dist/services/handlers.graph.js +2 -2
- package/dist/services/handlers.help.js +2 -2
- package/dist/services/handlers.instructionSchema.js +4 -2
- package/dist/services/handlers.integrity.js +42 -22
- package/dist/services/handlers.messaging.js +1 -1
- package/dist/services/handlers.metrics.js +51 -6
- package/dist/services/handlers.prompt.js +10 -2
- package/dist/services/handlers.search.js +94 -44
- package/dist/services/handlers.trace.js +1 -1
- package/dist/services/handlers.usage.js +38 -7
- package/dist/services/indexContext.d.ts +21 -1
- package/dist/services/indexContext.js +263 -78
- package/dist/services/indexLoader.d.ts +1 -0
- package/dist/services/indexLoader.js +28 -8
- package/dist/services/instructionRecordValidation.d.ts +39 -0
- package/dist/services/instructionRecordValidation.js +388 -0
- package/dist/services/instructions.dispatcher.js +4 -4
- package/dist/services/loaderSchemaValidator.d.ts +15 -0
- package/dist/services/loaderSchemaValidator.js +69 -0
- package/dist/services/logger.js +11 -2
- package/dist/services/mcpLogBridge.d.ts +49 -0
- package/dist/services/mcpLogBridge.js +83 -0
- package/dist/services/ownershipService.js +18 -8
- package/dist/services/performanceBaseline.js +23 -22
- package/dist/services/promptReviewService.d.ts +3 -1
- package/dist/services/promptReviewService.js +41 -13
- package/dist/services/regexSafety.d.ts +6 -0
- package/dist/services/regexSafety.js +46 -0
- package/dist/services/seedBootstrap.js +1 -1
- package/dist/services/storage/factory.d.ts +14 -1
- package/dist/services/storage/factory.js +61 -1
- package/dist/services/storage/jsonEmbeddingStore.d.ts +15 -0
- package/dist/services/storage/jsonEmbeddingStore.js +83 -0
- package/dist/services/storage/jsonFileStore.d.ts +3 -1
- package/dist/services/storage/jsonFileStore.js +8 -6
- package/dist/services/storage/migrationEngine.d.ts +13 -0
- package/dist/services/storage/migrationEngine.js +31 -0
- package/dist/services/storage/sqliteEmbeddingStore.d.ts +30 -0
- package/dist/services/storage/sqliteEmbeddingStore.js +222 -0
- package/dist/services/storage/sqliteStore.d.ts +3 -1
- package/dist/services/storage/sqliteStore.js +2 -2
- package/dist/services/storage/types.d.ts +48 -1
- package/dist/services/toolRegistry.js +77 -67
- package/dist/services/toolRegistry.zod.js +89 -86
- package/dist/services/tracing.js +5 -4
- package/dist/utils/envUtils.d.ts +4 -0
- package/dist/utils/envUtils.js +7 -0
- package/dist/utils/memoryMonitor.js +11 -10
- package/package.json +12 -4
- package/schemas/instruction.schema.json +38 -1
- package/scripts/copy-dashboard-assets.mjs +1 -1
- package/scripts/dist/README.md +1 -1
- package/scripts/generate-certs.mjs +201 -0
- package/scripts/setup-wizard.mjs +781 -0
- package/server.json +20 -0
- package/dist/externalClientLib.d.ts +0 -1
- package/dist/externalClientLib.js +0 -2
- package/dist/portableClientWrapper.d.ts +0 -1
- package/dist/portableClientWrapper.js +0 -2
- package/dist/services/indexingService.d.ts +0 -1
- package/dist/services/indexingService.js +0 -2
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getInvariantRepairSummary = getInvariantRepairSummary;
|
|
6
7
|
exports.clearUsageRateLimit = clearUsageRateLimit;
|
|
7
8
|
exports.loadUsageSnapshot = loadUsageSnapshot;
|
|
8
9
|
exports.getInstructionsDir = getInstructionsDir;
|
|
@@ -10,15 +11,19 @@ exports.diagnoseInstructionsDir = diagnoseInstructionsDir;
|
|
|
10
11
|
exports.touchIndexVersion = touchIndexVersion;
|
|
11
12
|
exports.markindexDirty = markindexDirty;
|
|
12
13
|
exports.ensureLoaded = ensureLoaded;
|
|
14
|
+
exports.ensureLoadedAsync = ensureLoadedAsync;
|
|
13
15
|
exports.startIndexVersionPoller = startIndexVersionPoller;
|
|
14
16
|
exports.stopIndexVersionPoller = stopIndexVersionPoller;
|
|
15
17
|
exports.invalidate = invalidate;
|
|
16
18
|
exports.getIndexState = getIndexState;
|
|
19
|
+
exports.getIndexStateAsync = getIndexStateAsync;
|
|
17
20
|
exports.getDebugIndexSnapshot = getDebugIndexSnapshot;
|
|
18
21
|
exports.getIndexDiagnostics = getIndexDiagnostics;
|
|
19
22
|
exports.projectGovernance = projectGovernance;
|
|
20
23
|
exports.computeGovernanceHash = computeGovernanceHash;
|
|
24
|
+
exports.isDuplicateInstructionWriteError = isDuplicateInstructionWriteError;
|
|
21
25
|
exports.writeEntry = writeEntry;
|
|
26
|
+
exports.writeEntryAsync = writeEntryAsync;
|
|
22
27
|
exports.removeEntry = removeEntry;
|
|
23
28
|
exports.scheduleUsagePersist = scheduleUsagePersist;
|
|
24
29
|
exports.incrementUsage = incrementUsage;
|
|
@@ -35,6 +40,9 @@ const envUtils_1 = require("../utils/envUtils");
|
|
|
35
40
|
const runtimeConfig_1 = require("../config/runtimeConfig");
|
|
36
41
|
const factory_1 = require("./storage/factory");
|
|
37
42
|
const migrationEngine_1 = require("./storage/migrationEngine");
|
|
43
|
+
const instructionRecordValidation_1 = require("./instructionRecordValidation");
|
|
44
|
+
const loaderSchemaValidator_1 = require("./loaderSchemaValidator");
|
|
45
|
+
const schemaVersion_1 = require("../versioning/schemaVersion");
|
|
38
46
|
let state = null;
|
|
39
47
|
// Simple reliable invalidation: any mutation sets dirty=true; next ensureLoaded() performs full rescan.
|
|
40
48
|
let dirty = false;
|
|
@@ -79,6 +87,19 @@ const firstSeenAuthority = {};
|
|
|
79
87
|
const usageAuthority = {};
|
|
80
88
|
// Authoritative lastUsedAt map for resilience between reload + snapshot overlay timing.
|
|
81
89
|
const lastUsedAuthority = {};
|
|
90
|
+
// ── Invariant repair tracking (#131) ─────────────────────────────
|
|
91
|
+
// Accumulates repair events so they can be surfaced via health checks.
|
|
92
|
+
const invariantRepairLog = [];
|
|
93
|
+
const MAX_REPAIR_LOG = 200;
|
|
94
|
+
function trackInvariantRepair(id, field, source) {
|
|
95
|
+
invariantRepairLog.push({ ts: new Date().toISOString(), id, field, source });
|
|
96
|
+
if (invariantRepairLog.length > MAX_REPAIR_LOG)
|
|
97
|
+
invariantRepairLog.shift();
|
|
98
|
+
}
|
|
99
|
+
/** Returns a summary of invariant repairs for health check visibility. */
|
|
100
|
+
function getInvariantRepairSummary() {
|
|
101
|
+
return { totalRepairs: invariantRepairLog.length, recentRepairs: invariantRepairLog.slice(-20) };
|
|
102
|
+
}
|
|
82
103
|
// Defensive invariant repair: if any code path ever observes an InstructionEntry with a missing
|
|
83
104
|
// firstSeenTs after it was previously established (should not happen, but flake indicates a very
|
|
84
105
|
// rare timing or cross-test interaction), we repair it from ephemeral cache or lastGood snapshot.
|
|
@@ -89,22 +110,30 @@ function restoreFirstSeenInvariant(e) {
|
|
|
89
110
|
if (auth) {
|
|
90
111
|
e.firstSeenTs = auth;
|
|
91
112
|
(0, features_1.incrementCounter)('usage:firstSeenAuthorityRepair');
|
|
113
|
+
trackInvariantRepair(e.id, 'firstSeenTs', 'authority');
|
|
114
|
+
(0, logger_js_1.logWarn)(`[invariant-repair] firstSeenTs restored from authority for ${e.id}`);
|
|
92
115
|
return;
|
|
93
116
|
}
|
|
94
117
|
const ep = ephemeralFirstSeen[e.id];
|
|
95
118
|
if (ep) {
|
|
96
119
|
e.firstSeenTs = ep;
|
|
97
120
|
(0, features_1.incrementCounter)('usage:firstSeenInvariantRepair');
|
|
121
|
+
trackInvariantRepair(e.id, 'firstSeenTs', 'ephemeral');
|
|
122
|
+
(0, logger_js_1.logWarn)(`[invariant-repair] firstSeenTs restored from ephemeral cache for ${e.id}`);
|
|
98
123
|
return;
|
|
99
124
|
}
|
|
100
125
|
const snap = lastGoodUsageSnapshot[e.id];
|
|
101
126
|
if (snap?.firstSeenTs) {
|
|
102
127
|
e.firstSeenTs = snap.firstSeenTs;
|
|
103
128
|
(0, features_1.incrementCounter)('usage:firstSeenInvariantRepair');
|
|
129
|
+
trackInvariantRepair(e.id, 'firstSeenTs', 'snapshot');
|
|
130
|
+
(0, logger_js_1.logWarn)(`[invariant-repair] firstSeenTs restored from snapshot for ${e.id}`);
|
|
104
131
|
}
|
|
105
132
|
// If still missing after all repair sources, track an exhausted repair attempt (extremely rare diagnostic)
|
|
106
133
|
if (!e.firstSeenTs) {
|
|
107
134
|
(0, features_1.incrementCounter)('usage:firstSeenRepairExhausted');
|
|
135
|
+
trackInvariantRepair(e.id, 'firstSeenTs', 'exhausted');
|
|
136
|
+
(0, logger_js_1.logWarn)(`[invariant-repair] firstSeenTs repair exhausted — no source found for ${e.id}`);
|
|
108
137
|
}
|
|
109
138
|
}
|
|
110
139
|
// Usage invariant repair (mirrors firstSeen invariant strategy). Extremely rare reload races in CI produced
|
|
@@ -115,26 +144,33 @@ function restoreFirstSeenInvariant(e) {
|
|
|
115
144
|
function restoreUsageInvariant(e) {
|
|
116
145
|
if (e.usageCount != null)
|
|
117
146
|
return;
|
|
118
|
-
// Prefer authoritative value, then observed, then persisted snapshot, else default 0.
|
|
119
147
|
if (usageAuthority[e.id] != null) {
|
|
120
148
|
e.usageCount = usageAuthority[e.id];
|
|
121
149
|
(0, features_1.incrementCounter)('usage:usageInvariantAuthorityRepair');
|
|
150
|
+
trackInvariantRepair(e.id, 'usageCount', 'authority');
|
|
151
|
+
(0, logger_js_1.logWarn)(`[invariant-repair] usageCount restored from authority for ${e.id} (value=${usageAuthority[e.id]})`);
|
|
122
152
|
return;
|
|
123
153
|
}
|
|
124
154
|
if (observedUsage[e.id] != null) {
|
|
125
155
|
e.usageCount = observedUsage[e.id];
|
|
126
156
|
(0, features_1.incrementCounter)('usage:usageInvariantObservedRepair');
|
|
157
|
+
trackInvariantRepair(e.id, 'usageCount', 'observed');
|
|
158
|
+
(0, logger_js_1.logWarn)(`[invariant-repair] usageCount restored from observed for ${e.id} (value=${observedUsage[e.id]})`);
|
|
127
159
|
return;
|
|
128
160
|
}
|
|
129
161
|
const snap = lastGoodUsageSnapshot[e.id];
|
|
130
162
|
if (snap?.usageCount != null) {
|
|
131
163
|
e.usageCount = snap.usageCount;
|
|
132
164
|
(0, features_1.incrementCounter)('usage:usageInvariantSnapshotRepair');
|
|
165
|
+
trackInvariantRepair(e.id, 'usageCount', 'snapshot');
|
|
166
|
+
(0, logger_js_1.logWarn)(`[invariant-repair] usageCount restored from snapshot for ${e.id} (value=${snap.usageCount})`);
|
|
133
167
|
return;
|
|
134
168
|
}
|
|
135
169
|
// Fall back to 0 – deterministic floor; next increment will advance.
|
|
136
170
|
e.usageCount = 0;
|
|
137
171
|
(0, features_1.incrementCounter)('usage:usageInvariantZeroRepair');
|
|
172
|
+
trackInvariantRepair(e.id, 'usageCount', 'zero-default');
|
|
173
|
+
(0, logger_js_1.logWarn)(`[invariant-repair] usageCount defaulted to 0 for ${e.id} — no repair source found`);
|
|
138
174
|
}
|
|
139
175
|
// Repair missing lastUsedAt for entries with usage.
|
|
140
176
|
function restoreLastUsedInvariant(e) {
|
|
@@ -143,17 +179,23 @@ function restoreLastUsedInvariant(e) {
|
|
|
143
179
|
if (lastUsedAuthority[e.id]) {
|
|
144
180
|
e.lastUsedAt = lastUsedAuthority[e.id];
|
|
145
181
|
(0, features_1.incrementCounter)('usage:lastUsedAuthorityRepair');
|
|
182
|
+
trackInvariantRepair(e.id, 'lastUsedAt', 'authority');
|
|
183
|
+
(0, logger_js_1.logWarn)(`[invariant-repair] lastUsedAt restored from authority for ${e.id}`);
|
|
146
184
|
return;
|
|
147
185
|
}
|
|
148
186
|
const snap = lastGoodUsageSnapshot[e.id];
|
|
149
187
|
if (snap?.lastUsedAt) {
|
|
150
188
|
e.lastUsedAt = snap.lastUsedAt;
|
|
151
189
|
(0, features_1.incrementCounter)('usage:lastUsedSnapshotRepair');
|
|
190
|
+
trackInvariantRepair(e.id, 'lastUsedAt', 'snapshot');
|
|
191
|
+
(0, logger_js_1.logWarn)(`[invariant-repair] lastUsedAt restored from snapshot for ${e.id}`);
|
|
152
192
|
return;
|
|
153
193
|
}
|
|
154
194
|
if ((e.usageCount ?? 0) > 0 && e.firstSeenTs) {
|
|
155
195
|
e.lastUsedAt = e.firstSeenTs;
|
|
156
196
|
(0, features_1.incrementCounter)('usage:lastUsedFirstSeenRepair');
|
|
197
|
+
trackInvariantRepair(e.id, 'lastUsedAt', 'firstSeen-approx');
|
|
198
|
+
(0, logger_js_1.logWarn)(`[invariant-repair] lastUsedAt approximated from firstSeenTs for ${e.id}`);
|
|
157
199
|
}
|
|
158
200
|
}
|
|
159
201
|
// Rate limiting for usage increments (Phase 1 requirement)
|
|
@@ -214,8 +256,9 @@ function loadUsageSnapshot() {
|
|
|
214
256
|
}
|
|
215
257
|
break; // file not present – exit attempts
|
|
216
258
|
}
|
|
217
|
-
catch {
|
|
218
|
-
//
|
|
259
|
+
catch (err) {
|
|
260
|
+
// Log parse/read error and retry (tight loop – extremely rare path)
|
|
261
|
+
(0, logger_js_1.logWarn)(`[invariant-repair] loadUsageSnapshot attempt ${attempt} failed: ${err.message || String(err)}`);
|
|
219
262
|
}
|
|
220
263
|
}
|
|
221
264
|
// Fallback to last good snapshot (prevents loss of firstSeenTs on rare parse race)
|
|
@@ -243,7 +286,7 @@ function flushUsageSnapshot() {
|
|
|
243
286
|
for (const e of state.list) {
|
|
244
287
|
const authoritative = e.firstSeenTs || firstSeenAuthority[e.id];
|
|
245
288
|
if (authoritative && !firstSeenAuthority[e.id])
|
|
246
|
-
firstSeenAuthority[e.id] = authoritative;
|
|
289
|
+
firstSeenAuthority[e.id] = authoritative; // lgtm[js/remote-property-injection] — id is regex-validated by instruction schema (^[a-z0-9](?:[a-z0-9-_]{0,118}[a-z0-9])?$) before reaching index
|
|
247
290
|
if (e.usageCount || e.lastUsedAt || authoritative) {
|
|
248
291
|
const rec = { usageCount: e.usageCount, firstSeenTs: authoritative, lastUsedAt: e.lastUsedAt };
|
|
249
292
|
// Merge signal/comment/action from in-memory cache (last-write-wins from incrementUsage calls)
|
|
@@ -256,18 +299,18 @@ function flushUsageSnapshot() {
|
|
|
256
299
|
if (cached.lastComment)
|
|
257
300
|
rec.lastComment = cached.lastComment;
|
|
258
301
|
}
|
|
259
|
-
obj[e.id] = rec;
|
|
302
|
+
obj[e.id] = rec; // lgtm[js/remote-property-injection] — id is schema-validated before reaching index
|
|
260
303
|
}
|
|
261
304
|
}
|
|
262
305
|
// Atomic write: write to temp then rename to avoid readers seeing partial JSON
|
|
263
306
|
const snapPath = getUsageSnapshotPath();
|
|
264
307
|
const tmp = snapPath + '.tmp';
|
|
265
|
-
fs_1.default.writeFileSync(tmp, JSON.stringify(obj, null, 2));
|
|
308
|
+
fs_1.default.writeFileSync(tmp, JSON.stringify(obj, null, 2)); // lgtm[js/http-to-file-access] — snapPath is config-controlled usage snapshot path
|
|
266
309
|
try {
|
|
267
310
|
fs_1.default.renameSync(tmp, snapPath);
|
|
268
311
|
}
|
|
269
312
|
catch { /* fallback to direct write if rename fails */
|
|
270
|
-
fs_1.default.writeFileSync(snapPath, JSON.stringify(obj, null, 2));
|
|
313
|
+
fs_1.default.writeFileSync(snapPath, JSON.stringify(obj, null, 2)); /* lgtm[js/http-to-file-access] — snapPath is config-controlled usage snapshot path */
|
|
271
314
|
}
|
|
272
315
|
lastGoodUsageSnapshot = obj; // update cache
|
|
273
316
|
}
|
|
@@ -278,7 +321,7 @@ function flushUsageSnapshot() {
|
|
|
278
321
|
// The guard ensures cleanup runs exactly once even if multiple signals race.
|
|
279
322
|
try {
|
|
280
323
|
// Import directly from shutdownGuard module (no circular dependency)
|
|
281
|
-
// eslint-disable-next-line @typescript-eslint/no-
|
|
324
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
282
325
|
const { createShutdownGuard: _createShutdownGuard } = require('../server/shutdownGuard');
|
|
283
326
|
// Get or create a process-wide singleton via a global symbol
|
|
284
327
|
const key = Symbol.for('mcp-shutdown-guard');
|
|
@@ -336,6 +379,7 @@ function getInstructionsDir() {
|
|
|
336
379
|
}
|
|
337
380
|
// Centralized tracing utilities
|
|
338
381
|
const tracing_1 = require("./tracing");
|
|
382
|
+
const logger_js_1 = require("./logger.js");
|
|
339
383
|
// Throttled file trace emission (avoid per-get amplification). We emit per-file decisions only
|
|
340
384
|
// on true reloads AND if file signature changed OR time since last emission > threshold.
|
|
341
385
|
// (legacy file-level trace removed in simplified loader)
|
|
@@ -394,6 +438,58 @@ function readVersionToken() { try {
|
|
|
394
438
|
}
|
|
395
439
|
catch { /* ignore */ } return ''; }
|
|
396
440
|
function markindexDirty() { dirty = true; }
|
|
441
|
+
function syncTouchedVersionIntoState() {
|
|
442
|
+
try {
|
|
443
|
+
touchIndexVersion();
|
|
444
|
+
const vfMTime = (function () { try {
|
|
445
|
+
const vf = path_1.default.join(getInstructionsDir(), '.index-version');
|
|
446
|
+
if (fs_1.default.existsSync(vf)) {
|
|
447
|
+
return fs_1.default.statSync(vf).mtimeMs || 0;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
catch { /* ignore */ } return 0; })();
|
|
451
|
+
const vfToken = (function () { try {
|
|
452
|
+
const vf = path_1.default.join(getInstructionsDir(), '.index-version');
|
|
453
|
+
if (fs_1.default.existsSync(vf)) {
|
|
454
|
+
return fs_1.default.readFileSync(vf, 'utf8').trim();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
catch { /* ignore */ } return ''; })();
|
|
458
|
+
if (state) {
|
|
459
|
+
if (vfMTime && state.versionMTime !== vfMTime) {
|
|
460
|
+
state.versionMTime = vfMTime;
|
|
461
|
+
}
|
|
462
|
+
if (vfToken && state.versionToken !== vfToken) {
|
|
463
|
+
state.versionToken = vfToken;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
catch { /* ignore */ }
|
|
468
|
+
}
|
|
469
|
+
function materializeWrittenEntry(record) {
|
|
470
|
+
if (state) {
|
|
471
|
+
const existing = state.byId.get(record.id);
|
|
472
|
+
if (existing) {
|
|
473
|
+
Object.assign(existing, record);
|
|
474
|
+
try {
|
|
475
|
+
(0, features_1.incrementCounter)('index:inMemoryUpdate');
|
|
476
|
+
}
|
|
477
|
+
catch { /* ignore */ }
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
state.list.push(record);
|
|
481
|
+
state.byId.set(record.id, record);
|
|
482
|
+
try {
|
|
483
|
+
(0, features_1.incrementCounter)('index:inMemoryMaterialize');
|
|
484
|
+
}
|
|
485
|
+
catch { /* ignore */ }
|
|
486
|
+
}
|
|
487
|
+
syncTouchedVersionIntoState();
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
markindexDirty();
|
|
491
|
+
syncTouchedVersionIntoState();
|
|
492
|
+
}
|
|
397
493
|
function ensureLoaded() {
|
|
398
494
|
const baseDir = getInstructionsDir();
|
|
399
495
|
// Always reload if no state or dirty or version file changed.
|
|
@@ -416,12 +512,12 @@ function ensureLoaded() {
|
|
|
416
512
|
const dbPath = (0, runtimeConfig_1.getRuntimeConfig)().storage?.sqlitePath ?? path_1.default.join(process.cwd(), 'data', 'index.db');
|
|
417
513
|
const mr = (0, migrationEngine_1.migrateJsonToSqlite)(baseDir, dbPath);
|
|
418
514
|
if (mr.migrated > 0) {
|
|
419
|
-
|
|
515
|
+
(0, logger_js_1.logInfo)(`[storage] Auto-migrated ${mr.migrated} instruction(s) from JSON → SQLite`);
|
|
420
516
|
result = store.load();
|
|
421
517
|
}
|
|
422
518
|
}
|
|
423
519
|
catch (err) {
|
|
424
|
-
|
|
520
|
+
(0, logger_js_1.logWarn)('[storage] Auto-migration failed:', err);
|
|
425
521
|
}
|
|
426
522
|
}
|
|
427
523
|
}
|
|
@@ -445,7 +541,54 @@ function ensureLoaded() {
|
|
|
445
541
|
e.firstSeenTs = rec.firstSeenTs;
|
|
446
542
|
if (!firstSeenAuthority[e.id])
|
|
447
543
|
firstSeenAuthority[e.id] = rec.firstSeenTs;
|
|
448
|
-
}
|
|
544
|
+
} // lgtm[js/remote-property-injection] — id is schema-validated before reaching index
|
|
545
|
+
if (!e.lastUsedAt && rec.lastUsedAt)
|
|
546
|
+
e.lastUsedAt = rec.lastUsedAt;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
catch { /* ignore */ }
|
|
552
|
+
if ((0, tracing_1.traceEnabled)(1)) {
|
|
553
|
+
try {
|
|
554
|
+
(0, tracing_1.emitTrace)('[trace:ensureLoaded:simple-reload]', { dir: baseDir, count: state.list.length });
|
|
555
|
+
}
|
|
556
|
+
catch { /* ignore */ }
|
|
557
|
+
}
|
|
558
|
+
return state;
|
|
559
|
+
}
|
|
560
|
+
async function ensureLoadedAsync() {
|
|
561
|
+
const baseDir = getInstructionsDir();
|
|
562
|
+
const currentVersionMTime = readVersionMTime();
|
|
563
|
+
const currentVersionToken = readVersionToken();
|
|
564
|
+
if (state && !dirty) {
|
|
565
|
+
if (currentVersionMTime && currentVersionMTime === state.versionMTime && currentVersionToken === state.versionToken) {
|
|
566
|
+
return state;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
const backend = (0, runtimeConfig_1.getRuntimeConfig)().storage?.backend ?? 'json';
|
|
570
|
+
if (backend === 'sqlite') {
|
|
571
|
+
return ensureLoaded();
|
|
572
|
+
}
|
|
573
|
+
const result = await new indexLoader_1.IndexLoader(baseDir).loadAsync();
|
|
574
|
+
const byId = new Map();
|
|
575
|
+
result.entries.forEach(e => byId.set(e.id, e));
|
|
576
|
+
const deduplicatedList = Array.from(byId.values());
|
|
577
|
+
state = { loadedAt: new Date().toISOString(), hash: result.hash, byId, list: deduplicatedList, fileCount: deduplicatedList.length, versionMTime: currentVersionMTime, versionToken: currentVersionToken, loadErrors: result.errors, loadDebug: result.debug, loadSummary: result.summary };
|
|
578
|
+
dirty = false;
|
|
579
|
+
try {
|
|
580
|
+
const snap = loadUsageSnapshot();
|
|
581
|
+
if (snap) {
|
|
582
|
+
for (const e of state.list) {
|
|
583
|
+
const rec = snap[e.id];
|
|
584
|
+
if (rec) {
|
|
585
|
+
if (e.usageCount == null && rec.usageCount != null)
|
|
586
|
+
e.usageCount = rec.usageCount;
|
|
587
|
+
if (!e.firstSeenTs && rec.firstSeenTs) {
|
|
588
|
+
e.firstSeenTs = rec.firstSeenTs;
|
|
589
|
+
if (!firstSeenAuthority[e.id])
|
|
590
|
+
firstSeenAuthority[e.id] = rec.firstSeenTs;
|
|
591
|
+
} // lgtm[js/remote-property-injection] — id is schema-validated before reaching index
|
|
449
592
|
if (!e.lastUsedAt && rec.lastUsedAt)
|
|
450
593
|
e.lastUsedAt = rec.lastUsedAt;
|
|
451
594
|
}
|
|
@@ -561,6 +704,21 @@ function getIndexState() {
|
|
|
561
704
|
}
|
|
562
705
|
return st;
|
|
563
706
|
}
|
|
707
|
+
async function getIndexStateAsync() {
|
|
708
|
+
const st = await ensureLoadedAsync();
|
|
709
|
+
for (const e of st.list) {
|
|
710
|
+
if (!e.firstSeenTs) {
|
|
711
|
+
restoreFirstSeenInvariant(e);
|
|
712
|
+
}
|
|
713
|
+
if (e.usageCount == null) {
|
|
714
|
+
restoreUsageInvariant(e);
|
|
715
|
+
}
|
|
716
|
+
if (e.lastUsedAt == null) {
|
|
717
|
+
restoreLastUsedInvariant(e);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return st;
|
|
721
|
+
}
|
|
564
722
|
// Lightweight debug snapshot WITHOUT forcing a reload (observes current in-memory view vs disk)
|
|
565
723
|
function getDebugIndexSnapshot() {
|
|
566
724
|
const dir = getInstructionsDir();
|
|
@@ -658,10 +816,19 @@ function computeGovernanceHash(entries) {
|
|
|
658
816
|
return h.digest('hex');
|
|
659
817
|
}
|
|
660
818
|
// Mutation helpers (import/add/remove/groom share)
|
|
661
|
-
function
|
|
819
|
+
function isDuplicateInstructionWriteError(error) {
|
|
820
|
+
const code = error?.code;
|
|
821
|
+
if (code === 'EEXIST')
|
|
822
|
+
return true;
|
|
823
|
+
if (!(error instanceof Error))
|
|
824
|
+
return false;
|
|
825
|
+
const message = error.message.toLowerCase();
|
|
826
|
+
return message.includes('unique constraint failed') || message.includes('duplicate key');
|
|
827
|
+
}
|
|
828
|
+
function writeEntry(entry, opts) {
|
|
662
829
|
const file = path_1.default.join(getInstructionsDir(), `${entry.id}.json`);
|
|
663
830
|
const classifier = new classificationService_1.ClassificationService();
|
|
664
|
-
|
|
831
|
+
let record = classifier.normalize(entry);
|
|
665
832
|
if (record.owner === 'unowned') {
|
|
666
833
|
const auto = (0, ownershipService_1.resolveOwner)(record.id);
|
|
667
834
|
if (auto) {
|
|
@@ -669,77 +836,96 @@ function writeEntry(entry) {
|
|
|
669
836
|
record.updatedAt = new Date().toISOString();
|
|
670
837
|
}
|
|
671
838
|
}
|
|
839
|
+
record = (0, instructionRecordValidation_1.assertValidInstructionRecord)(record);
|
|
840
|
+
// Run the SAME migration the loader runs on read so the write path is
|
|
841
|
+
// symmetric with the read path. This brings legacy in-memory records
|
|
842
|
+
// (carrying old schemaVersion or missing v3+ defaults) up to current
|
|
843
|
+
// schema BEFORE validateForDisk gates them against the loader schema.
|
|
844
|
+
// Without this, callers passing legacy records would be silently rejected
|
|
845
|
+
// by the loader-symmetric validator even though the loader itself would
|
|
846
|
+
// have migrated them transparently.
|
|
847
|
+
(0, schemaVersion_1.migrateInstructionRecord)(record);
|
|
848
|
+
// Validate against the SAME JSON schema the loader uses at reload time.
|
|
849
|
+
// This prevents schema drift from silently dropping entries on reload.
|
|
850
|
+
const diskCheck = (0, loaderSchemaValidator_1.validateForDisk)(record);
|
|
851
|
+
if (!diskCheck.valid) {
|
|
852
|
+
const err = new Error(`Pre-write loader-schema validation failed for '${entry.id}': ${diskCheck.errors?.join('; ')}`);
|
|
853
|
+
err.validationErrors = diskCheck.errors;
|
|
854
|
+
err.isInstructionValidation = true;
|
|
855
|
+
throw err;
|
|
856
|
+
}
|
|
672
857
|
const store = getStoreForDir(getInstructionsDir());
|
|
673
858
|
if (store) {
|
|
674
|
-
store.write(record);
|
|
859
|
+
store.write(record, opts);
|
|
860
|
+
}
|
|
861
|
+
else if (opts?.createOnly) {
|
|
862
|
+
(0, atomicFs_1.atomicCreateJson)(file, record);
|
|
675
863
|
}
|
|
676
864
|
else {
|
|
677
865
|
(0, atomicFs_1.atomicWriteJson)(file, record);
|
|
678
866
|
}
|
|
679
|
-
//
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
// 2. Touch the version file so other processes/pollers observe the change.
|
|
687
|
-
// 3. Only mark dirty if no state is currently loaded (so first subsequent
|
|
688
|
-
// access triggers a load). Otherwise we keep the current state hot.
|
|
689
|
-
if (state) {
|
|
690
|
-
const existing = state.byId.get(record.id);
|
|
691
|
-
if (existing) {
|
|
692
|
-
// Update in-place so references (including any cached projections) see new fields.
|
|
693
|
-
Object.assign(existing, record);
|
|
694
|
-
try {
|
|
695
|
-
(0, features_1.incrementCounter)('index:inMemoryUpdate');
|
|
867
|
+
// Post-write read-back: verify the file on disk passes the loader schema
|
|
868
|
+
if (!store) {
|
|
869
|
+
try {
|
|
870
|
+
const diskRaw = JSON.parse(fs_1.default.readFileSync(file, 'utf8'));
|
|
871
|
+
const postCheck = (0, loaderSchemaValidator_1.validateForDisk)(diskRaw);
|
|
872
|
+
if (!postCheck.valid) {
|
|
873
|
+
(0, logger_js_1.logWarn)(`[writeEntry] Post-write validation FAILED for '${entry.id}': ${postCheck.errors?.join('; ')}`);
|
|
696
874
|
}
|
|
697
|
-
catch { /* ignore */ }
|
|
698
875
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
state.byId.set(record.id, record);
|
|
702
|
-
try {
|
|
703
|
-
(0, features_1.incrementCounter)('index:inMemoryMaterialize');
|
|
704
|
-
}
|
|
705
|
-
catch { /* ignore */ }
|
|
876
|
+
catch (readErr) {
|
|
877
|
+
(0, logger_js_1.logWarn)(`[writeEntry] Post-write read-back failed for '${entry.id}': ${readErr.message}`);
|
|
706
878
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
const vfToken = (function () { try {
|
|
720
|
-
const vf = path_1.default.join(getInstructionsDir(), '.index-version');
|
|
721
|
-
if (fs_1.default.existsSync(vf)) {
|
|
722
|
-
return fs_1.default.readFileSync(vf, 'utf8').trim();
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
catch { /* ignore */ } return ''; })();
|
|
726
|
-
if (vfMTime && state.versionMTime !== vfMTime) {
|
|
727
|
-
state.versionMTime = vfMTime;
|
|
728
|
-
}
|
|
729
|
-
if (vfToken && state.versionToken !== vfToken) {
|
|
730
|
-
state.versionToken = vfToken;
|
|
731
|
-
}
|
|
879
|
+
}
|
|
880
|
+
materializeWrittenEntry(record);
|
|
881
|
+
}
|
|
882
|
+
async function writeEntryAsync(entry, opts) {
|
|
883
|
+
const file = path_1.default.join(getInstructionsDir(), `${entry.id}.json`);
|
|
884
|
+
const classifier = new classificationService_1.ClassificationService();
|
|
885
|
+
let record = classifier.normalize(entry);
|
|
886
|
+
if (record.owner === 'unowned') {
|
|
887
|
+
const auto = (0, ownershipService_1.resolveOwner)(record.id);
|
|
888
|
+
if (auto) {
|
|
889
|
+
record.owner = auto;
|
|
890
|
+
record.updatedAt = new Date().toISOString();
|
|
732
891
|
}
|
|
733
|
-
|
|
892
|
+
}
|
|
893
|
+
record = (0, instructionRecordValidation_1.assertValidInstructionRecord)(record);
|
|
894
|
+
// Mirror the loader's migration step before validating against the loader
|
|
895
|
+
// schema. See writeEntry for full rationale.
|
|
896
|
+
(0, schemaVersion_1.migrateInstructionRecord)(record);
|
|
897
|
+
// Validate against the SAME JSON schema the loader uses at reload time.
|
|
898
|
+
const diskCheck = (0, loaderSchemaValidator_1.validateForDisk)(record);
|
|
899
|
+
if (!diskCheck.valid) {
|
|
900
|
+
const err = new Error(`Pre-write loader-schema validation failed for '${entry.id}': ${diskCheck.errors?.join('; ')}`);
|
|
901
|
+
err.validationErrors = diskCheck.errors;
|
|
902
|
+
err.isInstructionValidation = true;
|
|
903
|
+
throw err;
|
|
904
|
+
}
|
|
905
|
+
const store = getStoreForDir(getInstructionsDir());
|
|
906
|
+
if (store) {
|
|
907
|
+
store.write(record, opts);
|
|
908
|
+
}
|
|
909
|
+
else if (opts?.createOnly) {
|
|
910
|
+
await (0, atomicFs_1.atomicCreateJsonAsync)(file, record);
|
|
734
911
|
}
|
|
735
912
|
else {
|
|
736
|
-
|
|
737
|
-
|
|
913
|
+
await (0, atomicFs_1.atomicWriteJsonAsync)(file, record);
|
|
914
|
+
}
|
|
915
|
+
// Post-write read-back: verify the file on disk passes the loader schema
|
|
916
|
+
if (!store) {
|
|
738
917
|
try {
|
|
739
|
-
|
|
918
|
+
const diskRaw = JSON.parse(fs_1.default.readFileSync(file, 'utf8'));
|
|
919
|
+
const postCheck = (0, loaderSchemaValidator_1.validateForDisk)(diskRaw);
|
|
920
|
+
if (!postCheck.valid) {
|
|
921
|
+
(0, logger_js_1.logWarn)(`[writeEntryAsync] Post-write validation FAILED for '${entry.id}': ${postCheck.errors?.join('; ')}`);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
catch (readErr) {
|
|
925
|
+
(0, logger_js_1.logWarn)(`[writeEntryAsync] Post-write read-back failed for '${entry.id}': ${readErr.message}`);
|
|
740
926
|
}
|
|
741
|
-
catch { /* ignore */ }
|
|
742
927
|
}
|
|
928
|
+
materializeWrittenEntry(record);
|
|
743
929
|
}
|
|
744
930
|
function removeEntry(id) {
|
|
745
931
|
const store = getStoreForDir(getInstructionsDir());
|
|
@@ -866,12 +1052,12 @@ function incrementUsage(id, opts) {
|
|
|
866
1052
|
// Atomically establish firstSeenTs if missing (avoid any window where undefined persists after increment)
|
|
867
1053
|
if (!e.firstSeenTs) {
|
|
868
1054
|
e.firstSeenTs = nowIso;
|
|
869
|
-
ephemeralFirstSeen[e.id] = e.firstSeenTs; // track immediately for reload resilience
|
|
1055
|
+
ephemeralFirstSeen[e.id] = e.firstSeenTs; // track immediately for reload resilience // lgtm[js/remote-property-injection] — id is schema-validated before reaching index
|
|
870
1056
|
firstSeenAuthority[e.id] = e.firstSeenTs;
|
|
871
|
-
(0, features_1.incrementCounter)('usage:firstSeenAuthoritySet');
|
|
1057
|
+
(0, features_1.incrementCounter)('usage:firstSeenAuthoritySet'); // lgtm[js/remote-property-injection] — id is schema-validated before reaching index
|
|
872
1058
|
}
|
|
873
1059
|
e.lastUsedAt = nowIso; // always advance lastUsedAt on any increment
|
|
874
|
-
lastUsedAuthority[e.id] = e.lastUsedAt;
|
|
1060
|
+
lastUsedAuthority[e.id] = e.lastUsedAt; // lgtm[js/remote-property-injection] — id is schema-validated before reaching index
|
|
875
1061
|
// For the first usage we force a synchronous flush to guarantee persistence of firstSeenTs quickly;
|
|
876
1062
|
// subsequent usages can rely on the debounce timer to coalesce writes.
|
|
877
1063
|
if (e.usageCount <= 2) {
|
|
@@ -892,8 +1078,7 @@ function incrementUsage(id, opts) {
|
|
|
892
1078
|
// Allow tests (or advanced operators) to disable the protective clamp logic for deterministic expectations.
|
|
893
1079
|
// Setting INDEX_SERVER_DISABLE_USAGE_CLAMP=1 will let the anomalous >1 initial count pass through for diagnostic visibility.
|
|
894
1080
|
if (!(0, runtimeConfig_1.getRuntimeConfig)().index.disableUsageClamp) {
|
|
895
|
-
|
|
896
|
-
console.error('[incrementUsage] anomalous initial usageCount', e.usageCount, 'id', id);
|
|
1081
|
+
(0, logger_js_1.logError)('[incrementUsage] anomalous initial usageCount', { usageCount: e.usageCount, id });
|
|
897
1082
|
// Clamp to 1 to enforce deterministic semantics for first observed increment. We intentionally
|
|
898
1083
|
// retain lastUsedAt/firstSeenTs. This guards rare race producing flaky test expectations while
|
|
899
1084
|
// preserving forward progress for subsequent increments (next call will yield 2).
|
|
@@ -969,13 +1154,13 @@ function __testResetUsageState() {
|
|
|
969
1154
|
usageRateLimiter.clear();
|
|
970
1155
|
lastGoodUsageSnapshot = {};
|
|
971
1156
|
for (const k of Object.keys(ephemeralFirstSeen))
|
|
972
|
-
delete ephemeralFirstSeen[k];
|
|
1157
|
+
delete ephemeralFirstSeen[k]; // lgtm[js/remote-property-injection] — k is own-key from internal object reset (test helper)
|
|
973
1158
|
for (const k of Object.keys(firstSeenAuthority))
|
|
974
|
-
delete firstSeenAuthority[k];
|
|
1159
|
+
delete firstSeenAuthority[k]; // lgtm[js/remote-property-injection] — k is own-key from internal object reset (test helper)
|
|
975
1160
|
for (const k of Object.keys(usageAuthority))
|
|
976
|
-
delete usageAuthority[k];
|
|
1161
|
+
delete usageAuthority[k]; // lgtm[js/remote-property-injection] — k is own-key from internal object reset (test helper)
|
|
977
1162
|
for (const k of Object.keys(lastUsedAuthority))
|
|
978
|
-
delete lastUsedAuthority[k];
|
|
1163
|
+
delete lastUsedAuthority[k]; // lgtm[js/remote-property-injection] — k is own-key from internal object reset (test helper)
|
|
979
1164
|
if (state) {
|
|
980
1165
|
for (const e of state.list) {
|
|
981
1166
|
// Reset optional usage-related fields; preserve object identity.
|
|
@@ -19,6 +19,16 @@ const instruction_schema_json_1 = __importDefault(require("../../schemas/instruc
|
|
|
19
19
|
const tracing_1 = require("./tracing");
|
|
20
20
|
const runtimeConfig_1 = require("../config/runtimeConfig");
|
|
21
21
|
const autoSplit_1 = require("./autoSplit");
|
|
22
|
+
const logger_js_1 = require("./logger.js");
|
|
23
|
+
function sleep(ms) {
|
|
24
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
25
|
+
}
|
|
26
|
+
function getRetryBackoffMs(baseBackoff, attempt) {
|
|
27
|
+
return baseBackoff * Math.pow(2, attempt - 1) + Math.floor(Math.random() * baseBackoff);
|
|
28
|
+
}
|
|
29
|
+
function isRetryableLoadError(error) {
|
|
30
|
+
return /empty file transient|unexpected end of json input|eprem|ebusy|eacces|enoent/i.test(error);
|
|
31
|
+
}
|
|
22
32
|
class IndexLoader {
|
|
23
33
|
baseDir;
|
|
24
34
|
classifier;
|
|
@@ -32,9 +42,8 @@ class IndexLoader {
|
|
|
32
42
|
* to momentary locks while another process is atomically renaming/writing.
|
|
33
43
|
*/
|
|
34
44
|
readJsonWithRetry(file) {
|
|
35
|
-
const { attempts
|
|
45
|
+
const { attempts } = (0, runtimeConfig_1.getRuntimeConfig)().index.readRetries;
|
|
36
46
|
const maxAttempts = Math.max(1, attempts);
|
|
37
|
-
const baseBackoff = Math.max(1, backoffMs);
|
|
38
47
|
let lastErr = null;
|
|
39
48
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
40
49
|
try {
|
|
@@ -58,15 +67,26 @@ class IndexLoader {
|
|
|
58
67
|
break;
|
|
59
68
|
}
|
|
60
69
|
lastErr = err;
|
|
61
|
-
const sleep = baseBackoff * Math.pow(2, attempt - 1) + Math.floor(Math.random() * baseBackoff);
|
|
62
|
-
const start = Date.now();
|
|
63
|
-
while (Date.now() - start < sleep) { /* spin tiny backoff (< few ms) */ }
|
|
64
70
|
}
|
|
65
71
|
}
|
|
66
72
|
if (lastErr)
|
|
67
73
|
throw lastErr instanceof Error ? lastErr : new Error('readJsonWithRetry failed');
|
|
68
74
|
return {}; // unreachable but satisfies typing
|
|
69
75
|
}
|
|
76
|
+
async loadAsync() {
|
|
77
|
+
const { attempts, backoffMs } = (0, runtimeConfig_1.getRuntimeConfig)().index.readRetries;
|
|
78
|
+
const maxAttempts = Math.max(1, attempts);
|
|
79
|
+
const baseBackoff = Math.max(1, backoffMs);
|
|
80
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
81
|
+
const result = this.load();
|
|
82
|
+
const retryable = result.errors.length > 0 && result.errors.every(error => isRetryableLoadError(error.error));
|
|
83
|
+
if (!retryable || attempt === maxAttempts) {
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
await sleep(getRetryBackoffMs(baseBackoff, attempt));
|
|
87
|
+
}
|
|
88
|
+
return this.load();
|
|
89
|
+
}
|
|
70
90
|
load() {
|
|
71
91
|
const runtimeConfig = (0, runtimeConfig_1.getRuntimeConfig)();
|
|
72
92
|
const IndexConfig = runtimeConfig.index;
|
|
@@ -126,7 +146,7 @@ class IndexLoader {
|
|
|
126
146
|
}
|
|
127
147
|
catch { /* ignore meta-schema registration issues */ }
|
|
128
148
|
// Patch schema body maxLength from config before compiling (allows INDEX_SERVER_BODY_WARN_LENGTH override)
|
|
129
|
-
const bodyMaxLen = IndexConfig.bodyWarnLength ||
|
|
149
|
+
const bodyMaxLen = IndexConfig.bodyWarnLength || 50000;
|
|
130
150
|
const autoSplit = IndexConfig.autoSplitOversized === true;
|
|
131
151
|
const schemaCopy = JSON.parse(JSON.stringify(instruction_schema_json_1.default));
|
|
132
152
|
try {
|
|
@@ -672,7 +692,7 @@ class IndexLoader {
|
|
|
672
692
|
trace.push({ file: f, accepted: false, reason });
|
|
673
693
|
// Log schema rejections at info level for operational visibility
|
|
674
694
|
try {
|
|
675
|
-
|
|
695
|
+
(0, logger_js_1.logError)(`[index:skip] ${f}: ${reason}`);
|
|
676
696
|
}
|
|
677
697
|
catch { /* ignore */ }
|
|
678
698
|
if ((0, tracing_1.traceEnabled)(1)) {
|
|
@@ -698,7 +718,7 @@ class IndexLoader {
|
|
|
698
718
|
if (trace)
|
|
699
719
|
trace.push({ file: f, accepted: false, reason });
|
|
700
720
|
try {
|
|
701
|
-
|
|
721
|
+
(0, logger_js_1.logError)(`[index:skip] ${f}: classification: ${reason}`);
|
|
702
722
|
}
|
|
703
723
|
catch { /* ignore */ }
|
|
704
724
|
if ((0, tracing_1.traceEnabled)(1)) {
|