@soulcraft/brainy 5.10.4 → 5.11.0

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.
@@ -5,14 +5,16 @@
5
5
  * Only enforces universal truths, learns everything else
6
6
  */
7
7
  import { NounType, VerbType } from '../types/graphTypes.js';
8
- // Dynamic import for Node.js os module
8
+ // Dynamic import for Node.js os and fs modules
9
9
  let os = null;
10
+ let fs = null;
10
11
  if (typeof window === 'undefined') {
11
12
  try {
12
13
  os = await import('node:os');
14
+ fs = await import('node:fs');
13
15
  }
14
16
  catch (e) {
15
- // OS module not available
17
+ // OS/FS modules not available
16
18
  }
17
19
  }
18
20
  // Browser-safe memory detection
@@ -30,46 +32,157 @@ const getAvailableMemory = () => {
30
32
  // Browser fallback: assume 2GB available
31
33
  return 2 * 1024 * 1024 * 1024;
32
34
  };
35
+ /**
36
+ * Detect container memory limit (Docker/Kubernetes/Cloud Run)
37
+ *
38
+ * Production-grade detection for containerized environments.
39
+ * Supports:
40
+ * - cgroup v1 (legacy Docker/K8s)
41
+ * - cgroup v2 (modern systems)
42
+ * - Environment variables (Cloud Run, GCP, AWS, Azure)
43
+ *
44
+ * @returns Container memory limit in bytes, or null if not containerized
45
+ */
46
+ const getContainerMemoryLimit = () => {
47
+ // Not in Node.js environment
48
+ if (!fs) {
49
+ return null;
50
+ }
51
+ try {
52
+ // 1. Check environment variables first (fastest, most reliable for Cloud Run)
53
+ // Google Cloud Run
54
+ if (process.env.CLOUD_RUN_MEMORY) {
55
+ // Format: "512Mi", "1Gi", "2Gi", "4Gi"
56
+ const match = process.env.CLOUD_RUN_MEMORY.match(/^(\d+)(Mi|Gi)$/);
57
+ if (match) {
58
+ const value = parseInt(match[1]);
59
+ const unit = match[2];
60
+ return unit === 'Gi' ? value * 1024 * 1024 * 1024 : value * 1024 * 1024;
61
+ }
62
+ }
63
+ // Generic MEMORY_LIMIT env var (bytes)
64
+ if (process.env.MEMORY_LIMIT) {
65
+ const limit = parseInt(process.env.MEMORY_LIMIT);
66
+ if (!isNaN(limit) && limit > 0) {
67
+ return limit;
68
+ }
69
+ }
70
+ // 2. Check cgroup v2 (modern Docker/K8s)
71
+ try {
72
+ const cgroupV2Path = '/sys/fs/cgroup/memory.max';
73
+ const cgroupV2Content = fs.readFileSync(cgroupV2Path, 'utf8').trim();
74
+ // "max" means no limit, otherwise it's bytes
75
+ if (cgroupV2Content !== 'max') {
76
+ const limit = parseInt(cgroupV2Content);
77
+ if (!isNaN(limit) && limit > 0) {
78
+ return limit;
79
+ }
80
+ }
81
+ }
82
+ catch (e) {
83
+ // cgroup v2 not available, try v1
84
+ }
85
+ // 3. Check cgroup v1 (legacy Docker/K8s)
86
+ try {
87
+ const cgroupV1Path = '/sys/fs/cgroup/memory/memory.limit_in_bytes';
88
+ const cgroupV1Content = fs.readFileSync(cgroupV1Path, 'utf8').trim();
89
+ const limit = parseInt(cgroupV1Content);
90
+ // Very large values (> 1 PB) indicate no limit
91
+ const ONE_PETABYTE = 1024 * 1024 * 1024 * 1024 * 1024;
92
+ if (!isNaN(limit) && limit > 0 && limit < ONE_PETABYTE) {
93
+ return limit;
94
+ }
95
+ }
96
+ catch (e) {
97
+ // cgroup v1 not available
98
+ }
99
+ // Not containerized or no limit set
100
+ return null;
101
+ }
102
+ catch (e) {
103
+ // Error reading cgroup files
104
+ return null;
105
+ }
106
+ };
33
107
  /**
34
108
  * Auto-configured limits based on system resources
35
109
  * These adapt to available memory and observed performance
36
110
  */
