@jagilber-org/index-server 1.27.0 → 1.28.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.
- package/CHANGELOG.md +63 -1
- package/CONTRIBUTING.md +3 -3
- package/dist/dashboard/client/admin.html +58 -28
- package/dist/dashboard/client/css/admin.css +54 -0
- package/dist/dashboard/client/js/admin.config.js +3 -6
- package/dist/dashboard/client/js/admin.embeddings.js +63 -4
- package/dist/dashboard/client/js/admin.events.js +256 -0
- package/dist/dashboard/client/js/admin.feedback.js +1 -1
- package/dist/dashboard/client/js/admin.instructions.js +1 -1
- package/dist/dashboard/client/js/admin.maintenance.js +75 -32
- package/dist/dashboard/client/js/admin.sessions.js +1 -1
- package/dist/dashboard/security/SecurityMonitor.js +2 -2
- package/dist/dashboard/server/AdminPanel.js +83 -6
- package/dist/dashboard/server/AdminPanelConfig.d.ts +11 -0
- package/dist/dashboard/server/AdminPanelConfig.js +47 -17
- package/dist/dashboard/server/AdminPanelState.js +5 -1
- package/dist/dashboard/server/ApiRoutes.js +2 -1
- package/dist/dashboard/server/DashboardServer.js +13 -0
- package/dist/dashboard/server/MetricsCollector.js +3 -2
- package/dist/dashboard/server/WebSocketManager.js +2 -2
- package/dist/dashboard/server/middleware/ensureLoadedMiddleware.d.ts +1 -1
- package/dist/dashboard/server/middleware/ensureLoadedMiddleware.js +1 -1
- package/dist/dashboard/server/routes/admin.routes.js +143 -17
- package/dist/dashboard/server/routes/api.usage.routes.js +5 -1
- package/dist/dashboard/server/routes/embeddings.routes.js +91 -1
- package/dist/dashboard/server/routes/instructions.routes.js +142 -12
- package/dist/dashboard/server/routes/scripts.routes.js +1 -1
- package/dist/dashboard/server/routes/sqlite.routes.js +74 -0
- package/dist/models/instruction.d.ts +1 -1
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/index.js +1 -1
- package/dist/server/sdkServer.js +12 -4
- package/dist/services/auditLog.d.ts +1 -1
- package/dist/services/auditLog.js +1 -1
- package/dist/services/embeddingService.d.ts +2 -0
- package/dist/services/embeddingService.js +16 -4
- package/dist/services/embeddingTrigger.d.ts +33 -0
- package/dist/services/embeddingTrigger.js +86 -0
- package/dist/services/eventBuffer.d.ts +45 -0
- package/dist/services/eventBuffer.js +110 -0
- package/dist/services/feedbackStorage.js +1 -1
- package/dist/services/handlers/instructions.add.js +36 -3
- package/dist/services/handlers/instructions.import.js +71 -13
- package/dist/services/handlers.dashboardConfig.js +81 -0
- package/dist/services/handlers.feedback.js +1 -1
- package/dist/services/handlers.instructionSchema.js +4 -4
- package/dist/services/handlers.search.js +3 -3
- package/dist/services/indexContext.d.ts +18 -0
- package/dist/services/indexContext.js +133 -24
- package/dist/services/instructionRecordValidation.d.ts +3 -0
- package/dist/services/instructionRecordValidation.js +64 -4
- package/dist/services/logger.js +9 -0
- package/dist/services/manifestManager.js +11 -1
- package/dist/services/seedBootstrap.js +7 -3
- package/dist/services/storage/factory.d.ts +2 -0
- package/dist/services/storage/factory.js +12 -1
- package/dist/services/toolRegistry.js +8 -8
- package/dist/services/toolRegistry.zod.js +3 -3
- package/dist/services/tracing.js +3 -1
- package/dist/versioning/schemaVersion.d.ts +1 -1
- package/dist/versioning/schemaVersion.js +47 -2
- package/package.json +54 -40
- package/schemas/index-server.code-schema.json +1 -1
- package/schemas/instruction.schema.json +3 -3
- package/schemas/json-schema/instruction-content-type.schema.json +1 -1
- package/schemas/json-schema/instruction-instruction-entry.schema.json +1 -1
- package/scripts/README.md +48 -0
- package/scripts/{generate-certs.mjs → build/generate-certs.mjs} +1 -1
- package/scripts/{setup-wizard.mjs → build/setup-wizard.mjs} +1 -1
- package/scripts/{setup-hooks.cjs → hooks/setup-hooks.cjs} +3 -3
- package/server.json +3 -3
- /package/scripts/{copy-dashboard-assets.mjs → build/copy-dashboard-assets.mjs} +0 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* In-process ring buffer of recent log events at WARN/ERROR severity.
|
|
4
|
+
*
|
|
5
|
+
* Surfaces operationally-meaningful events to the admin dashboard so operators
|
|
6
|
+
* do not have to tail server logs. Read-only, bounded; never persists to disk.
|
|
7
|
+
*
|
|
8
|
+
* Capacity defaults to 500. The logger emits records via `recordEvent()` only
|
|
9
|
+
* for WARN or ERROR levels so that volume on healthy systems is negligible.
|
|
10
|
+
*
|
|
11
|
+
* Constitution alignment: OB-1, OB-3, OB-4, OB-5 (structured, severity-visible).
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.recordEvent = recordEvent;
|
|
15
|
+
exports.listEvents = listEvents;
|
|
16
|
+
exports.eventCounts = eventCounts;
|
|
17
|
+
exports.clearEvents = clearEvents;
|
|
18
|
+
exports._eventBufferSize = _eventBufferSize;
|
|
19
|
+
const DEFAULT_CAPACITY = 500;
|
|
20
|
+
class EventRing {
|
|
21
|
+
buf = [];
|
|
22
|
+
nextId = 1;
|
|
23
|
+
capacity = DEFAULT_CAPACITY;
|
|
24
|
+
/** Read the configured capacity (env: INDEX_SERVER_EVENT_BUFFER_SIZE, min 50, max 5000). */
|
|
25
|
+
resolveCapacity() {
|
|
26
|
+
const raw = process.env.INDEX_SERVER_EVENT_BUFFER_SIZE;
|
|
27
|
+
if (!raw)
|
|
28
|
+
return DEFAULT_CAPACITY;
|
|
29
|
+
const n = parseInt(raw, 10);
|
|
30
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
31
|
+
return DEFAULT_CAPACITY;
|
|
32
|
+
return Math.min(5000, Math.max(50, n));
|
|
33
|
+
}
|
|
34
|
+
add(level, msg, detail, pid) {
|
|
35
|
+
// Refresh capacity lazily so runtime changes take effect.
|
|
36
|
+
const cap = this.resolveCapacity();
|
|
37
|
+
if (cap !== this.capacity) {
|
|
38
|
+
this.capacity = cap;
|
|
39
|
+
if (this.buf.length > cap)
|
|
40
|
+
this.buf.splice(0, this.buf.length - cap);
|
|
41
|
+
}
|
|
42
|
+
const evt = {
|
|
43
|
+
id: this.nextId++,
|
|
44
|
+
ts: new Date().toISOString(),
|
|
45
|
+
level,
|
|
46
|
+
msg,
|
|
47
|
+
pid,
|
|
48
|
+
};
|
|
49
|
+
if (detail)
|
|
50
|
+
evt.detail = detail.length > 4096 ? detail.slice(0, 4096) + '…' : detail;
|
|
51
|
+
this.buf.push(evt);
|
|
52
|
+
if (this.buf.length > this.capacity)
|
|
53
|
+
this.buf.shift();
|
|
54
|
+
}
|
|
55
|
+
/** List events newer than `sinceId` (exclusive), optionally filtered by level. */
|
|
56
|
+
list(opts = {}) {
|
|
57
|
+
const sinceId = opts.sinceId ?? 0;
|
|
58
|
+
const limit = Math.min(1000, Math.max(1, opts.limit ?? 200));
|
|
59
|
+
const filtered = [];
|
|
60
|
+
for (let i = this.buf.length - 1; i >= 0 && filtered.length < limit; i--) {
|
|
61
|
+
const e = this.buf[i];
|
|
62
|
+
if (e.id <= sinceId)
|
|
63
|
+
break;
|
|
64
|
+
if (opts.level && e.level !== opts.level)
|
|
65
|
+
continue;
|
|
66
|
+
filtered.push(e);
|
|
67
|
+
}
|
|
68
|
+
return filtered.reverse();
|
|
69
|
+
}
|
|
70
|
+
/** Counts of WARN and ERROR events newer than `sinceId`. */
|
|
71
|
+
counts(sinceId = 0) {
|
|
72
|
+
let warn = 0, error = 0;
|
|
73
|
+
for (const e of this.buf) {
|
|
74
|
+
if (e.id <= sinceId)
|
|
75
|
+
continue;
|
|
76
|
+
if (e.level === 'WARN')
|
|
77
|
+
warn++;
|
|
78
|
+
else if (e.level === 'ERROR')
|
|
79
|
+
error++;
|
|
80
|
+
}
|
|
81
|
+
const latestId = this.buf.length ? this.buf[this.buf.length - 1].id : 0;
|
|
82
|
+
return { warn, error, total: warn + error, latestId };
|
|
83
|
+
}
|
|
84
|
+
clear() {
|
|
85
|
+
this.buf = [];
|
|
86
|
+
}
|
|
87
|
+
/** Test-only: current size. */
|
|
88
|
+
size() { return this.buf.length; }
|
|
89
|
+
}
|
|
90
|
+
const ring = new EventRing();
|
|
91
|
+
/** Emit a WARN/ERROR event into the buffer. Called by the logger. */
|
|
92
|
+
function recordEvent(level, msg, detail, pid) {
|
|
93
|
+
ring.add(level, msg, detail, pid);
|
|
94
|
+
}
|
|
95
|
+
/** List recent events (most recent last). */
|
|
96
|
+
function listEvents(opts) {
|
|
97
|
+
return ring.list(opts);
|
|
98
|
+
}
|
|
99
|
+
/** Compute new-event counts since the supplied id (used for the dashboard counter bubble). */
|
|
100
|
+
function eventCounts(sinceId) {
|
|
101
|
+
return ring.counts(sinceId);
|
|
102
|
+
}
|
|
103
|
+
/** Clear the buffer (used by `Mark all read` and tests). */
|
|
104
|
+
function clearEvents() {
|
|
105
|
+
ring.clear();
|
|
106
|
+
}
|
|
107
|
+
/** Test-only helper. */
|
|
108
|
+
function _eventBufferSize() {
|
|
109
|
+
return ring.size();
|
|
110
|
+
}
|
|
@@ -83,6 +83,6 @@ function saveFeedbackStorage(storage) {
|
|
|
83
83
|
}
|
|
84
84
|
function generateFeedbackId(type, timestamp) {
|
|
85
85
|
const hash = (0, crypto_1.createHash)('sha256');
|
|
86
|
-
hash.update(`${type}-${timestamp}-${
|
|
86
|
+
hash.update(`${type}-${timestamp}-${(0, crypto_1.randomBytes)(8).toString('hex')}`);
|
|
87
87
|
return hash.digest('hex').substring(0, 16);
|
|
88
88
|
}
|
|
@@ -96,6 +96,22 @@ const instructions_shared_1 = require("./instructions.shared");
|
|
|
96
96
|
return { error: priorLoad };
|
|
97
97
|
return { error: { code: 'unknown', detail: 'missing-existing-entry', raw: 'missing-existing-entry' } };
|
|
98
98
|
};
|
|
99
|
+
const validateExistingCollisionFile = (filePath) => {
|
|
100
|
+
if (!fs_1.default.existsSync(filePath))
|
|
101
|
+
return [];
|
|
102
|
+
try {
|
|
103
|
+
const raw = JSON.parse(fs_1.default.readFileSync(filePath, 'utf8'));
|
|
104
|
+
const validationCandidate = { ...raw, schemaVersion: schemaVersion_1.SCHEMA_VERSION };
|
|
105
|
+
const validation = (0, instructionRecordValidation_1.validateInstructionRecord)(validationCandidate);
|
|
106
|
+
return validation.validationErrors
|
|
107
|
+
.filter((issue) => issue.includes('/classification'))
|
|
108
|
+
.map((issue) => (0, instructionRecordValidation_1.sanitizeErrorDetail)(issue) || 'existing entry failed validation');
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
const sanitized = (0, instructionRecordValidation_1.sanitizeLoadError)(err, 'parse_failed');
|
|
112
|
+
return [sanitized.detail];
|
|
113
|
+
}
|
|
114
|
+
};
|
|
99
115
|
const verifyReadBack = async (id, filePath, requestedCategories) => {
|
|
100
116
|
try {
|
|
101
117
|
(0, indexContext_1.invalidate)();
|
|
@@ -222,6 +238,12 @@ const instructions_shared_1 = require("./instructions.shared");
|
|
|
222
238
|
mutable.requirement = 'optional';
|
|
223
239
|
if (mutable.categories === undefined)
|
|
224
240
|
mutable.categories = [];
|
|
241
|
+
if (mutable.contentType === undefined)
|
|
242
|
+
mutable.contentType = 'instruction';
|
|
243
|
+
}
|
|
244
|
+
const surfaceValidation = (0, instructionRecordValidation_1.validateInstructionInputSurface)(e);
|
|
245
|
+
if (surfaceValidation.validationErrors.length) {
|
|
246
|
+
return failValidation('invalid_instruction', surfaceValidation.validationErrors, surfaceValidation.hints, { id: e.id });
|
|
225
247
|
}
|
|
226
248
|
const dir = (0, indexContext_1.getInstructionsDir)();
|
|
227
249
|
if (!fs_1.default.existsSync(dir))
|
|
@@ -257,9 +279,8 @@ const instructions_shared_1 = require("./instructions.shared");
|
|
|
257
279
|
e.title === undefined ? 'title: missing required field' : undefined,
|
|
258
280
|
e.body === undefined ? 'body: missing required field' : undefined,
|
|
259
281
|
].filter((issue) => !!issue);
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
return failValidation(requiredFieldErrors.length ? 'missing required fields' : 'invalid_instruction', [...requiredFieldErrors, ...surfaceValidation.validationErrors], surfaceValidation.hints, { id: e.id });
|
|
282
|
+
if (requiredFieldErrors.length) {
|
|
283
|
+
return failValidation('missing required fields', requiredFieldErrors, surfaceValidation.hints, { id: e.id });
|
|
263
284
|
}
|
|
264
285
|
const overwrite = !!p.overwrite;
|
|
265
286
|
const exists = overwrite ? ((await (0, indexContext_1.ensureLoadedAsync)()).byId.has(e.id) || fs_1.default.existsSync(file)) : false;
|
|
@@ -503,6 +524,18 @@ const instructions_shared_1 = require("./instructions.shared");
|
|
|
503
524
|
return failValidation('invalid_instruction', err.validationErrors, err.hints, { id: e.id });
|
|
504
525
|
}
|
|
505
526
|
if (!overwrite && (0, indexContext_1.isDuplicateInstructionWriteError)(err)) {
|
|
527
|
+
const existingValidationErrors = validateExistingCollisionFile(file);
|
|
528
|
+
if (existingValidationErrors.length) {
|
|
529
|
+
return {
|
|
530
|
+
id: e.id,
|
|
531
|
+
success: false,
|
|
532
|
+
skipped: false,
|
|
533
|
+
created: false,
|
|
534
|
+
overwritten: false,
|
|
535
|
+
error: 'existing_instruction_invalid',
|
|
536
|
+
validationErrors: existingValidationErrors,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
506
539
|
let st0 = await (0, indexContext_1.ensureLoadedAsync)();
|
|
507
540
|
let visible = st0.byId.has(e.id);
|
|
508
541
|
let repaired = false;
|
|
@@ -15,6 +15,12 @@ const ownershipService_1 = require("../ownershipService");
|
|
|
15
15
|
const instructionRecordValidation_1 = require("../instructionRecordValidation");
|
|
16
16
|
const instructionRecordValidation_2 = require("../instructionRecordValidation");
|
|
17
17
|
const auditLog_1 = require("../auditLog");
|
|
18
|
+
const logger_1 = require("../logger");
|
|
19
|
+
// Structured WARN without auto-attached call stack: the log-hygiene gate
|
|
20
|
+
// (scripts/crawl-logs.mjs) treats WARN-with-stack as a budget violation
|
|
21
|
+
// (max-stack-warn=5). Use log('WARN', ...) directly with serialized detail
|
|
22
|
+
// so per-entry import rejections stay structured but stackless.
|
|
23
|
+
const warnStruct = (msg, detail) => (0, logger_1.log)('WARN', msg, { detail: detail === undefined ? undefined : typeof detail === 'string' ? detail : JSON.stringify(detail) });
|
|
18
24
|
const runtimeConfig_1 = require("../../config/runtimeConfig");
|
|
19
25
|
const manifestManager_1 = require("../manifestManager");
|
|
20
26
|
const instructions_shared_1 = require("./instructions.shared");
|
|
@@ -44,48 +50,77 @@ function parseInlineEntries(rawEntries) {
|
|
|
44
50
|
(0, registry_1.registerHandler)('index_import', (0, instructions_shared_1.guard)('index_import', async (p) => {
|
|
45
51
|
let entries;
|
|
46
52
|
const mode = p.mode || 'skip';
|
|
53
|
+
// Source-type breadcrumb for observability: agents currently get a silent
|
|
54
|
+
// { error: ... } back on top-level failures (path-blocked, parse errors,
|
|
55
|
+
// missing files). Without these explicit WARN logs the only signal is the
|
|
56
|
+
// RPC response, which dashboards/tails never see (RCA 2026-05-01 dev 8687).
|
|
57
|
+
const sourceType = Array.isArray(p.entries)
|
|
58
|
+
? 'inline-array'
|
|
59
|
+
: typeof p.entries === 'string'
|
|
60
|
+
? 'inline-or-file'
|
|
61
|
+
: typeof p.source === 'string'
|
|
62
|
+
? 'directory'
|
|
63
|
+
: 'none';
|
|
64
|
+
const inlineCount = Array.isArray(p.entries) ? p.entries.length : undefined;
|
|
65
|
+
(0, logger_1.logInfo)('[import] start', { mode, sourceType, inlineCount, source: typeof p.source === 'string' ? p.source : undefined });
|
|
47
66
|
if (Array.isArray(p.entries)) {
|
|
48
67
|
entries = p.entries;
|
|
49
68
|
}
|
|
50
69
|
else if (typeof p.entries === 'string') {
|
|
51
70
|
const inlineEntries = parseInlineEntries(p.entries);
|
|
52
|
-
if (inlineEntries.error)
|
|
71
|
+
if (inlineEntries.error) {
|
|
72
|
+
warnStruct('[import] rejected', { reason: 'inline-parse-error', detail: inlineEntries.error });
|
|
53
73
|
return inlineEntries.error;
|
|
74
|
+
}
|
|
54
75
|
if (inlineEntries.entries) {
|
|
55
76
|
entries = inlineEntries.entries;
|
|
56
77
|
}
|
|
57
78
|
else {
|
|
58
79
|
const filePath = path_1.default.resolve(p.entries);
|
|
59
|
-
if (!isPathAllowed(filePath))
|
|
80
|
+
if (!isPathAllowed(filePath)) {
|
|
81
|
+
warnStruct('[import] rejected', { reason: 'path-not-allowed', path: filePath });
|
|
60
82
|
return { error: 'entries path is outside allowed directories', path: filePath };
|
|
61
|
-
|
|
83
|
+
}
|
|
84
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
85
|
+
warnStruct('[import] rejected', { reason: 'entries-file-not-found', path: filePath });
|
|
62
86
|
return { error: 'entries file not found', path: filePath };
|
|
87
|
+
}
|
|
63
88
|
try {
|
|
64
89
|
const raw = JSON.parse(fs_1.default.readFileSync(filePath, 'utf8'));
|
|
65
|
-
if (!Array.isArray(raw))
|
|
90
|
+
if (!Array.isArray(raw)) {
|
|
91
|
+
warnStruct('[import] rejected', { reason: 'entries-file-not-array', path: filePath });
|
|
66
92
|
return { error: 'entries file must contain a JSON array', path: filePath };
|
|
93
|
+
}
|
|
67
94
|
entries = raw;
|
|
68
95
|
}
|
|
69
96
|
catch (e) {
|
|
97
|
+
warnStruct('[import] rejected', { reason: 'entries-file-parse-error', path: filePath, detail: e.message });
|
|
70
98
|
return { error: 'entries file parse error', path: filePath, detail: e.message };
|
|
71
99
|
}
|
|
72
100
|
}
|
|
73
101
|
}
|
|
74
102
|
else if (typeof p.source === 'string') {
|
|
75
103
|
const dirPath = path_1.default.resolve(p.source);
|
|
76
|
-
if (!isPathAllowed(dirPath))
|
|
104
|
+
if (!isPathAllowed(dirPath)) {
|
|
105
|
+
warnStruct('[import] rejected', { reason: 'source-not-allowed', path: dirPath });
|
|
77
106
|
return { error: 'source path is outside allowed directories', path: dirPath };
|
|
78
|
-
|
|
107
|
+
}
|
|
108
|
+
if (!fs_1.default.existsSync(dirPath)) {
|
|
109
|
+
warnStruct('[import] rejected', { reason: 'source-not-found', path: dirPath });
|
|
79
110
|
return { error: 'source directory not found', path: dirPath };
|
|
111
|
+
}
|
|
80
112
|
let stat;
|
|
81
113
|
try {
|
|
82
114
|
stat = fs_1.default.statSync(dirPath);
|
|
83
115
|
}
|
|
84
116
|
catch (e) {
|
|
117
|
+
warnStruct('[import] rejected', { reason: 'source-inaccessible', path: dirPath, detail: e.message });
|
|
85
118
|
return { error: 'source path inaccessible', path: dirPath, detail: e.message };
|
|
86
119
|
}
|
|
87
|
-
if (!stat.isDirectory())
|
|
120
|
+
if (!stat.isDirectory()) {
|
|
121
|
+
warnStruct('[import] rejected', { reason: 'source-not-directory', path: dirPath });
|
|
88
122
|
return { error: 'source path is not a directory', path: dirPath };
|
|
123
|
+
}
|
|
89
124
|
const files = fs_1.default.readdirSync(dirPath).filter(f => f.endsWith('.json') && !f.startsWith('_'));
|
|
90
125
|
entries = [];
|
|
91
126
|
for (const fname of files) {
|
|
@@ -95,14 +130,18 @@ function parseInlineEntries(rawEntries) {
|
|
|
95
130
|
if (parsed && typeof parsed === 'object' && parsed.id)
|
|
96
131
|
entries.push(parsed);
|
|
97
132
|
}
|
|
98
|
-
catch {
|
|
133
|
+
catch (e) {
|
|
134
|
+
warnStruct('[import] file skipped (parse error)', { file: fname, detail: e.message });
|
|
135
|
+
}
|
|
99
136
|
}
|
|
100
137
|
}
|
|
101
138
|
else {
|
|
102
139
|
entries = [];
|
|
103
140
|
}
|
|
104
|
-
if (!entries.length)
|
|
141
|
+
if (!entries.length) {
|
|
142
|
+
warnStruct('[import] rejected', { reason: 'no-entries' });
|
|
105
143
|
return { error: 'no entries' };
|
|
144
|
+
}
|
|
106
145
|
const dir = (0, indexContext_1.getInstructionsDir)();
|
|
107
146
|
if (!fs_1.default.existsSync(dir))
|
|
108
147
|
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
@@ -124,13 +163,16 @@ function parseInlineEntries(rawEntries) {
|
|
|
124
163
|
].filter((issue) => !!issue);
|
|
125
164
|
const surfaceValidation = e ? (0, instructionRecordValidation_1.validateInstructionInputSurface)(e) : { validationErrors: [], hints: [], schemaRef: 'index_add#input' };
|
|
126
165
|
if (!e || requiredFieldErrors.length || surfaceValidation.validationErrors.length) {
|
|
127
|
-
|
|
166
|
+
const errMsg = formatImportValidationError([...requiredFieldErrors, ...surfaceValidation.validationErrors]);
|
|
167
|
+
errors.push({ id, error: errMsg });
|
|
168
|
+
warnStruct('[import] entry rejected', { id, reason: 'invalid-input', error: errMsg });
|
|
128
169
|
continue;
|
|
129
170
|
}
|
|
130
171
|
const bodyTrimmed = typeof e.body === 'string' ? e.body.trim() : String(e.body);
|
|
131
172
|
const { bodyWarnLength: importBodyMax } = (0, runtimeConfig_1.getRuntimeConfig)().index;
|
|
132
173
|
if (bodyTrimmed.length > importBodyMax) {
|
|
133
174
|
errors.push({ id: e.id, error: `body_too_large: ${bodyTrimmed.length} chars exceeds ${importBodyMax} limit. Split into cross-linked instructions.` });
|
|
175
|
+
warnStruct('[import] entry rejected', { id: e.id, reason: 'body-too-large', length: bodyTrimmed.length, limit: importBodyMax });
|
|
134
176
|
continue;
|
|
135
177
|
}
|
|
136
178
|
const file = path_1.default.join(dir, `${e.id}.json`);
|
|
@@ -143,6 +185,7 @@ function parseInlineEntries(rawEntries) {
|
|
|
143
185
|
if (!categories.length) {
|
|
144
186
|
if (instructionsCfg.requireCategory) {
|
|
145
187
|
errors.push({ id: e.id, error: 'category_required' });
|
|
188
|
+
warnStruct('[import] entry rejected', { id: e.id, reason: 'category-required' });
|
|
146
189
|
continue;
|
|
147
190
|
}
|
|
148
191
|
categories = ['uncategorized'];
|
|
@@ -168,10 +211,12 @@ function parseInlineEntries(rawEntries) {
|
|
|
168
211
|
}
|
|
169
212
|
if (e.priorityTier === 'P1' && (!categories.length || !e.owner)) {
|
|
170
213
|
errors.push({ id: e.id, error: 'P1 requires category & owner' });
|
|
214
|
+
warnStruct('[import] entry rejected', { id: e.id, reason: 'p1-requires-category-and-owner' });
|
|
171
215
|
continue;
|
|
172
216
|
}
|
|
173
217
|
if ((e.requirement === 'mandatory' || e.requirement === 'critical') && !e.owner) {
|
|
174
218
|
errors.push({ id: e.id, error: 'mandatory/critical require owner' });
|
|
219
|
+
warnStruct('[import] entry rejected', { id: e.id, reason: 'mandatory-critical-require-owner', requirement: e.requirement });
|
|
175
220
|
continue;
|
|
176
221
|
}
|
|
177
222
|
if (fileExists && mode === 'skip') {
|
|
@@ -194,7 +239,9 @@ function parseInlineEntries(rawEntries) {
|
|
|
194
239
|
}
|
|
195
240
|
const recordValidation = (0, instructionRecordValidation_1.validateInstructionRecord)(record);
|
|
196
241
|
if (recordValidation.validationErrors.length) {
|
|
197
|
-
|
|
242
|
+
const errMsg = formatImportValidationError(recordValidation.validationErrors);
|
|
243
|
+
errors.push({ id: e.id, error: errMsg });
|
|
244
|
+
warnStruct('[import] entry rejected', { id: e.id, reason: 'record-validation-failed', error: errMsg });
|
|
198
245
|
continue;
|
|
199
246
|
}
|
|
200
247
|
try {
|
|
@@ -202,10 +249,14 @@ function parseInlineEntries(rawEntries) {
|
|
|
202
249
|
}
|
|
203
250
|
catch (err) {
|
|
204
251
|
if ((0, instructionRecordValidation_2.isInstructionValidationError)(err)) {
|
|
205
|
-
|
|
252
|
+
const errMsg = formatImportValidationError(err.validationErrors);
|
|
253
|
+
errors.push({ id: e.id, error: errMsg });
|
|
254
|
+
(0, logger_1.logError)('[import] entry write rejected', { id: e.id, reason: 'validation-failed-at-write', error: errMsg });
|
|
206
255
|
continue;
|
|
207
256
|
}
|
|
208
|
-
|
|
257
|
+
const writeMsg = err.message || 'unknown';
|
|
258
|
+
errors.push({ id: e.id, error: `write-failed: ${writeMsg}` });
|
|
259
|
+
(0, logger_1.logError)('[import] entry write failed', { id: e.id, error: writeMsg, stack: err.stack });
|
|
209
260
|
continue;
|
|
210
261
|
}
|
|
211
262
|
if (fileExists && mode === 'overwrite')
|
|
@@ -229,10 +280,17 @@ function parseInlineEntries(rawEntries) {
|
|
|
229
280
|
if (verificationErrors.length) {
|
|
230
281
|
errors.push(...verificationErrors);
|
|
231
282
|
(0, auditLog_1.logAudit)('import_verification', verificationErrors.map(v => v.id), { missingAfterReload: verificationErrors.length });
|
|
283
|
+
(0, logger_1.logError)('[import] verification failed', { missingAfterReload: verificationErrors.length, ids: verificationErrors.map(v => v.id) });
|
|
232
284
|
}
|
|
233
285
|
const verifiedCount = writtenIds.length - verificationErrors.length;
|
|
234
286
|
const summary = { hash: st.hash, imported, skipped, overwritten, total: entries.length, errors, verified: verificationErrors.length === 0, verifiedCount, verificationErrorCount: verificationErrors.length };
|
|
235
287
|
(0, auditLog_1.logAudit)('import', entries.map(e => e.id), { imported, skipped, overwritten, errors: errors.length, verified: verificationErrors.length === 0 });
|
|
288
|
+
if (errors.length) {
|
|
289
|
+
warnStruct('[import] complete with errors', { imported, skipped, overwritten, total: entries.length, errorCount: errors.length, verifiedCount, verificationErrorCount: verificationErrors.length, errorIds: errors.map(e => e.id) });
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
(0, logger_1.logInfo)('[import] complete', { imported, skipped, overwritten, total: entries.length, verifiedCount });
|
|
293
|
+
}
|
|
236
294
|
(0, manifestManager_1.attemptManifestUpdate)();
|
|
237
295
|
return summary;
|
|
238
296
|
}));
|
|
@@ -76,6 +76,87 @@ exports.FLAG_REGISTRY = [
|
|
|
76
76
|
{ name: 'INDEX_SERVER_LEADER_PORT', category: 'multi-instance', description: 'HTTP port for leader MCP transport (thin clients connect here).', stability: 'experimental', default: '9090', type: 'number', since: '1.8.5' },
|
|
77
77
|
{ name: 'INDEX_SERVER_HEARTBEAT_MS', category: 'multi-instance', description: 'Leader heartbeat interval (ms).', stability: 'experimental', default: '5000', type: 'number', since: '1.8.5' },
|
|
78
78
|
{ name: 'INDEX_SERVER_STALE_THRESHOLD_MS', category: 'multi-instance', description: 'Stale leader threshold (ms) before follower promotes.', stability: 'experimental', default: '15000', type: 'number', since: '1.8.5' },
|
|
79
|
+
// Semantic / embeddings
|
|
80
|
+
{ name: 'INDEX_SERVER_SEMANTIC_ENABLED', category: 'semantic', description: 'Enable semantic search & embedding compute.', stability: 'experimental', default: 'off', type: 'boolean', since: '1.20.0' },
|
|
81
|
+
{ name: 'INDEX_SERVER_SEMANTIC_MODEL', category: 'semantic', description: 'HuggingFace embedding model id (e.g. Xenova/all-MiniLM-L6-v2).', stability: 'experimental', default: '(default)', type: 'string', since: '1.20.0' },
|
|
82
|
+
{ name: 'INDEX_SERVER_SEMANTIC_DEVICE', category: 'semantic', description: 'Inference device: cpu | cuda | dml.', stability: 'experimental', default: 'cpu', type: 'string', since: '1.20.0' },
|
|
83
|
+
{ name: 'INDEX_SERVER_SEMANTIC_CACHE_DIR', category: 'semantic', description: 'Directory for downloaded model artifacts.', stability: 'experimental', default: './data/models', type: 'string', since: '1.20.0' },
|
|
84
|
+
{ name: 'INDEX_SERVER_SEMANTIC_LOCAL_ONLY', category: 'semantic', description: 'Disallow remote model downloads (offline mode).', stability: 'experimental', default: 'on', type: 'boolean', since: '1.20.0' },
|
|
85
|
+
{ name: 'INDEX_SERVER_EMBEDDING_PATH', category: 'semantic', description: 'Path to embeddings JSON cache file.', stability: 'experimental', default: './data/embeddings.json', type: 'string', since: '1.20.0' },
|
|
86
|
+
{ name: 'INDEX_SERVER_AUTO_EMBED_ON_IMPORT', category: 'semantic', description: 'Auto-compute embeddings after zip import / restore (when semantic enabled).', stability: 'experimental', default: 'on', type: 'boolean', since: '1.27.3' },
|
|
87
|
+
// Storage backend
|
|
88
|
+
{ name: 'INDEX_SERVER_STORAGE_BACKEND', category: 'storage', description: 'Instruction store backend: json (default) | sqlite (experimental).', stability: 'experimental', default: 'json', type: 'string', since: '1.25.0' },
|
|
89
|
+
{ name: 'INDEX_SERVER_SQLITE_PATH', category: 'storage', description: 'SQLite database file path.', stability: 'experimental', default: './data/index.db', type: 'string', since: '1.25.0' },
|
|
90
|
+
{ name: 'INDEX_SERVER_SQLITE_WAL', category: 'storage', description: 'Enable SQLite WAL journaling.', stability: 'experimental', default: 'on', type: 'boolean', since: '1.25.0' },
|
|
91
|
+
{ name: 'INDEX_SERVER_SQLITE_MIGRATE_ON_START', category: 'storage', description: 'Run schema migrations at startup.', stability: 'experimental', default: 'on', type: 'boolean', since: '1.25.0' },
|
|
92
|
+
{ name: 'INDEX_SERVER_SQLITE_VEC_ENABLED', category: 'storage', description: 'Enable sqlite-vec extension for embeddings.', stability: 'experimental', default: 'off', type: 'boolean', since: '1.25.0' },
|
|
93
|
+
{ name: 'INDEX_SERVER_SQLITE_VEC_PATH', category: 'storage', description: 'Path to sqlite-vec loadable extension.', stability: 'experimental', default: '(unset)', type: 'string', since: '1.25.0' },
|
|
94
|
+
// Feedback & messaging
|
|
95
|
+
{ name: 'INDEX_SERVER_FEEDBACK_DIR', category: 'feedback', description: 'Feedback storage directory.', stability: 'stable', default: './feedback', type: 'string', since: '1.10.0' },
|
|
96
|
+
{ name: 'INDEX_SERVER_FEEDBACK_MAX_ENTRIES', category: 'feedback', description: 'Maximum retained feedback entries.', stability: 'stable', default: '10000', type: 'number', since: '1.10.0' },
|
|
97
|
+
{ name: 'INDEX_SERVER_MESSAGING_DIR', category: 'messaging', description: 'Inter-agent messaging storage dir.', stability: 'experimental', default: './data/messaging', type: 'string', since: '1.18.0' },
|
|
98
|
+
{ name: 'INDEX_SERVER_MESSAGING_MAX', category: 'messaging', description: 'Max retained messages.', stability: 'experimental', default: '5000', type: 'number', since: '1.18.0' },
|
|
99
|
+
{ name: 'INDEX_SERVER_MESSAGING_SWEEP_MS', category: 'messaging', description: 'Messaging sweep interval (ms).', stability: 'experimental', default: '60000', type: 'number', since: '1.18.0' },
|
|
100
|
+
// Dashboard / TLS
|
|
101
|
+
{ name: 'INDEX_SERVER_DASHBOARD_TLS', category: 'dashboard', description: 'Enable HTTPS for the admin dashboard.', stability: 'stable', default: 'off', type: 'boolean', since: '1.20.0' },
|
|
102
|
+
{ name: 'INDEX_SERVER_DASHBOARD_TLS_CERT', category: 'dashboard', description: 'TLS certificate path.', stability: 'stable', default: '(unset)', type: 'string', since: '1.20.0' },
|
|
103
|
+
{ name: 'INDEX_SERVER_DASHBOARD_TLS_KEY', category: 'dashboard', description: 'TLS private key path.', stability: 'stable', default: '(unset)', type: 'string', since: '1.20.0' },
|
|
104
|
+
{ name: 'INDEX_SERVER_DASHBOARD_TLS_CA', category: 'dashboard', description: 'Optional TLS CA bundle path.', stability: 'stable', default: '(unset)', type: 'string', since: '1.20.0' },
|
|
105
|
+
{ name: 'INDEX_SERVER_DASHBOARD_GRAPH', category: 'dashboard', description: 'Enable dashboard graph rendering.', stability: 'stable', default: 'off', type: 'boolean', since: '1.18.0' },
|
|
106
|
+
{ name: 'INDEX_SERVER_HTTP_METRICS', category: 'dashboard', description: 'Expose Prometheus-style HTTP metrics.', stability: 'stable', default: 'on', type: 'boolean', since: '1.20.0' },
|
|
107
|
+
{ name: 'INDEX_SERVER_REQUEST_TIMEOUT', category: 'dashboard', description: 'HTTP request timeout (ms).', stability: 'stable', default: '30000', type: 'number', since: '1.20.0' },
|
|
108
|
+
{ name: 'INDEX_SERVER_MAX_CONNECTIONS', category: 'dashboard', description: 'Max concurrent HTTP connections.', stability: 'stable', default: '100', type: 'number', since: '1.20.0' },
|
|
109
|
+
{ name: 'INDEX_SERVER_ADMIN_API_KEY', category: 'auth', description: 'Bearer token for admin endpoints.', stability: 'stable', default: '(unset)', type: 'string', since: '1.20.0' },
|
|
110
|
+
{ name: 'INDEX_SERVER_ADMIN_MAX_SESSION_HISTORY', category: 'dashboard', description: 'Max retained admin session history.', stability: 'stable', default: '500', type: 'number', since: '1.20.0' },
|
|
111
|
+
{ name: 'INDEX_SERVER_BACKUPS_DIR', category: 'dashboard', description: 'Directory for backup zips.', stability: 'stable', default: './backups', type: 'string', since: '1.20.0' },
|
|
112
|
+
{ name: 'INDEX_SERVER_STATE_DIR', category: 'dashboard', description: 'Persistent dashboard state directory.', stability: 'stable', default: './data/state', type: 'string', since: '1.20.0' },
|
|
113
|
+
// Backup / mutation safety
|
|
114
|
+
{ name: 'INDEX_SERVER_AUTO_BACKUP', category: 'index', description: 'Auto-create backup before risky mutations.', stability: 'stable', default: 'on', type: 'boolean', since: '1.20.0' },
|
|
115
|
+
{ name: 'INDEX_SERVER_AUTO_BACKUP_INTERVAL_MS', category: 'index', description: 'Auto-backup interval ms.', stability: 'stable', default: '3600000', type: 'number', since: '1.20.0' },
|
|
116
|
+
{ name: 'INDEX_SERVER_AUTO_BACKUP_MAX_COUNT', category: 'index', description: 'Max retained auto-backups.', stability: 'stable', default: '10', type: 'number', since: '1.20.0' },
|
|
117
|
+
{ name: 'INDEX_SERVER_BACKUP_BEFORE_BULK_DELETE', category: 'index', description: 'Snapshot before bulk delete.', stability: 'stable', default: 'on', type: 'boolean', since: '1.20.0' },
|
|
118
|
+
{ name: 'INDEX_SERVER_MAX_BULK_DELETE', category: 'index', description: 'Max ids per bulk delete call.', stability: 'stable', default: '1000', type: 'number', since: '1.20.0' },
|
|
119
|
+
{ name: 'INDEX_SERVER_BODY_WARN_LENGTH', category: 'index', description: 'Warn-then-reject threshold for instruction body length.', stability: 'stable', default: '50000', type: 'number', since: '1.20.0' },
|
|
120
|
+
{ name: 'INDEX_SERVER_AUTO_SPLIT_OVERSIZED', category: 'index', description: 'Auto-split oversized instruction bodies on add.', stability: 'experimental', default: 'off', type: 'boolean', since: '1.20.0' },
|
|
121
|
+
{ name: 'INDEX_SERVER_AUTO_USAGE_TRACK', category: 'usage', description: 'Auto-track usage for tool invocations.', stability: 'stable', default: 'on', type: 'boolean', since: '1.20.0' },
|
|
122
|
+
// Bootstrap & seed
|
|
123
|
+
{ name: 'INDEX_SERVER_AUTO_SEED', category: 'bootstrap', description: 'Auto-seed canonical bootstrap instructions on first start.', stability: 'stable', default: 'on', type: 'boolean', since: '1.10.0' },
|
|
124
|
+
{ name: 'INDEX_SERVER_SEED_VERBOSE', category: 'bootstrap', description: 'Verbose seed-bootstrap logging.', stability: 'diagnostic', default: 'off', type: 'boolean', since: '1.10.0' },
|
|
125
|
+
{ name: 'INDEX_SERVER_BOOTSTRAP_AUTOCONFIRM', category: 'bootstrap', description: 'Auto-confirm bootstrap (skip token prompt).', stability: 'stable', default: 'off', type: 'boolean', since: '1.10.0' },
|
|
126
|
+
{ name: 'INDEX_SERVER_BOOTSTRAP_TOKEN_TTL_SEC', category: 'bootstrap', description: 'Bootstrap token TTL (seconds).', stability: 'stable', default: '600', type: 'number', since: '1.10.0' },
|
|
127
|
+
// Logging surface
|
|
128
|
+
{ name: 'INDEX_SERVER_LOG_FILE', category: 'core', description: 'NDJSON log file path (or 1 to use default).', stability: 'stable', default: '(unset)', type: 'string', since: '1.0.0' },
|
|
129
|
+
{ name: 'INDEX_SERVER_LOG_LEVEL', category: 'core', description: 'Minimum log level: trace|debug|info|warn|error.', stability: 'stable', default: 'info', type: 'string', since: '1.20.0' },
|
|
130
|
+
{ name: 'INDEX_SERVER_LOG_JSON', category: 'core', description: '(Reserved) Force JSON log mode.', stability: 'reserved', default: 'off', type: 'boolean', since: '1.0.0' },
|
|
131
|
+
{ name: 'INDEX_SERVER_LOG_SYNC', category: 'diagnostics', description: 'Fsync after each log write (test determinism).', stability: 'diagnostic', default: 'off', type: 'boolean', since: '1.0.0' },
|
|
132
|
+
{ name: 'INDEX_SERVER_LOG_PROTOCOL', category: 'diagnostics', description: 'Log MCP protocol frames.', stability: 'diagnostic', default: 'off', type: 'boolean', since: '1.0.0' },
|
|
133
|
+
{ name: 'INDEX_SERVER_EVENT_BUFFER_SIZE', category: 'diagnostics', description: 'Capacity of in-memory WARN/ERROR ring buffer surfaced in dashboard Events panel.', stability: 'stable', default: '500', type: 'number', since: '1.27.3' },
|
|
134
|
+
{ name: 'INDEX_SERVER_EVENT_SILENT', category: 'diagnostics', description: 'Suppress redundant index-event logs.', stability: 'diagnostic', default: 'off', type: 'boolean', since: '1.20.0' },
|
|
135
|
+
// Profile / dev mode
|
|
136
|
+
{ name: 'INDEX_SERVER_PROFILE', category: 'core', description: 'Runtime profile selector (default | dev | prod).', stability: 'stable', default: 'default', type: 'string', since: '1.20.0' },
|
|
137
|
+
// Operationally-meaningful additions surfaced by the catalog drift test.
|
|
138
|
+
{ name: 'INDEX_SERVER_BODY_MAX_LENGTH', category: 'index', description: 'Hard reject threshold for instruction body length (bytes).', stability: 'stable', default: '(unset)', type: 'number', since: '1.20.0' },
|
|
139
|
+
{ name: 'INDEX_SERVER_MAX_FILES', category: 'index', description: 'Soft cap on number of indexed instruction files.', stability: 'stable', default: '(unset)', type: 'number', since: '1.20.0' },
|
|
140
|
+
{ name: 'INDEX_SERVER_LOAD_WARN_MS', category: 'diagnostics', description: 'Emit WARN if initial index load exceeds this many ms.', stability: 'diagnostic', default: '(unset)', type: 'number', since: '1.20.0' },
|
|
141
|
+
{ name: 'INDEX_SERVER_AGENT_ID', category: 'core', description: 'Logical agent identity for audit/attestation trailers.', stability: 'stable', default: '(unset)', type: 'string', since: '1.20.0' },
|
|
142
|
+
{ name: 'INDEX_SERVER_FLAGS_FILE', category: 'core', description: 'Path to feature-flag persistence file.', stability: 'stable', default: './flags.json', type: 'string', since: '1.10.0' },
|
|
143
|
+
{ name: 'INDEX_SERVER_HEALTH_MEMORY_THRESHOLD', category: 'diagnostics', description: 'Memory threshold (bytes) for /health degraded status.', stability: 'diagnostic', default: '(unset)', type: 'number', since: '1.20.0' },
|
|
144
|
+
{ name: 'INDEX_SERVER_HEALTH_ERROR_THRESHOLD', category: 'diagnostics', description: 'Error-rate threshold for /health degraded status.', stability: 'diagnostic', default: '(unset)', type: 'number', since: '1.20.0' },
|
|
145
|
+
{ name: 'INDEX_SERVER_HEALTH_MIN_UPTIME', category: 'diagnostics', description: 'Minimum uptime (s) before /health reports healthy.', stability: 'diagnostic', default: '(unset)', type: 'number', since: '1.20.0' },
|
|
146
|
+
{ name: 'INDEX_SERVER_RESOURCE_CAPACITY', category: 'diagnostics', description: 'In-memory resource sample buffer capacity.', stability: 'diagnostic', default: '(unset)', type: 'number', since: '1.20.0' },
|
|
147
|
+
{ name: 'INDEX_SERVER_RESOURCE_SAMPLE_INTERVAL_MS', category: 'diagnostics', description: 'Resource sampler interval (ms).', stability: 'diagnostic', default: '(unset)', type: 'number', since: '1.20.0' },
|
|
148
|
+
{ name: 'INDEX_SERVER_TOOLCALL_CHUNK_SIZE', category: 'diagnostics', description: 'Tool-call ring buffer chunk size.', stability: 'diagnostic', default: '(unset)', type: 'number', since: '1.20.0' },
|
|
149
|
+
{ name: 'INDEX_SERVER_TOOLCALL_FLUSH_MS', category: 'diagnostics', description: 'Tool-call ring buffer flush interval (ms).', stability: 'diagnostic', default: '(unset)', type: 'number', since: '1.20.0' },
|
|
150
|
+
{ name: 'INDEX_SERVER_TOOLCALL_COMPACT_MS', category: 'diagnostics', description: 'Tool-call ring buffer compaction interval (ms).', stability: 'diagnostic', default: '(unset)', type: 'number', since: '1.20.0' },
|
|
151
|
+
{ name: 'INDEX_SERVER_TOOLCALL_APPEND_LOG', category: 'diagnostics', description: 'Append-only tool-call log path.', stability: 'diagnostic', default: '(unset)', type: 'string', since: '1.20.0' },
|
|
152
|
+
{ name: 'INDEX_SERVER_AUDIT_LOG', category: 'diagnostics', description: 'Audit log file path (mutation operations).', stability: 'diagnostic', default: '(unset)', type: 'string', since: '1.10.0' },
|
|
153
|
+
{ name: 'INDEX_SERVER_NORMALIZATION_LOG', category: 'diagnostics', description: 'Normalization log file path.', stability: 'diagnostic', default: '(unset)', type: 'string', since: '1.10.0' },
|
|
154
|
+
{ name: 'INDEX_SERVER_TRACE', category: 'diagnostics', description: 'Enable verbose trace logging.', stability: 'diagnostic', default: 'off', type: 'boolean', since: '1.0.0' },
|
|
155
|
+
{ name: 'INDEX_SERVER_TIMING_JSON', category: 'diagnostics', description: 'Emit timing data as JSON-NDJSON.', stability: 'diagnostic', default: 'off', type: 'boolean', since: '1.20.0' },
|
|
156
|
+
{ name: 'INDEX_SERVER_MINIMAL_DEBUG', category: 'diagnostics', description: 'Minimal debug surface (reduces verbosity).', stability: 'diagnostic', default: 'off', type: 'boolean', since: '1.20.0' },
|
|
157
|
+
{ name: 'INDEX_SERVER_STRESS_MODE', category: 'diagnostics', description: 'Enable stress-test instrumentation.', stability: 'diagnostic', default: 'off', type: 'boolean', since: '1.20.0' },
|
|
158
|
+
{ name: 'INDEX_SERVER_GRAPH_INCLUDE_PRIMARY_EDGES', category: 'index', description: 'Include primary edges in graph export.', stability: 'experimental', default: 'on', type: 'boolean', since: '1.18.0' },
|
|
159
|
+
{ name: 'INDEX_SERVER_GRAPH_LARGE_CATEGORY_CAP', category: 'index', description: 'Cap large-category fanout in graph export.', stability: 'experimental', default: '(unset)', type: 'number', since: '1.18.0' },
|
|
79
160
|
];
|
|
80
161
|
function parseValue(meta) {
|
|
81
162
|
const raw = process.env[meta.name];
|
|
@@ -71,7 +71,7 @@ function buildMinimalExample() {
|
|
|
71
71
|
categories: ["example", "documentation"],
|
|
72
72
|
primaryCategory: "example",
|
|
73
73
|
contentType: "instruction",
|
|
74
|
-
schemaVersion: "
|
|
74
|
+
schemaVersion: "5"
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
@@ -89,7 +89,7 @@ function _buildComprehensiveExample() {
|
|
|
89
89
|
categories: ["example", "documentation", "governance"],
|
|
90
90
|
primaryCategory: "governance",
|
|
91
91
|
contentType: "instruction",
|
|
92
|
-
schemaVersion: "
|
|
92
|
+
schemaVersion: "5",
|
|
93
93
|
version: "1.0.0",
|
|
94
94
|
status: "approved",
|
|
95
95
|
owner: "platform-team",
|
|
@@ -170,8 +170,8 @@ function defineValidationRules() {
|
|
|
170
170
|
{ field: 'requirement', rule: 'Enum', constraint: 'One of: mandatory, critical, recommended, optional, deprecated' },
|
|
171
171
|
{ field: 'categories', rule: 'Array', constraint: '0-25 items, each 1-49 chars, lowercase, pattern: ^[a-z0-9][a-z0-9-_]{0,48}$' },
|
|
172
172
|
{ field: 'primaryCategory', rule: 'Reference', constraint: 'Must be a member of categories array if present' },
|
|
173
|
-
{ field: 'contentType', rule: 'Enum', constraint: 'One of: instruction (default), template,
|
|
174
|
-
{ field: 'schemaVersion', rule: 'Enum', constraint: 'Currently "
|
|
173
|
+
{ field: 'contentType', rule: 'Enum', constraint: 'One of: instruction (default), template, workflow, reference, example, agent' },
|
|
174
|
+
{ field: 'schemaVersion', rule: 'Enum', constraint: 'Currently "5"' },
|
|
175
175
|
{ field: 'sourceHash', rule: 'Pattern', constraint: 'SHA256 hex string (64 chars) when present' },
|
|
176
176
|
{ field: 'version', rule: 'Pattern', constraint: 'Semantic version: ^\\d+\\.\\d+\\.\\d+$ (e.g., "1.0.0")' },
|
|
177
177
|
{ field: 'status', rule: 'Enum', constraint: 'One of: draft, review, approved, deprecated' },
|
|
@@ -41,7 +41,7 @@ const SEARCH_SCHEMA = {
|
|
|
41
41
|
limit: { type: 'number', minimum: 1, maximum: 100, default: 50 },
|
|
42
42
|
includeCategories: { type: 'boolean', default: false },
|
|
43
43
|
caseSensitive: { type: 'boolean', default: false },
|
|
44
|
-
contentType: { type: 'string', enum: ['instruction', 'template', '
|
|
44
|
+
contentType: { type: 'string', enum: ['instruction', 'template', 'workflow', 'reference', 'example', 'agent'] }
|
|
45
45
|
},
|
|
46
46
|
example: { keywords: ['build', 'validate', 'discipline'], limit: 10, includeCategories: true }
|
|
47
47
|
};
|
|
@@ -394,7 +394,7 @@ function performSearch(params) {
|
|
|
394
394
|
const caseSensitive = params.caseSensitive ?? false;
|
|
395
395
|
const contentType = params.contentType;
|
|
396
396
|
// Validate contentType if provided
|
|
397
|
-
const validContentTypes = ['instruction', 'template', '
|
|
397
|
+
const validContentTypes = ['instruction', 'template', 'workflow', 'reference', 'example', 'agent'];
|
|
398
398
|
if (contentType && !validContentTypes.includes(contentType)) {
|
|
399
399
|
throw new Error(`Invalid contentType: must be one of ${validContentTypes.join(', ')}`);
|
|
400
400
|
}
|
|
@@ -556,7 +556,7 @@ async function handleInstructionsSearch(params) {
|
|
|
556
556
|
if (typeof params.contentType !== 'string') {
|
|
557
557
|
throw new Error('contentType must be a string');
|
|
558
558
|
}
|
|
559
|
-
const validContentTypes = ['instruction', 'template', '
|
|
559
|
+
const validContentTypes = ['instruction', 'template', 'workflow', 'reference', 'example', 'agent'];
|
|
560
560
|
if (!validContentTypes.includes(params.contentType)) {
|
|
561
561
|
throw new Error(`contentType must be one of: ${validContentTypes.join(', ')}`);
|
|
562
562
|
}
|
|
@@ -49,11 +49,29 @@ declare const invariantRepairLog: {
|
|
|
49
49
|
field: string;
|
|
50
50
|
source: string;
|
|
51
51
|
}[];
|
|
52
|
+
/**
|
|
53
|
+
* Test-only hook — reset process-scoped latches between vitest specs.
|
|
54
|
+
* @internal Not part of the public API.
|
|
55
|
+
*/
|
|
56
|
+
export declare function _resetIndexContextProcessLatches(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Test-only hook — reset the module-scoped index state cache.
|
|
59
|
+
* @internal Not part of the public API.
|
|
60
|
+
*/
|
|
61
|
+
export declare function _resetIndexContextStateForTests(): void;
|
|
52
62
|
/** Returns a summary of invariant repairs for health check visibility. */
|
|
53
63
|
export declare function getInvariantRepairSummary(): {
|
|
54
64
|
totalRepairs: number;
|
|
55
65
|
recentRepairs: typeof invariantRepairLog;
|
|
56
66
|
};
|
|
67
|
+
declare function restoreFirstSeenInvariant(e: InstructionEntry): void;
|
|
68
|
+
/**
|
|
69
|
+
* Internal handles for unit tests only. Not part of the public API.
|
|
70
|
+
* @internal
|
|
71
|
+
*/
|
|
72
|
+
export declare const _internal: {
|
|
73
|
+
restoreFirstSeenInvariant: typeof restoreFirstSeenInvariant;
|
|
74
|
+
};
|
|
57
75
|
export declare function clearUsageRateLimit(id?: string): void;
|
|
58
76
|
export declare function loadUsageSnapshot(): Record<string, UsagePersistRecord>;
|
|
59
77
|
export declare function getInstructionsDir(): string;
|