@jagilber-org/index-server 1.19.1
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 +1218 -0
- package/CODE_OF_CONDUCT.md +49 -0
- package/CONTRIBUTING.md +75 -0
- package/LICENSE +21 -0
- package/README.md +523 -0
- package/SECURITY.md +50 -0
- package/dist/config/configUtils.d.ts +11 -0
- package/dist/config/configUtils.js +87 -0
- package/dist/config/dashboardConfig.d.ts +45 -0
- package/dist/config/dashboardConfig.js +63 -0
- package/dist/config/defaultValues.d.ts +61 -0
- package/dist/config/defaultValues.js +70 -0
- package/dist/config/dirConstants.d.ts +17 -0
- package/dist/config/dirConstants.js +28 -0
- package/dist/config/featureConfig.d.ts +61 -0
- package/dist/config/featureConfig.js +121 -0
- package/dist/config/runtimeConfig.d.ts +145 -0
- package/dist/config/runtimeConfig.js +334 -0
- package/dist/config/serverConfig.d.ts +90 -0
- package/dist/config/serverConfig.js +164 -0
- package/dist/dashboard/analytics/AnalyticsEngine.d.ts +142 -0
- package/dist/dashboard/analytics/AnalyticsEngine.js +373 -0
- package/dist/dashboard/analytics/BusinessIntelligence.d.ts +187 -0
- package/dist/dashboard/analytics/BusinessIntelligence.js +594 -0
- package/dist/dashboard/client/admin.html +2150 -0
- package/dist/dashboard/client/chunks/mermaid-layout-elk.esm.min/chunk-SP2CHFBE.mjs +1 -0
- package/dist/dashboard/client/chunks/mermaid-layout-elk.esm.min/render-T6MDALS3.mjs +27 -0
- package/dist/dashboard/client/css/admin.css +1466 -0
- package/dist/dashboard/client/js/admin.boot.js +359 -0
- package/dist/dashboard/client/js/admin.config.js +196 -0
- package/dist/dashboard/client/js/admin.embeddings.js +425 -0
- package/dist/dashboard/client/js/admin.graph.js +583 -0
- package/dist/dashboard/client/js/admin.instances.js +120 -0
- package/dist/dashboard/client/js/admin.instructions.js +552 -0
- package/dist/dashboard/client/js/admin.logs.js +113 -0
- package/dist/dashboard/client/js/admin.maintenance.js +354 -0
- package/dist/dashboard/client/js/admin.messaging.js +635 -0
- package/dist/dashboard/client/js/admin.monitor.js +181 -0
- package/dist/dashboard/client/js/admin.overview.js +221 -0
- package/dist/dashboard/client/js/admin.performance.js +61 -0
- package/dist/dashboard/client/js/admin.sessions.js +293 -0
- package/dist/dashboard/client/js/admin.sqlite.js +366 -0
- package/dist/dashboard/client/js/admin.utils.js +49 -0
- package/dist/dashboard/client/js/chart.umd.js +14 -0
- package/dist/dashboard/client/js/elk.bundled.js +6696 -0
- package/dist/dashboard/client/js/marked.umd.js +74 -0
- package/dist/dashboard/client/js/mermaid.min.js +3022 -0
- package/dist/dashboard/client/mermaid-layout-elk.esm.min.mjs +1 -0
- package/dist/dashboard/export/DataExporter.d.ts +169 -0
- package/dist/dashboard/export/DataExporter.js +737 -0
- package/dist/dashboard/export/exporters/csvExporter.d.ts +11 -0
- package/dist/dashboard/export/exporters/csvExporter.js +46 -0
- package/dist/dashboard/export/exporters/exportTypes.d.ts +89 -0
- package/dist/dashboard/export/exporters/exportTypes.js +5 -0
- package/dist/dashboard/export/exporters/jsonExporter.d.ts +7 -0
- package/dist/dashboard/export/exporters/jsonExporter.js +22 -0
- package/dist/dashboard/export/exporters/xmlExporter.d.ts +17 -0
- package/dist/dashboard/export/exporters/xmlExporter.js +175 -0
- package/dist/dashboard/integration/APIIntegration.d.ts +41 -0
- package/dist/dashboard/integration/APIIntegration.js +95 -0
- package/dist/dashboard/security/SecurityMonitor.d.ts +167 -0
- package/dist/dashboard/security/SecurityMonitor.js +559 -0
- package/dist/dashboard/server/AdminPanel.d.ts +183 -0
- package/dist/dashboard/server/AdminPanel.js +792 -0
- package/dist/dashboard/server/AdminPanelConfig.d.ts +42 -0
- package/dist/dashboard/server/AdminPanelConfig.js +80 -0
- package/dist/dashboard/server/AdminPanelState.d.ts +47 -0
- package/dist/dashboard/server/AdminPanelState.js +214 -0
- package/dist/dashboard/server/ApiRoutes.d.ts +17 -0
- package/dist/dashboard/server/ApiRoutes.js +149 -0
- package/dist/dashboard/server/DashboardServer.d.ts +49 -0
- package/dist/dashboard/server/DashboardServer.js +159 -0
- package/dist/dashboard/server/FileMetricsStorage.d.ts +49 -0
- package/dist/dashboard/server/FileMetricsStorage.js +195 -0
- package/dist/dashboard/server/HttpTransport.d.ts +23 -0
- package/dist/dashboard/server/HttpTransport.js +116 -0
- package/dist/dashboard/server/InstanceManager.d.ts +53 -0
- package/dist/dashboard/server/InstanceManager.js +284 -0
- package/dist/dashboard/server/KnowledgeStore.d.ts +35 -0
- package/dist/dashboard/server/KnowledgeStore.js +105 -0
- package/dist/dashboard/server/LeaderElection.d.ts +81 -0
- package/dist/dashboard/server/LeaderElection.js +268 -0
- package/dist/dashboard/server/MetricsCollector.d.ts +200 -0
- package/dist/dashboard/server/MetricsCollector.js +803 -0
- package/dist/dashboard/server/SessionPersistenceManager.d.ts +88 -0
- package/dist/dashboard/server/SessionPersistenceManager.js +457 -0
- package/dist/dashboard/server/ThinClient.d.ts +64 -0
- package/dist/dashboard/server/ThinClient.js +237 -0
- package/dist/dashboard/server/WebSocketManager.d.ts +161 -0
- package/dist/dashboard/server/WebSocketManager.js +463 -0
- package/dist/dashboard/server/httpLifecycle.d.ts +17 -0
- package/dist/dashboard/server/httpLifecycle.js +35 -0
- package/dist/dashboard/server/legacyDashboardHtml.d.ts +9 -0
- package/dist/dashboard/server/legacyDashboardHtml.js +618 -0
- package/dist/dashboard/server/legacyDashboardStyles.d.ts +5 -0
- package/dist/dashboard/server/legacyDashboardStyles.js +490 -0
- package/dist/dashboard/server/metricsAggregation.d.ts +252 -0
- package/dist/dashboard/server/metricsAggregation.js +206 -0
- package/dist/dashboard/server/metricsSerializer.d.ts +25 -0
- package/dist/dashboard/server/metricsSerializer.js +195 -0
- package/dist/dashboard/server/routes/admin.routes.d.ts +16 -0
- package/dist/dashboard/server/routes/admin.routes.js +596 -0
- package/dist/dashboard/server/routes/alerts.routes.d.ts +7 -0
- package/dist/dashboard/server/routes/alerts.routes.js +93 -0
- package/dist/dashboard/server/routes/api.feedback.routes.d.ts +73 -0
- package/dist/dashboard/server/routes/api.feedback.routes.js +171 -0
- package/dist/dashboard/server/routes/api.instructions.routes.d.ts +101 -0
- package/dist/dashboard/server/routes/api.instructions.routes.js +213 -0
- package/dist/dashboard/server/routes/api.usage.routes.d.ts +57 -0
- package/dist/dashboard/server/routes/api.usage.routes.js +374 -0
- package/dist/dashboard/server/routes/embeddings.routes.d.ts +6 -0
- package/dist/dashboard/server/routes/embeddings.routes.js +246 -0
- package/dist/dashboard/server/routes/graph.routes.d.ts +6 -0
- package/dist/dashboard/server/routes/graph.routes.js +280 -0
- package/dist/dashboard/server/routes/index.d.ts +38 -0
- package/dist/dashboard/server/routes/index.js +194 -0
- package/dist/dashboard/server/routes/instances.routes.d.ts +6 -0
- package/dist/dashboard/server/routes/instances.routes.js +35 -0
- package/dist/dashboard/server/routes/instructions.routes.d.ts +8 -0
- package/dist/dashboard/server/routes/instructions.routes.js +336 -0
- package/dist/dashboard/server/routes/knowledge.routes.d.ts +6 -0
- package/dist/dashboard/server/routes/knowledge.routes.js +82 -0
- package/dist/dashboard/server/routes/logs.routes.d.ts +6 -0
- package/dist/dashboard/server/routes/logs.routes.js +164 -0
- package/dist/dashboard/server/routes/messaging.routes.d.ts +16 -0
- package/dist/dashboard/server/routes/messaging.routes.js +293 -0
- package/dist/dashboard/server/routes/metrics.routes.d.ts +10 -0
- package/dist/dashboard/server/routes/metrics.routes.js +346 -0
- package/dist/dashboard/server/routes/scripts.routes.d.ts +9 -0
- package/dist/dashboard/server/routes/scripts.routes.js +84 -0
- package/dist/dashboard/server/routes/sqlite.routes.d.ts +9 -0
- package/dist/dashboard/server/routes/sqlite.routes.js +569 -0
- package/dist/dashboard/server/routes/status.routes.d.ts +7 -0
- package/dist/dashboard/server/routes/status.routes.js +183 -0
- package/dist/dashboard/server/routes/synthetic.routes.d.ts +7 -0
- package/dist/dashboard/server/routes/synthetic.routes.js +195 -0
- package/dist/dashboard/server/routes/tools.routes.d.ts +6 -0
- package/dist/dashboard/server/routes/tools.routes.js +46 -0
- package/dist/dashboard/server/routes/usage.routes.d.ts +6 -0
- package/dist/dashboard/server/routes/usage.routes.js +25 -0
- package/dist/dashboard/server/wsInit.d.ts +16 -0
- package/dist/dashboard/server/wsInit.js +35 -0
- package/dist/externalClientLib.d.ts +1 -0
- package/dist/externalClientLib.js +2 -0
- package/dist/minimal/index.d.ts +1 -0
- package/dist/minimal/index.js +140 -0
- package/dist/models/SessionPersistence.d.ts +115 -0
- package/dist/models/SessionPersistence.js +66 -0
- package/dist/models/instruction.d.ts +45 -0
- package/dist/models/instruction.js +2 -0
- package/dist/perf/benchmark.d.ts +1 -0
- package/dist/perf/benchmark.js +50 -0
- package/dist/portableClientWrapper.d.ts +1 -0
- package/dist/portableClientWrapper.js +2 -0
- package/dist/schemas/index.d.ts +128 -0
- package/dist/schemas/index.js +371 -0
- package/dist/scripts/runPerformanceBaseline.d.ts +1 -0
- package/dist/scripts/runPerformanceBaseline.js +17 -0
- package/dist/server/handshakeManager.d.ts +25 -0
- package/dist/server/handshakeManager.js +472 -0
- package/dist/server/index-server.d.ts +56 -0
- package/dist/server/index-server.js +822 -0
- package/dist/server/registry.d.ts +44 -0
- package/dist/server/registry.js +236 -0
- package/dist/server/sdkServer.d.ts +8 -0
- package/dist/server/sdkServer.js +299 -0
- package/dist/server/shutdownGuard.d.ts +41 -0
- package/dist/server/shutdownGuard.js +52 -0
- package/dist/server/thin-client.d.ts +22 -0
- package/dist/server/thin-client.js +111 -0
- package/dist/server/transport.d.ts +41 -0
- package/dist/server/transport.js +312 -0
- package/dist/server/transportFactory.d.ts +21 -0
- package/dist/server/transportFactory.js +429 -0
- package/dist/services/atomicFs.d.ts +22 -0
- package/dist/services/atomicFs.js +103 -0
- package/dist/services/auditLog.d.ts +38 -0
- package/dist/services/auditLog.js +142 -0
- package/dist/services/autoBackup.d.ts +14 -0
- package/dist/services/autoBackup.js +171 -0
- package/dist/services/autoSplit.d.ts +32 -0
- package/dist/services/autoSplit.js +113 -0
- package/dist/services/backupZip.d.ts +25 -0
- package/dist/services/backupZip.js +110 -0
- package/dist/services/bootstrapGating.d.ts +123 -0
- package/dist/services/bootstrapGating.js +221 -0
- package/dist/services/canonical.d.ts +23 -0
- package/dist/services/canonical.js +65 -0
- package/dist/services/categoryRules.d.ts +7 -0
- package/dist/services/categoryRules.js +37 -0
- package/dist/services/classificationService.d.ts +42 -0
- package/dist/services/classificationService.js +168 -0
- package/dist/services/embeddingService.d.ts +62 -0
- package/dist/services/embeddingService.js +259 -0
- package/dist/services/errors.d.ts +22 -0
- package/dist/services/errors.js +31 -0
- package/dist/services/featureFlags.d.ts +25 -0
- package/dist/services/featureFlags.js +89 -0
- package/dist/services/features.d.ts +13 -0
- package/dist/services/features.js +35 -0
- package/dist/services/handlers/instructions.add.d.ts +1 -0
- package/dist/services/handlers/instructions.add.js +496 -0
- package/dist/services/handlers/instructions.groom.d.ts +1 -0
- package/dist/services/handlers/instructions.groom.js +523 -0
- package/dist/services/handlers/instructions.import.d.ts +1 -0
- package/dist/services/handlers/instructions.import.js +173 -0
- package/dist/services/handlers/instructions.patch.d.ts +1 -0
- package/dist/services/handlers/instructions.patch.js +167 -0
- package/dist/services/handlers/instructions.query.d.ts +163 -0
- package/dist/services/handlers/instructions.query.js +522 -0
- package/dist/services/handlers/instructions.reload.d.ts +1 -0
- package/dist/services/handlers/instructions.reload.js +13 -0
- package/dist/services/handlers/instructions.remove.d.ts +1 -0
- package/dist/services/handlers/instructions.remove.js +118 -0
- package/dist/services/handlers/instructions.shared.d.ts +31 -0
- package/dist/services/handlers/instructions.shared.js +124 -0
- package/dist/services/handlers.activation.d.ts +1 -0
- package/dist/services/handlers.activation.js +203 -0
- package/dist/services/handlers.bootstrap.d.ts +1 -0
- package/dist/services/handlers.bootstrap.js +38 -0
- package/dist/services/handlers.dashboardConfig.d.ts +34 -0
- package/dist/services/handlers.dashboardConfig.js +108 -0
- package/dist/services/handlers.diagnostics.d.ts +1 -0
- package/dist/services/handlers.diagnostics.js +64 -0
- package/dist/services/handlers.feedback.d.ts +15 -0
- package/dist/services/handlers.feedback.js +378 -0
- package/dist/services/handlers.gates.d.ts +1 -0
- package/dist/services/handlers.gates.js +46 -0
- package/dist/services/handlers.graph.d.ts +53 -0
- package/dist/services/handlers.graph.js +231 -0
- package/dist/services/handlers.help.d.ts +1 -0
- package/dist/services/handlers.help.js +119 -0
- package/dist/services/handlers.instructionSchema.d.ts +1 -0
- package/dist/services/handlers.instructionSchema.js +227 -0
- package/dist/services/handlers.instructions.d.ts +8 -0
- package/dist/services/handlers.instructions.js +14 -0
- package/dist/services/handlers.instructionsDiagnostics.d.ts +1 -0
- package/dist/services/handlers.instructionsDiagnostics.js +14 -0
- package/dist/services/handlers.integrity.d.ts +1 -0
- package/dist/services/handlers.integrity.js +35 -0
- package/dist/services/handlers.manifest.d.ts +1 -0
- package/dist/services/handlers.manifest.js +24 -0
- package/dist/services/handlers.messaging.d.ts +12 -0
- package/dist/services/handlers.messaging.js +203 -0
- package/dist/services/handlers.metrics.d.ts +1 -0
- package/dist/services/handlers.metrics.js +43 -0
- package/dist/services/handlers.promote.d.ts +1 -0
- package/dist/services/handlers.promote.js +306 -0
- package/dist/services/handlers.prompt.d.ts +1 -0
- package/dist/services/handlers.prompt.js +7 -0
- package/dist/services/handlers.search.d.ts +69 -0
- package/dist/services/handlers.search.js +645 -0
- package/dist/services/handlers.testPrimitive.d.ts +1 -0
- package/dist/services/handlers.testPrimitive.js +5 -0
- package/dist/services/handlers.trace.d.ts +1 -0
- package/dist/services/handlers.trace.js +31 -0
- package/dist/services/handlers.usage.d.ts +1 -0
- package/dist/services/handlers.usage.js +11 -0
- package/dist/services/hotScore.d.ts +137 -0
- package/dist/services/hotScore.js +244 -0
- package/dist/services/indexContext.d.ts +117 -0
- package/dist/services/indexContext.js +968 -0
- package/dist/services/indexLoader.d.ts +44 -0
- package/dist/services/indexLoader.js +921 -0
- package/dist/services/indexRepository.d.ts +32 -0
- package/dist/services/indexRepository.js +71 -0
- package/dist/services/indexingService.d.ts +1 -0
- package/dist/services/indexingService.js +2 -0
- package/dist/services/instructions.dispatcher.d.ts +1 -0
- package/dist/services/instructions.dispatcher.js +231 -0
- package/dist/services/logPrefix.d.ts +1 -0
- package/dist/services/logPrefix.js +30 -0
- package/dist/services/logger.d.ts +52 -0
- package/dist/services/logger.js +268 -0
- package/dist/services/manifestManager.d.ts +82 -0
- package/dist/services/manifestManager.js +200 -0
- package/dist/services/messaging/agentMailbox.d.ts +60 -0
- package/dist/services/messaging/agentMailbox.js +353 -0
- package/dist/services/messaging/messagingPersistence.d.ts +20 -0
- package/dist/services/messaging/messagingPersistence.js +111 -0
- package/dist/services/messaging/messagingTypes.d.ts +150 -0
- package/dist/services/messaging/messagingTypes.js +66 -0
- package/dist/services/ownershipService.d.ts +1 -0
- package/dist/services/ownershipService.js +38 -0
- package/dist/services/performanceBaseline.d.ts +19 -0
- package/dist/services/performanceBaseline.js +210 -0
- package/dist/services/preflight.d.ts +12 -0
- package/dist/services/preflight.js +79 -0
- package/dist/services/promptReviewService.d.ts +44 -0
- package/dist/services/promptReviewService.js +101 -0
- package/dist/services/responseEnvelope.d.ts +6 -0
- package/dist/services/responseEnvelope.js +25 -0
- package/dist/services/seedBootstrap.d.ts +34 -0
- package/dist/services/seedBootstrap.js +427 -0
- package/dist/services/storage/factory.d.ts +17 -0
- package/dist/services/storage/factory.js +35 -0
- package/dist/services/storage/hashUtils.d.ts +11 -0
- package/dist/services/storage/hashUtils.js +35 -0
- package/dist/services/storage/index.d.ts +12 -0
- package/dist/services/storage/index.js +18 -0
- package/dist/services/storage/jsonFileStore.d.ts +32 -0
- package/dist/services/storage/jsonFileStore.js +241 -0
- package/dist/services/storage/migrationEngine.d.ts +35 -0
- package/dist/services/storage/migrationEngine.js +93 -0
- package/dist/services/storage/sqliteMessageStore.d.ts +53 -0
- package/dist/services/storage/sqliteMessageStore.js +146 -0
- package/dist/services/storage/sqliteSchema.d.ts +12 -0
- package/dist/services/storage/sqliteSchema.js +122 -0
- package/dist/services/storage/sqliteStore.d.ts +41 -0
- package/dist/services/storage/sqliteStore.js +339 -0
- package/dist/services/storage/sqliteUsageStore.d.ts +35 -0
- package/dist/services/storage/sqliteUsageStore.js +94 -0
- package/dist/services/storage/types.d.ts +171 -0
- package/dist/services/storage/types.js +12 -0
- package/dist/services/toolHandlers.d.ts +23 -0
- package/dist/services/toolHandlers.js +50 -0
- package/dist/services/toolRegistry.d.ts +20 -0
- package/dist/services/toolRegistry.js +490 -0
- package/dist/services/toolRegistry.zod.d.ts +10 -0
- package/dist/services/toolRegistry.zod.js +323 -0
- package/dist/services/tracing.d.ts +26 -0
- package/dist/services/tracing.js +260 -0
- package/dist/services/usageBuckets.d.ts +161 -0
- package/dist/services/usageBuckets.js +364 -0
- package/dist/services/validationService.d.ts +38 -0
- package/dist/services/validationService.js +125 -0
- package/dist/utils/BufferRing.d.ts +203 -0
- package/dist/utils/BufferRing.js +551 -0
- package/dist/utils/BufferRingExamples.d.ts +55 -0
- package/dist/utils/BufferRingExamples.js +188 -0
- package/dist/utils/envUtils.d.ts +42 -0
- package/dist/utils/envUtils.js +80 -0
- package/dist/utils/memoryMonitor.d.ts +83 -0
- package/dist/utils/memoryMonitor.js +275 -0
- package/dist/versioning/schemaVersion.d.ts +6 -0
- package/dist/versioning/schemaVersion.js +93 -0
- package/package.json +134 -0
- package/schemas/README.md +13 -0
- package/schemas/feedback-entry.schema.json +27 -0
- package/schemas/graph-export-v2.schema.json +60 -0
- package/schemas/index-server.code-schema.json +38477 -0
- package/schemas/instruction.schema.json +262 -0
- package/schemas/json-schema/SessionPersistence-persisted-admin-session.schema.json +54 -0
- package/schemas/json-schema/SessionPersistence-persisted-session-history-entry.schema.json +51 -0
- package/schemas/json-schema/SessionPersistence-persisted-web-socket-connection.schema.json +54 -0
- package/schemas/json-schema/SessionPersistence-session-persistence-config.schema.json +110 -0
- package/schemas/json-schema/SessionPersistence-session-persistence-data.schema.json +229 -0
- package/schemas/json-schema/SessionPersistence-session-persistence-manifest.schema.json +109 -0
- package/schemas/json-schema/SessionPersistence-session-persistence-metadata.schema.json +55 -0
- package/schemas/json-schema/instruction-audience-scope.schema.json +14 -0
- package/schemas/json-schema/instruction-content-type.schema.json +17 -0
- package/schemas/json-schema/instruction-instruction-entry.schema.json +206 -0
- package/schemas/json-schema/instruction-requirement-level.schema.json +16 -0
- package/schemas/manifest.json +78 -0
- package/schemas/manifest.schema.json +33 -0
- package/schemas/usage-batch.schema.json +16 -0
- package/schemas/usage-buckets.schema.json +30 -0
- package/schemas/usage-event.schema.json +17 -0
- package/scripts/copy-dashboard-assets.mjs +170 -0
- package/scripts/setup-hooks.cjs +28 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage Buckets Service - Phase 2 Temporal Windowing Implementation
|
|
3
|
+
*
|
|
4
|
+
* Provides time-windowed usage tracking with bucket rotation and persistence.
|
|
5
|
+
* Maintains usage-buckets.json sidecar file for temporal analytics.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Time bucket configuration
|
|
9
|
+
*/
|
|
10
|
+
export interface BucketConfig {
|
|
11
|
+
/** Bucket size in minutes (default: 60 = 1 hour) */
|
|
12
|
+
bucketSizeMinutes: number;
|
|
13
|
+
/** Number of buckets to maintain (default: 24 = 24 hours) */
|
|
14
|
+
bucketCount: number;
|
|
15
|
+
/** Maximum entries per bucket (default: 1000) */
|
|
16
|
+
maxEntriesPerBucket: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Usage entry within a time bucket
|
|
20
|
+
*/
|
|
21
|
+
export interface UsageEntry {
|
|
22
|
+
timestamp: string;
|
|
23
|
+
operation: string;
|
|
24
|
+
instructionId?: string;
|
|
25
|
+
clientInfo?: string;
|
|
26
|
+
durationMs?: number;
|
|
27
|
+
success: boolean;
|
|
28
|
+
errorCode?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Time bucket containing usage entries
|
|
32
|
+
*/
|
|
33
|
+
export interface UsageBucket {
|
|
34
|
+
/** Bucket start time (ISO 8601 UTC) */
|
|
35
|
+
startTime: string;
|
|
36
|
+
/** Bucket end time (ISO 8601 UTC) */
|
|
37
|
+
endTime: string;
|
|
38
|
+
/** Usage entries in this bucket */
|
|
39
|
+
entries: UsageEntry[];
|
|
40
|
+
/** Entry count for quick access */
|
|
41
|
+
entryCount: number;
|
|
42
|
+
/** Bucket creation timestamp */
|
|
43
|
+
createdAt: string;
|
|
44
|
+
/** Last update timestamp */
|
|
45
|
+
lastUpdated: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Usage buckets container with metadata
|
|
49
|
+
*/
|
|
50
|
+
export interface UsageBucketsContainer {
|
|
51
|
+
/** Configuration used */
|
|
52
|
+
config: BucketConfig;
|
|
53
|
+
/** Current bucket index (0 to bucketCount-1) */
|
|
54
|
+
currentBucketIndex: number;
|
|
55
|
+
/** Ring of time buckets */
|
|
56
|
+
buckets: UsageBucket[];
|
|
57
|
+
/** Container creation timestamp */
|
|
58
|
+
createdAt: string;
|
|
59
|
+
/** Last rotation timestamp */
|
|
60
|
+
lastRotation: string;
|
|
61
|
+
/** Total entries across all buckets */
|
|
62
|
+
totalEntries: number;
|
|
63
|
+
/** Metrics summary */
|
|
64
|
+
metrics: {
|
|
65
|
+
rotationCount: number;
|
|
66
|
+
totalOperations: number;
|
|
67
|
+
successRate: number;
|
|
68
|
+
avgDurationMs: number;
|
|
69
|
+
};
|
|
70
|
+
/** Integrity hash (sha256) over critical fields */
|
|
71
|
+
containerHash?: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Usage Buckets Service
|
|
75
|
+
*/
|
|
76
|
+
export declare class UsageBucketsService {
|
|
77
|
+
private container;
|
|
78
|
+
private bucketsFilePath;
|
|
79
|
+
private config;
|
|
80
|
+
private timeProvider;
|
|
81
|
+
constructor(instructionsDir: string, config?: Partial<BucketConfig>, opts?: {
|
|
82
|
+
timeProvider?: () => Date;
|
|
83
|
+
});
|
|
84
|
+
/**
|
|
85
|
+
* Initialize or load existing buckets container
|
|
86
|
+
*/
|
|
87
|
+
initialize(): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Record a usage entry
|
|
90
|
+
*/
|
|
91
|
+
recordUsage(entry: Omit<UsageEntry, 'timestamp'>): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Get current usage statistics
|
|
94
|
+
*/
|
|
95
|
+
getStats(): {
|
|
96
|
+
currentBucket: UsageBucket;
|
|
97
|
+
totalEntries: number;
|
|
98
|
+
metrics: UsageBucketsContainer['metrics'];
|
|
99
|
+
bucketCount: number;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Get usage entries for a time range
|
|
103
|
+
*/
|
|
104
|
+
getEntriesInRange(startTime: string, endTime: string): UsageEntry[];
|
|
105
|
+
/**
|
|
106
|
+
* Force a bucket rotation (for testing)
|
|
107
|
+
*/
|
|
108
|
+
forceRotation(): Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* Get the current bucket
|
|
111
|
+
*/
|
|
112
|
+
private getCurrentBucket;
|
|
113
|
+
/**
|
|
114
|
+
* Check if bucket rotation is needed and perform it
|
|
115
|
+
*/
|
|
116
|
+
private maybeRotate;
|
|
117
|
+
/**
|
|
118
|
+
* Rotate buckets and create new current bucket
|
|
119
|
+
*/
|
|
120
|
+
private rotateBuckets;
|
|
121
|
+
/**
|
|
122
|
+
* Create a new container
|
|
123
|
+
*/
|
|
124
|
+
private createContainer;
|
|
125
|
+
/**
|
|
126
|
+
* Load container from disk
|
|
127
|
+
*/
|
|
128
|
+
private loadContainer;
|
|
129
|
+
/**
|
|
130
|
+
* Save container to disk
|
|
131
|
+
*/
|
|
132
|
+
private saveContainer;
|
|
133
|
+
/**
|
|
134
|
+
* Get bucket start time aligned to bucket boundaries
|
|
135
|
+
*/
|
|
136
|
+
private getBucketStartTime;
|
|
137
|
+
/**
|
|
138
|
+
* Update container metrics
|
|
139
|
+
*/
|
|
140
|
+
private updateMetrics;
|
|
141
|
+
/**
|
|
142
|
+
* Compute integrity hash for container (excluding containerHash field itself)
|
|
143
|
+
*/
|
|
144
|
+
private computeHash;
|
|
145
|
+
/**
|
|
146
|
+
* Verify integrity hash of a loaded container
|
|
147
|
+
*/
|
|
148
|
+
private verifyHash;
|
|
149
|
+
/**
|
|
150
|
+
* Exposed only for testing: return internal container snapshot
|
|
151
|
+
*/
|
|
152
|
+
_debugGetContainer(): UsageBucketsContainer | null;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get or create the global usage buckets service instance
|
|
156
|
+
*/
|
|
157
|
+
export declare function getUsageBucketsService(instructionsDir?: string, config?: Partial<BucketConfig>): UsageBucketsService;
|
|
158
|
+
/**
|
|
159
|
+
* Record usage for the global instance
|
|
160
|
+
*/
|
|
161
|
+
export declare function recordUsage(entry: Omit<UsageEntry, 'timestamp'>): Promise<void>;
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Usage Buckets Service - Phase 2 Temporal Windowing Implementation
|
|
4
|
+
*
|
|
5
|
+
* Provides time-windowed usage tracking with bucket rotation and persistence.
|
|
6
|
+
* Maintains usage-buckets.json sidecar file for temporal analytics.
|
|
7
|
+
*/
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.UsageBucketsService = void 0;
|
|
13
|
+
exports.getUsageBucketsService = getUsageBucketsService;
|
|
14
|
+
exports.recordUsage = recordUsage;
|
|
15
|
+
const fs_1 = __importDefault(require("fs"));
|
|
16
|
+
const path_1 = __importDefault(require("path"));
|
|
17
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
18
|
+
const logger_1 = require("./logger");
|
|
19
|
+
/**
|
|
20
|
+
* Default bucket configuration
|
|
21
|
+
*/
|
|
22
|
+
const DEFAULT_CONFIG = {
|
|
23
|
+
bucketSizeMinutes: 60, // 1 hour buckets
|
|
24
|
+
bucketCount: 24, // 24 hours of history
|
|
25
|
+
maxEntriesPerBucket: 1000
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Usage Buckets Service
|
|
29
|
+
*/
|
|
30
|
+
class UsageBucketsService {
|
|
31
|
+
container = null;
|
|
32
|
+
bucketsFilePath;
|
|
33
|
+
config;
|
|
34
|
+
timeProvider;
|
|
35
|
+
constructor(instructionsDir, config = {}, opts = {}) {
|
|
36
|
+
this.bucketsFilePath = path_1.default.join(instructionsDir, 'usage-buckets.json');
|
|
37
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
38
|
+
this.timeProvider = opts.timeProvider ?? (() => new Date());
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Initialize or load existing buckets container
|
|
42
|
+
*/
|
|
43
|
+
async initialize() {
|
|
44
|
+
try {
|
|
45
|
+
if (fs_1.default.existsSync(this.bucketsFilePath)) {
|
|
46
|
+
await this.loadContainer();
|
|
47
|
+
(0, logger_1.logInfo)(`[usageBuckets] Loaded existing container from ${this.bucketsFilePath}`);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
await this.createContainer();
|
|
51
|
+
(0, logger_1.logInfo)(`[usageBuckets] Created new container at ${this.bucketsFilePath}`);
|
|
52
|
+
}
|
|
53
|
+
// Check if rotation is needed
|
|
54
|
+
await this.maybeRotate();
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
(0, logger_1.logError)(`[usageBuckets] Failed to initialize`, error);
|
|
58
|
+
// Fallback: create new container
|
|
59
|
+
await this.createContainer();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Record a usage entry
|
|
64
|
+
*/
|
|
65
|
+
async recordUsage(entry) {
|
|
66
|
+
if (!this.container) {
|
|
67
|
+
await this.initialize();
|
|
68
|
+
}
|
|
69
|
+
const usageEntry = {
|
|
70
|
+
...entry,
|
|
71
|
+
timestamp: this.timeProvider().toISOString()
|
|
72
|
+
};
|
|
73
|
+
// Add to current bucket
|
|
74
|
+
const currentBucket = this.getCurrentBucket();
|
|
75
|
+
currentBucket.entries.push(usageEntry);
|
|
76
|
+
currentBucket.entryCount++;
|
|
77
|
+
currentBucket.lastUpdated = usageEntry.timestamp;
|
|
78
|
+
// Update container metrics
|
|
79
|
+
this.updateMetrics(usageEntry);
|
|
80
|
+
// Check for rotation and persistence
|
|
81
|
+
await this.maybeRotate();
|
|
82
|
+
// Persist immediately for reliability
|
|
83
|
+
await this.saveContainer();
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get current usage statistics
|
|
87
|
+
*/
|
|
88
|
+
getStats() {
|
|
89
|
+
if (!this.container) {
|
|
90
|
+
throw new Error('UsageBuckets not initialized');
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
currentBucket: this.getCurrentBucket(),
|
|
94
|
+
totalEntries: this.container.totalEntries,
|
|
95
|
+
metrics: this.container.metrics,
|
|
96
|
+
bucketCount: this.container.buckets.length
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get usage entries for a time range
|
|
101
|
+
*/
|
|
102
|
+
getEntriesInRange(startTime, endTime) {
|
|
103
|
+
if (!this.container) {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
const start = new Date(startTime);
|
|
107
|
+
const end = new Date(endTime);
|
|
108
|
+
const entries = [];
|
|
109
|
+
for (const bucket of this.container.buckets) {
|
|
110
|
+
const bucketStart = new Date(bucket.startTime);
|
|
111
|
+
const bucketEnd = new Date(bucket.endTime);
|
|
112
|
+
// Check if bucket overlaps with requested range
|
|
113
|
+
if (bucketEnd >= start && bucketStart <= end) {
|
|
114
|
+
// Filter entries within the exact time range
|
|
115
|
+
const filteredEntries = bucket.entries.filter(entry => {
|
|
116
|
+
const entryTime = new Date(entry.timestamp);
|
|
117
|
+
return entryTime >= start && entryTime <= end;
|
|
118
|
+
});
|
|
119
|
+
entries.push(...filteredEntries);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return entries.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Force a bucket rotation (for testing)
|
|
126
|
+
*/
|
|
127
|
+
async forceRotation() {
|
|
128
|
+
if (!this.container) {
|
|
129
|
+
await this.initialize();
|
|
130
|
+
}
|
|
131
|
+
await this.rotateBuckets();
|
|
132
|
+
await this.saveContainer();
|
|
133
|
+
(0, logger_1.logInfo)('[usageBuckets] Forced bucket rotation');
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get the current bucket
|
|
137
|
+
*/
|
|
138
|
+
getCurrentBucket() {
|
|
139
|
+
if (!this.container) {
|
|
140
|
+
throw new Error('Container not initialized');
|
|
141
|
+
}
|
|
142
|
+
return this.container.buckets[this.container.currentBucketIndex];
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Check if bucket rotation is needed and perform it
|
|
146
|
+
*/
|
|
147
|
+
async maybeRotate() {
|
|
148
|
+
if (!this.container)
|
|
149
|
+
return;
|
|
150
|
+
const currentBucket = this.getCurrentBucket();
|
|
151
|
+
const now = this.timeProvider();
|
|
152
|
+
const bucketEnd = new Date(currentBucket.endTime);
|
|
153
|
+
if (now >= bucketEnd) {
|
|
154
|
+
await this.rotateBuckets();
|
|
155
|
+
(0, logger_1.logInfo)(`[usageBuckets] Rotated buckets at ${now.toISOString()}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Rotate buckets and create new current bucket
|
|
160
|
+
*/
|
|
161
|
+
async rotateBuckets() {
|
|
162
|
+
if (!this.container)
|
|
163
|
+
return;
|
|
164
|
+
const now = this.timeProvider();
|
|
165
|
+
const bucketStartTime = this.getBucketStartTime(now);
|
|
166
|
+
const bucketEndTime = new Date(bucketStartTime.getTime() + this.config.bucketSizeMinutes * 60 * 1000);
|
|
167
|
+
// Move to next bucket index (circular)
|
|
168
|
+
this.container.currentBucketIndex = (this.container.currentBucketIndex + 1) % this.config.bucketCount;
|
|
169
|
+
// Replace the bucket at current index
|
|
170
|
+
const newBucket = {
|
|
171
|
+
startTime: bucketStartTime.toISOString(),
|
|
172
|
+
endTime: bucketEndTime.toISOString(),
|
|
173
|
+
entries: [],
|
|
174
|
+
entryCount: 0,
|
|
175
|
+
createdAt: now.toISOString(),
|
|
176
|
+
lastUpdated: now.toISOString()
|
|
177
|
+
};
|
|
178
|
+
// Update total entries count (subtract old bucket entries)
|
|
179
|
+
const oldBucket = this.container.buckets[this.container.currentBucketIndex];
|
|
180
|
+
this.container.totalEntries -= oldBucket.entryCount;
|
|
181
|
+
// Replace bucket
|
|
182
|
+
this.container.buckets[this.container.currentBucketIndex] = newBucket;
|
|
183
|
+
// Update container metadata
|
|
184
|
+
this.container.lastRotation = now.toISOString();
|
|
185
|
+
this.container.metrics.rotationCount++;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Create a new container
|
|
189
|
+
*/
|
|
190
|
+
async createContainer() {
|
|
191
|
+
const now = this.timeProvider();
|
|
192
|
+
const buckets = [];
|
|
193
|
+
// Create initial buckets
|
|
194
|
+
for (let i = 0; i < this.config.bucketCount; i++) {
|
|
195
|
+
const bucketStart = new Date(now.getTime() - (this.config.bucketCount - 1 - i) * this.config.bucketSizeMinutes * 60 * 1000);
|
|
196
|
+
const bucketStartTime = this.getBucketStartTime(bucketStart);
|
|
197
|
+
const bucketEndTime = new Date(bucketStartTime.getTime() + this.config.bucketSizeMinutes * 60 * 1000);
|
|
198
|
+
buckets.push({
|
|
199
|
+
startTime: bucketStartTime.toISOString(),
|
|
200
|
+
endTime: bucketEndTime.toISOString(),
|
|
201
|
+
entries: [],
|
|
202
|
+
entryCount: 0,
|
|
203
|
+
createdAt: now.toISOString(),
|
|
204
|
+
lastUpdated: now.toISOString()
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
this.container = {
|
|
208
|
+
config: { ...this.config },
|
|
209
|
+
currentBucketIndex: this.config.bucketCount - 1, // Latest bucket
|
|
210
|
+
buckets,
|
|
211
|
+
createdAt: now.toISOString(),
|
|
212
|
+
lastRotation: now.toISOString(),
|
|
213
|
+
totalEntries: 0,
|
|
214
|
+
metrics: {
|
|
215
|
+
rotationCount: 0,
|
|
216
|
+
totalOperations: 0,
|
|
217
|
+
successRate: 1.0,
|
|
218
|
+
avgDurationMs: 0
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
await this.saveContainer();
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Load container from disk
|
|
225
|
+
*/
|
|
226
|
+
async loadContainer() {
|
|
227
|
+
const attemptLoad = (file) => {
|
|
228
|
+
try {
|
|
229
|
+
const raw = fs_1.default.readFileSync(file, 'utf8');
|
|
230
|
+
const parsed = JSON.parse(raw);
|
|
231
|
+
if (!parsed || !parsed.buckets || !Array.isArray(parsed.buckets)) {
|
|
232
|
+
throw new Error('Invalid format');
|
|
233
|
+
}
|
|
234
|
+
return parsed;
|
|
235
|
+
}
|
|
236
|
+
catch (e) {
|
|
237
|
+
(0, logger_1.logError)(`[usageBuckets] Failed to load ${file}`, e);
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
const primary = attemptLoad(this.bucketsFilePath);
|
|
242
|
+
const backupPath = this.bucketsFilePath + '.bak';
|
|
243
|
+
let container = primary;
|
|
244
|
+
if (!container || !this.verifyHash(container)) {
|
|
245
|
+
(0, logger_1.logError)('[usageBuckets] Primary container missing or hash mismatch, attempting backup');
|
|
246
|
+
const backup = fs_1.default.existsSync(backupPath) ? attemptLoad(backupPath) : null;
|
|
247
|
+
if (backup && this.verifyHash(backup)) {
|
|
248
|
+
container = backup;
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
(0, logger_1.logError)('[usageBuckets] Backup container invalid or hash mismatch; rebuilding new container');
|
|
252
|
+
await this.createContainer();
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Update config if changed
|
|
257
|
+
container.config = { ...container.config, ...this.config };
|
|
258
|
+
this.container = container;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Save container to disk
|
|
262
|
+
*/
|
|
263
|
+
async saveContainer() {
|
|
264
|
+
if (!this.container)
|
|
265
|
+
return;
|
|
266
|
+
// Ensure directory exists
|
|
267
|
+
const dir = path_1.default.dirname(this.bucketsFilePath);
|
|
268
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
269
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
270
|
+
}
|
|
271
|
+
// Write atomically
|
|
272
|
+
// Update hash prior to save
|
|
273
|
+
this.container.containerHash = this.computeHash(this.container);
|
|
274
|
+
const serialized = JSON.stringify(this.container, null, 2);
|
|
275
|
+
const tempFile = this.bucketsFilePath + '.tmp';
|
|
276
|
+
fs_1.default.writeFileSync(tempFile, serialized);
|
|
277
|
+
fs_1.default.renameSync(tempFile, this.bucketsFilePath);
|
|
278
|
+
// Maintain backup copy for recovery
|
|
279
|
+
try {
|
|
280
|
+
fs_1.default.writeFileSync(this.bucketsFilePath + '.bak', serialized);
|
|
281
|
+
}
|
|
282
|
+
catch (e) {
|
|
283
|
+
(0, logger_1.logError)(`[usageBuckets] Failed to write backup`, e);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Get bucket start time aligned to bucket boundaries
|
|
288
|
+
*/
|
|
289
|
+
getBucketStartTime(date) {
|
|
290
|
+
const minutes = date.getMinutes();
|
|
291
|
+
const alignedMinutes = Math.floor(minutes / this.config.bucketSizeMinutes) * this.config.bucketSizeMinutes;
|
|
292
|
+
const aligned = new Date(date);
|
|
293
|
+
aligned.setMinutes(alignedMinutes, 0, 0); // Clear seconds and milliseconds
|
|
294
|
+
return aligned;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Update container metrics
|
|
298
|
+
*/
|
|
299
|
+
updateMetrics(entry) {
|
|
300
|
+
if (!this.container)
|
|
301
|
+
return;
|
|
302
|
+
this.container.totalEntries++;
|
|
303
|
+
this.container.metrics.totalOperations++;
|
|
304
|
+
// Update success rate
|
|
305
|
+
const totalOps = this.container.metrics.totalOperations;
|
|
306
|
+
const currentSuccessCount = Math.round(this.container.metrics.successRate * (totalOps - 1));
|
|
307
|
+
const newSuccessCount = currentSuccessCount + (entry.success ? 1 : 0);
|
|
308
|
+
this.container.metrics.successRate = newSuccessCount / totalOps;
|
|
309
|
+
// Update average duration
|
|
310
|
+
if (entry.durationMs !== undefined) {
|
|
311
|
+
const currentAvg = this.container.metrics.avgDurationMs;
|
|
312
|
+
const currentCount = totalOps - 1;
|
|
313
|
+
this.container.metrics.avgDurationMs = (currentAvg * currentCount + entry.durationMs) / totalOps;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Compute integrity hash for container (excluding containerHash field itself)
|
|
318
|
+
*/
|
|
319
|
+
computeHash(container) {
|
|
320
|
+
const { containerHash: _unusedHash, ...rest } = container; // exclude hash field
|
|
321
|
+
void _unusedHash; // satisfy linter for intentional discard
|
|
322
|
+
const hash = crypto_1.default.createHash('sha256');
|
|
323
|
+
hash.update(JSON.stringify(rest));
|
|
324
|
+
return hash.digest('hex');
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Verify integrity hash of a loaded container
|
|
328
|
+
*/
|
|
329
|
+
verifyHash(container) {
|
|
330
|
+
if (!container.containerHash)
|
|
331
|
+
return false;
|
|
332
|
+
const expected = this.computeHash(container);
|
|
333
|
+
return expected === container.containerHash;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Exposed only for testing: return internal container snapshot
|
|
337
|
+
*/
|
|
338
|
+
/* @internal */
|
|
339
|
+
_debugGetContainer() {
|
|
340
|
+
return this.container ? JSON.parse(JSON.stringify(this.container)) : null;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
exports.UsageBucketsService = UsageBucketsService;
|
|
344
|
+
// Singleton instance for server use
|
|
345
|
+
let usageBucketsInstance = null;
|
|
346
|
+
/**
|
|
347
|
+
* Get or create the global usage buckets service instance
|
|
348
|
+
*/
|
|
349
|
+
function getUsageBucketsService(instructionsDir, config = {}) {
|
|
350
|
+
if (!usageBucketsInstance && instructionsDir) {
|
|
351
|
+
usageBucketsInstance = new UsageBucketsService(instructionsDir, config);
|
|
352
|
+
}
|
|
353
|
+
if (!usageBucketsInstance) {
|
|
354
|
+
throw new Error('Usage buckets service not initialized - call with instructionsDir first');
|
|
355
|
+
}
|
|
356
|
+
return usageBucketsInstance;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Record usage for the global instance
|
|
360
|
+
*/
|
|
361
|
+
async function recordUsage(entry) {
|
|
362
|
+
const service = getUsageBucketsService();
|
|
363
|
+
await service.recordUsage(entry);
|
|
364
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ErrorObject } from 'ajv';
|
|
2
|
+
interface ValidationCounters {
|
|
3
|
+
zodSuccess: number;
|
|
4
|
+
zodFailure: number;
|
|
5
|
+
ajvSuccess: number;
|
|
6
|
+
ajvFailure: number;
|
|
7
|
+
mode: string;
|
|
8
|
+
}
|
|
9
|
+
declare global {
|
|
10
|
+
var __MCP_VALIDATION_METRICS__: ValidationCounters | undefined;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Validate request parameters against the registered JSON Schema (or Zod schema) for `method`.
|
|
14
|
+
* Returns `{ ok: true }` when no schema is registered (fail-open) or when validation passes.
|
|
15
|
+
* @param method - JSON-RPC method name whose schema should be used for validation
|
|
16
|
+
* @param params - Parameter value received from the caller (may be `undefined`)
|
|
17
|
+
* @returns `{ ok: true }` on success, or `{ ok: false; errors: ErrorObject[] }` on failure
|
|
18
|
+
*/
|
|
19
|
+
export declare function validateParams(method: string, params: unknown): {
|
|
20
|
+
ok: true;
|
|
21
|
+
} | {
|
|
22
|
+
ok: false;
|
|
23
|
+
errors: ErrorObject[];
|
|
24
|
+
};
|
|
25
|
+
/** Evict all cached validators, forcing re-compilation on the next call to {@link validateParams}. */
|
|
26
|
+
export declare function clearValidationCache(): void;
|
|
27
|
+
/**
|
|
28
|
+
* Return a snapshot of the validation counters for use in metrics reporting.
|
|
29
|
+
* @returns Copy of the current Zod vs. Ajv success/failure counters and active mode
|
|
30
|
+
*/
|
|
31
|
+
export declare function getValidationMetrics(): {
|
|
32
|
+
zodSuccess: number;
|
|
33
|
+
zodFailure: number;
|
|
34
|
+
ajvSuccess: number;
|
|
35
|
+
ajvFailure: number;
|
|
36
|
+
mode: string;
|
|
37
|
+
};
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.validateParams = validateParams;
|
|
7
|
+
exports.clearValidationCache = clearValidationCache;
|
|
8
|
+
exports.getValidationMetrics = getValidationMetrics;
|
|
9
|
+
const ajv_1 = __importDefault(require("ajv"));
|
|
10
|
+
const ajv_formats_1 = __importDefault(require("ajv-formats"));
|
|
11
|
+
const json_schema_draft_07_json_1 = __importDefault(require("ajv/dist/refs/json-schema-draft-07.json"));
|
|
12
|
+
const runtimeConfig_1 = require("../config/runtimeConfig");
|
|
13
|
+
const toolRegistry_zod_1 = require("./toolRegistry.zod");
|
|
14
|
+
const zod_1 = require("zod");
|
|
15
|
+
const ajv = new ajv_1.default({ allErrors: true, strict: false });
|
|
16
|
+
(0, ajv_formats_1.default)(ajv);
|
|
17
|
+
try {
|
|
18
|
+
if (!ajv.getSchema('https://json-schema.org/draft-07/schema'))
|
|
19
|
+
ajv.addMetaSchema(json_schema_draft_07_json_1.default, 'https://json-schema.org/draft-07/schema');
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
// ignore meta-schema registration errors (non-fatal)
|
|
23
|
+
}
|
|
24
|
+
const cache = new Map();
|
|
25
|
+
// Validation mode feature flag is centrally defined via runtime config.
|
|
26
|
+
// Modes: 'ajv' => force Ajv only, 'zod' (default) => prefer Zod with Ajv fallback, 'auto' reserved for future heuristics.
|
|
27
|
+
function resolveValidationMode() {
|
|
28
|
+
const mode = (0, runtimeConfig_1.getRuntimeConfig)().validation.mode;
|
|
29
|
+
return mode && mode.trim().length ? mode : 'zod';
|
|
30
|
+
}
|
|
31
|
+
const validationCounters = { zodSuccess: 0, zodFailure: 0, ajvSuccess: 0, ajvFailure: 0, mode: resolveValidationMode() };
|
|
32
|
+
function resolveValidationSettings() {
|
|
33
|
+
const mode = resolveValidationMode();
|
|
34
|
+
validationCounters.mode = mode;
|
|
35
|
+
return { mode, forceAjv: mode === 'ajv' };
|
|
36
|
+
}
|
|
37
|
+
globalThis.__MCP_VALIDATION_METRICS__ = validationCounters;
|
|
38
|
+
function buildValidator(method) {
|
|
39
|
+
try {
|
|
40
|
+
const reg = (0, toolRegistry_zod_1.getZodEnhancedRegistry)().find(t => t.name === method);
|
|
41
|
+
if (!reg)
|
|
42
|
+
return null;
|
|
43
|
+
const compiled = ajv.compile(reg.inputSchema);
|
|
44
|
+
const { forceAjv } = resolveValidationSettings();
|
|
45
|
+
if (reg.zodSchema && !forceAjv) {
|
|
46
|
+
const z = reg.zodSchema;
|
|
47
|
+
const v = {
|
|
48
|
+
validate: (d) => {
|
|
49
|
+
try {
|
|
50
|
+
z.parse(d ?? {});
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
if (e instanceof zod_1.ZodError) {
|
|
55
|
+
// Map Zod issues into Ajv-like errors for uniform consumption
|
|
56
|
+
compiled.errors = e.issues.map(issue => ({
|
|
57
|
+
instancePath: issue.path.length ? '/' + issue.path.join('/') : '',
|
|
58
|
+
keyword: issue.code,
|
|
59
|
+
message: issue.message,
|
|
60
|
+
params: { issue },
|
|
61
|
+
schemaPath: '#'
|
|
62
|
+
}));
|
|
63
|
+
validationCounters.zodFailure++;
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
validationCounters.zodFailure++;
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
errors: null,
|
|
71
|
+
zod: z,
|
|
72
|
+
ajvFn: compiled
|
|
73
|
+
};
|
|
74
|
+
return v;
|
|
75
|
+
}
|
|
76
|
+
return { validate: (d) => compiled(d), errors: null };
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Validate request parameters against the registered JSON Schema (or Zod schema) for `method`.
|
|
84
|
+
* Returns `{ ok: true }` when no schema is registered (fail-open) or when validation passes.
|
|
85
|
+
* @param method - JSON-RPC method name whose schema should be used for validation
|
|
86
|
+
* @param params - Parameter value received from the caller (may be `undefined`)
|
|
87
|
+
* @returns `{ ok: true }` on success, or `{ ok: false; errors: ErrorObject[] }` on failure
|
|
88
|
+
*/
|
|
89
|
+
function validateParams(method, params) {
|
|
90
|
+
let entry = cache.get(method);
|
|
91
|
+
if (entry === undefined) {
|
|
92
|
+
entry = buildValidator(method);
|
|
93
|
+
cache.set(method, entry);
|
|
94
|
+
}
|
|
95
|
+
if (!entry)
|
|
96
|
+
return { ok: true }; // no schema => accept
|
|
97
|
+
const { forceAjv } = resolveValidationSettings();
|
|
98
|
+
const ok = entry.validate(params === undefined ? {} : params);
|
|
99
|
+
const hasZod = entry.zod !== undefined && !forceAjv;
|
|
100
|
+
if (ok) {
|
|
101
|
+
if (hasZod) {
|
|
102
|
+
validationCounters.zodSuccess++;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
validationCounters.ajvSuccess++;
|
|
106
|
+
}
|
|
107
|
+
return { ok: true };
|
|
108
|
+
}
|
|
109
|
+
if (hasZod) {
|
|
110
|
+
validationCounters.zodFailure++;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
validationCounters.ajvFailure++;
|
|
114
|
+
}
|
|
115
|
+
const v = entry.validate;
|
|
116
|
+
const errors = (v && typeof v === 'object' && 'errors' in v) ? v.errors || [] : [];
|
|
117
|
+
return { ok: false, errors };
|
|
118
|
+
}
|
|
119
|
+
/** Evict all cached validators, forcing re-compilation on the next call to {@link validateParams}. */
|
|
120
|
+
function clearValidationCache() { cache.clear(); }
|
|
121
|
+
/**
|
|
122
|
+
* Return a snapshot of the validation counters for use in metrics reporting.
|
|
123
|
+
* @returns Copy of the current Zod vs. Ajv success/failure counters and active mode
|
|
124
|
+
*/
|
|
125
|
+
function getValidationMetrics() { return { ...validationCounters }; }
|