37
- class ValidationConfig {
38
- constructor() {
111
+ export class ValidationConfig {
112
+ constructor(options) {
39
113
  // Performance observations
40
114
  this.avgQueryTime = 0;
41
115
  this.queryCount = 0;
42
- // Auto-configure based on system resources
43
- const totalMemory = getSystemMemory();
44
- const availableMemory = getAvailableMemory();
45
- // Scale limits based on available memory
46
- // 1GB = 10K limit, 8GB = 80K limit, etc.
47
- this.maxLimit = Math.min(100000, // Absolute max for safety
48
- Math.floor(availableMemory / (1024 * 1024 * 100)) * 1000);
49
- // Query length scales with memory too
50
- this.maxQueryLength = Math.min(50000, Math.floor(availableMemory / (1024 * 1024 * 10)) * 1000);
51
116
  // Vector dimensions (standard for all-MiniLM-L6-v2)
52
117
  this.maxVectorDimensions = 384;
118
+ // Detect container memory limit
119
+ this.detectedContainerLimit = getContainerMemoryLimit();
120
+ // Priority 1: Explicit override (highest priority)
121
+ if (options?.maxQueryLimit !== undefined) {
122
+ this.maxLimit = Math.min(options.maxQueryLimit, 100000); // Still cap at 100k for safety
123
+ this.limitBasis = 'override';
124
+ // Scale query length with limit
125
+ this.maxQueryLength = Math.min(50000, this.maxLimit * 5);
126
+ return;
127
+ }
128
+ // Priority 2: Reserved memory specified
129
+ if (options?.reservedQueryMemory !== undefined) {
130
+ this.maxLimit = Math.min(100000, Math.floor(options.reservedQueryMemory / (1024 * 1024 * 100)) * 1000);
131
+ this.limitBasis = 'reservedMemory';
132
+ this.maxQueryLength = Math.min(50000, Math.floor(options.reservedQueryMemory / (1024 * 1024 * 10)) * 1000);
133
+ return;
134
+ }
135
+ // Priority 3: Container detected (smart containerized behavior)
136
+ if (this.detectedContainerLimit) {
137
+ // In containers, assume 75% used by graph data (EXPECTED)
138
+ // Reserve 25% for query operations
139
+ const queryMemory = this.detectedContainerLimit * 0.25;
140
+ this.maxLimit = Math.min(100000, Math.floor(queryMemory / (1024 * 1024 * 100)) * 1000);
141
+ this.limitBasis = 'containerMemory';
142
+ this.maxQueryLength = Math.min(50000, Math.floor(queryMemory / (1024 * 1024 * 10)) * 1000);
143
+ return;
144
+ }
145
+ // Priority 4: Free memory (fallback, current behavior)
146
+ const availableMemory = getAvailableMemory();
147
+ this.maxLimit = Math.min(100000, Math.floor(availableMemory / (1024 * 1024 * 100)) * 1000);
148
+ this.limitBasis = 'freeMemory';
149
+ this.maxQueryLength = Math.min(50000, Math.floor(availableMemory / (1024 * 1024 * 10)) * 1000);
53
150
  }
54
- static getInstance() {
151
+ static getInstance(options) {
55
152
  if (!ValidationConfig.instance) {
56
- ValidationConfig.instance = new ValidationConfig();
153
+ ValidationConfig.instance = new ValidationConfig(options);
57
154
  }
58
155
  return ValidationConfig.instance;
59
156
  }
157
+ /**
158
+ * Reset singleton (for testing or reconfiguration)
159
+ */
160
+ static reset() {
161
+ ValidationConfig.instance = null;
162
+ }
163
+ /**
164
+ * Reconfigure with new options
165
+ */
166
+ static reconfigure(options) {
167
+ ValidationConfig.instance = new ValidationConfig(options);
168
+ return ValidationConfig.instance;
169
+ }
60
170
  /**
61
171
  * Learn from actual usage to adjust limits
62
172
  */
63
173
  recordQuery(duration, resultCount) {
64
174
  this.queryCount++;
65
175
  this.avgQueryTime = (this.avgQueryTime * (this.queryCount - 1) + duration) / this.queryCount;
66
- // If queries are consistently fast with large results, increase limits
67
- if (this.avgQueryTime < 100 && resultCount > this.maxLimit * 0.8) {
68
- this.maxLimit = Math.min(this.maxLimit * 1.5, 100000);
69
- }
70
- // If queries are slow, reduce limits
71
- if (this.avgQueryTime > 1000) {
72
- this.maxLimit = Math.max(this.maxLimit * 0.8, 1000);
176
+ // Only auto-adjust if not using explicit overrides
177
+ if (this.limitBasis !== 'override') {
178
+ // If queries are consistently fast with large results, increase limits
179
+ if (this.avgQueryTime < 100 && resultCount > this.maxLimit * 0.8) {
180
+ this.maxLimit = Math.min(this.maxLimit * 1.5, 100000);
181
+ }
182
+ // If queries are slow, reduce limits
183
+ if (this.avgQueryTime > 1000) {
184
+ this.maxLimit = Math.max(this.maxLimit * 0.8, 1000);
185
+ }
73
186
  }
74
187
  }
75
188
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soulcraft/brainy",
3
- "version": "5.10.4",
3
+ "version": "5.11.0",
4
4
  "description": "Universal Knowledge Protocol™ - World's first Triple Intelligence database unifying vector, graph, and document search in one API. Stage 3 CANONICAL: 42 nouns × 127 verbs covering 96-97% of all human knowledge.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",