@danielblomma/cortex-mcp 2.2.5 → 2.4.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.
Files changed (196) hide show
  1. package/CHANGELOG.md +201 -0
  2. package/README.md +73 -3
  3. package/bin/cortex.mjs +339 -242
  4. package/bin/daemon-control.mjs +162 -0
  5. package/package.json +7 -3
  6. package/scaffold/mcp/dist/.cortex-build-hash +1 -0
  7. package/scaffold/mcp/dist/cli/enterprise-setup.js +134 -0
  8. package/scaffold/mcp/dist/cli/govern.js +937 -0
  9. package/scaffold/mcp/dist/cli/query.js +409 -0
  10. package/scaffold/mcp/dist/cli/run.js +295 -0
  11. package/scaffold/mcp/dist/cli/stage.js +308 -0
  12. package/scaffold/mcp/dist/cli/telemetry-test.js +141 -0
  13. package/scaffold/mcp/dist/cli/ungoverned-detector.js +133 -0
  14. package/scaffold/mcp/dist/contextEntities.js +282 -0
  15. package/scaffold/mcp/dist/core/audit/query.js +72 -0
  16. package/scaffold/mcp/dist/core/audit/writer.js +28 -0
  17. package/scaffold/mcp/dist/core/config.js +235 -0
  18. package/scaffold/mcp/dist/core/enterprise-host-identity.js +193 -0
  19. package/scaffold/mcp/dist/core/enterprise-identity.js +18 -0
  20. package/scaffold/mcp/dist/core/enterprise-rotation.js +158 -0
  21. package/scaffold/mcp/dist/core/govern-paths.js +21 -0
  22. package/scaffold/mcp/dist/core/index.js +14 -0
  23. package/scaffold/mcp/dist/core/license.js +278 -0
  24. package/scaffold/mcp/dist/core/policy/enforce.js +66 -0
  25. package/scaffold/mcp/dist/core/policy/injection.js +172 -0
  26. package/scaffold/mcp/dist/core/policy/store.js +179 -0
  27. package/scaffold/mcp/dist/core/rbac/check.js +30 -0
  28. package/scaffold/mcp/dist/core/secure-endpoint.js +25 -0
  29. package/scaffold/mcp/dist/core/telemetry/collector.js +285 -0
  30. package/scaffold/mcp/dist/core/telemetry/state-dir.js +31 -0
  31. package/scaffold/mcp/dist/core/validators/builtins.js +632 -0
  32. package/scaffold/mcp/dist/core/validators/config.js +44 -0
  33. package/scaffold/mcp/dist/core/validators/engine.js +120 -0
  34. package/scaffold/mcp/dist/core/validators/evaluators/code_comments.js +236 -0
  35. package/scaffold/mcp/dist/core/validators/evaluators/regex.js +116 -0
  36. package/scaffold/mcp/dist/core/workflow/artifact-io.js +103 -0
  37. package/scaffold/mcp/dist/core/workflow/capabilities.js +88 -0
  38. package/scaffold/mcp/dist/core/workflow/default-workflows.js +102 -0
  39. package/scaffold/mcp/dist/core/workflow/enforcement.js +164 -0
  40. package/scaffold/mcp/dist/core/workflow/envelope.js +132 -0
  41. package/scaffold/mcp/dist/core/workflow/index.js +11 -0
  42. package/scaffold/mcp/dist/core/workflow/mcp-tools.js +175 -0
  43. package/scaffold/mcp/dist/core/workflow/resolution.js +69 -0
  44. package/scaffold/mcp/dist/core/workflow/run-lifecycle.js +123 -0
  45. package/scaffold/mcp/dist/core/workflow/schemas.js +141 -0
  46. package/scaffold/mcp/dist/core/workflow/synced-capability-registry.js +60 -0
  47. package/scaffold/mcp/dist/core/workflow/synced-registry.js +60 -0
  48. package/scaffold/mcp/dist/daemon/capability-sync-checker.js +242 -0
  49. package/scaffold/mcp/dist/daemon/client.js +130 -0
  50. package/scaffold/mcp/dist/daemon/egress-proxy.js +288 -0
  51. package/scaffold/mcp/dist/daemon/global-host-events.js +222 -0
  52. package/scaffold/mcp/dist/daemon/heartbeat-pusher.js +116 -0
  53. package/scaffold/mcp/dist/daemon/heartbeat-tracker.js +165 -0
  54. package/scaffold/mcp/dist/daemon/host-events-pusher.js +249 -0
  55. package/scaffold/mcp/dist/daemon/main.js +449 -0
  56. package/scaffold/mcp/dist/daemon/paths.js +36 -0
  57. package/scaffold/mcp/dist/daemon/project-service-registry.js +78 -0
  58. package/scaffold/mcp/dist/daemon/protocol.js +8 -0
  59. package/scaffold/mcp/dist/daemon/server.js +180 -0
  60. package/scaffold/mcp/dist/daemon/skill-sync-checker.js +557 -0
  61. package/scaffold/mcp/dist/daemon/sync-checker.js +165 -0
  62. package/scaffold/mcp/dist/daemon/ungoverned-scanner.js +136 -0
  63. package/scaffold/mcp/dist/daemon/workflow-sync-checker.js +249 -0
  64. package/scaffold/mcp/dist/defaults.js +6 -0
  65. package/scaffold/mcp/dist/embed.js +627 -0
  66. package/scaffold/mcp/dist/embedScheduler.js +479 -0
  67. package/scaffold/mcp/dist/embeddings.js +167 -0
  68. package/scaffold/mcp/dist/enterprise/audit/push.js +66 -0
  69. package/scaffold/mcp/dist/enterprise/index.js +327 -0
  70. package/scaffold/mcp/dist/enterprise/model/deploy.js +27 -0
  71. package/scaffold/mcp/dist/enterprise/policy/sync.js +129 -0
  72. package/scaffold/mcp/dist/enterprise/privacy/boundary.js +184 -0
  73. package/scaffold/mcp/dist/enterprise/reviews/changed-files.js +33 -0
  74. package/scaffold/mcp/dist/enterprise/reviews/pattern-context.js +202 -0
  75. package/scaffold/mcp/dist/enterprise/reviews/policy-selection.js +46 -0
  76. package/scaffold/mcp/dist/enterprise/reviews/push.js +102 -0
  77. package/scaffold/mcp/dist/enterprise/reviews/trust-state.js +186 -0
  78. package/scaffold/mcp/dist/enterprise/telemetry/sync.js +57 -0
  79. package/scaffold/mcp/dist/enterprise/tools/enterprise.js +826 -0
  80. package/scaffold/mcp/dist/enterprise/tools/harness.js +40 -0
  81. package/scaffold/mcp/dist/enterprise/tools/walk.js +73 -0
  82. package/scaffold/mcp/dist/enterprise/violations/push.js +77 -0
  83. package/scaffold/mcp/dist/enterprise/workflow/push.js +44 -0
  84. package/scaffold/mcp/dist/enterprise/workflow/state.js +329 -0
  85. package/scaffold/mcp/dist/frontmatter.js +33 -0
  86. package/scaffold/mcp/dist/graph.js +769 -0
  87. package/scaffold/mcp/dist/graphCsv.js +55 -0
  88. package/scaffold/mcp/dist/graphMetrics.js +8 -0
  89. package/scaffold/mcp/dist/hooks/permission-request.js +89 -0
  90. package/scaffold/mcp/dist/hooks/post-tool-use.js +105 -0
  91. package/scaffold/mcp/dist/hooks/pre-compact.js +29 -0
  92. package/scaffold/mcp/dist/hooks/pre-tool-use.js +78 -0
  93. package/scaffold/mcp/dist/hooks/session-end.js +43 -0
  94. package/scaffold/mcp/dist/hooks/session-start.js +41 -0
  95. package/scaffold/mcp/dist/hooks/shared.js +194 -0
  96. package/scaffold/mcp/dist/hooks/stop.js +28 -0
  97. package/scaffold/mcp/dist/hooks/user-prompt-submit.js +33 -0
  98. package/scaffold/mcp/dist/impactPresentation.js +137 -0
  99. package/scaffold/mcp/dist/impactRanking.js +191 -0
  100. package/scaffold/mcp/dist/impactResponse.js +30 -0
  101. package/scaffold/mcp/dist/impactResults.js +105 -0
  102. package/scaffold/mcp/dist/impactSeed.js +20 -0
  103. package/scaffold/mcp/dist/impactTraversal.js +64 -0
  104. package/scaffold/mcp/dist/jsonl.js +77 -0
  105. package/scaffold/mcp/dist/loadGraph.js +759 -0
  106. package/scaffold/mcp/dist/lruCache.js +38 -0
  107. package/scaffold/mcp/dist/paths.js +97 -0
  108. package/scaffold/mcp/dist/patternEvidence.js +272 -0
  109. package/scaffold/mcp/dist/plugin.js +81 -0
  110. package/scaffold/mcp/dist/presets.js +78 -0
  111. package/scaffold/mcp/dist/relatedResponse.js +18 -0
  112. package/scaffold/mcp/dist/relatedTraversal.js +78 -0
  113. package/scaffold/mcp/dist/rules.js +23 -0
  114. package/scaffold/mcp/dist/search.js +212 -0
  115. package/scaffold/mcp/dist/searchCore.js +457 -0
  116. package/scaffold/mcp/dist/searchResults.js +230 -0
  117. package/scaffold/mcp/dist/server.js +317 -0
  118. package/scaffold/mcp/dist/types.js +1 -0
  119. package/scaffold/mcp/package-lock.json +336 -212
  120. package/scaffold/mcp/package.json +15 -6
  121. package/scaffold/mcp/src/cli/enterprise-setup.ts +82 -8
  122. package/scaffold/mcp/src/cli/govern.ts +137 -52
  123. package/scaffold/mcp/src/cli/query.ts +36 -2
  124. package/scaffold/mcp/src/cli/run.ts +53 -19
  125. package/scaffold/mcp/src/cli/stage.ts +8 -3
  126. package/scaffold/mcp/src/core/config.ts +9 -2
  127. package/scaffold/mcp/src/core/enterprise-host-identity.ts +259 -0
  128. package/scaffold/mcp/src/core/enterprise-identity.ts +20 -0
  129. package/scaffold/mcp/src/core/enterprise-rotation.ts +185 -0
  130. package/scaffold/mcp/src/core/govern-paths.ts +30 -0
  131. package/scaffold/mcp/src/core/license.ts +186 -17
  132. package/scaffold/mcp/src/core/secure-endpoint.ts +23 -0
  133. package/scaffold/mcp/src/core/workflow/enforcement.ts +8 -1
  134. package/scaffold/mcp/src/core/workflow/mcp-tools.ts +3 -0
  135. package/scaffold/mcp/src/core/workflow/resolution.ts +9 -1
  136. package/scaffold/mcp/src/core/workflow/synced-capability-registry.ts +15 -0
  137. package/scaffold/mcp/src/core/workflow/synced-registry.ts +15 -0
  138. package/scaffold/mcp/src/daemon/capability-sync-checker.ts +38 -2
  139. package/scaffold/mcp/src/daemon/egress-proxy.ts +14 -8
  140. package/scaffold/mcp/src/daemon/global-host-events.ts +288 -0
  141. package/scaffold/mcp/src/daemon/heartbeat-pusher.ts +4 -0
  142. package/scaffold/mcp/src/daemon/heartbeat-tracker.ts +2 -0
  143. package/scaffold/mcp/src/daemon/host-events-pusher.ts +5 -0
  144. package/scaffold/mcp/src/daemon/main.ts +99 -25
  145. package/scaffold/mcp/src/daemon/project-service-registry.ts +107 -0
  146. package/scaffold/mcp/src/daemon/skill-sync-checker.ts +368 -31
  147. package/scaffold/mcp/src/daemon/sync-checker.ts +4 -0
  148. package/scaffold/mcp/src/daemon/ungoverned-scanner.ts +57 -25
  149. package/scaffold/mcp/src/daemon/workflow-sync-checker.ts +41 -2
  150. package/scaffold/mcp/src/embed.ts +211 -10
  151. package/scaffold/mcp/src/enterprise/audit/push.ts +8 -0
  152. package/scaffold/mcp/src/enterprise/index.ts +1 -1
  153. package/scaffold/mcp/src/enterprise/policy/sync.ts +12 -0
  154. package/scaffold/mcp/src/enterprise/reviews/changed-files.ts +34 -0
  155. package/scaffold/mcp/src/enterprise/reviews/pattern-context.ts +231 -0
  156. package/scaffold/mcp/src/enterprise/reviews/push.ts +9 -0
  157. package/scaffold/mcp/src/enterprise/reviews/trust-state.ts +3 -1
  158. package/scaffold/mcp/src/enterprise/telemetry/sync.ts +9 -0
  159. package/scaffold/mcp/src/enterprise/tools/enterprise.ts +49 -36
  160. package/scaffold/mcp/src/enterprise/violations/push.ts +9 -0
  161. package/scaffold/mcp/src/enterprise/workflow/push.ts +7 -0
  162. package/scaffold/mcp/src/paths.ts +3 -5
  163. package/scaffold/mcp/src/patternEvidence.ts +347 -0
  164. package/scaffold/mcp/src/plugin.ts +20 -0
  165. package/scaffold/mcp/src/search.ts +35 -9
  166. package/scaffold/mcp/src/searchCore.ts +254 -12
  167. package/scaffold/mcp/src/searchResults.ts +96 -9
  168. package/scaffold/mcp/src/types.ts +7 -0
  169. package/scaffold/mcp/tests/changed-files.test.mjs +41 -0
  170. package/scaffold/mcp/tests/copilot-shim.test.mjs +19 -1
  171. package/scaffold/mcp/tests/egress-proxy.test.mjs +37 -0
  172. package/scaffold/mcp/tests/embed-entities.test.mjs +109 -1
  173. package/scaffold/mcp/tests/enterprise-identity-sync.test.mjs +401 -0
  174. package/scaffold/mcp/tests/enterprise-pattern-context.test.mjs +322 -0
  175. package/scaffold/mcp/tests/enterprise-setup.test.mjs +189 -0
  176. package/scaffold/mcp/tests/global-host-events.test.mjs +81 -0
  177. package/scaffold/mcp/tests/govern-install.test.mjs +95 -2
  178. package/scaffold/mcp/tests/govern-repair.test.mjs +20 -3
  179. package/scaffold/mcp/tests/heartbeat-tracker.test.mjs +26 -0
  180. package/scaffold/mcp/tests/license.test.mjs +367 -0
  181. package/scaffold/mcp/tests/paths.test.mjs +11 -3
  182. package/scaffold/mcp/tests/pattern-evidence.test.mjs +321 -0
  183. package/scaffold/mcp/tests/project-service-registry.test.mjs +172 -0
  184. package/scaffold/mcp/tests/query-cli.test.mjs +73 -1
  185. package/scaffold/mcp/tests/review-trust-contract.test.mjs +18 -0
  186. package/scaffold/mcp/tests/search-graph-score.test.mjs +167 -0
  187. package/scaffold/mcp/tests/secure-enterprise-endpoint.test.mjs +46 -0
  188. package/scaffold/mcp/tests/skill-sync-checker.test.mjs +446 -2
  189. package/scaffold/mcp/tests/ungoverned-scanner.test.mjs +104 -22
  190. package/scaffold/mcp/tests/workflow-cli.test.mjs +14 -1
  191. package/scaffold/mcp/tests/workflow-synced-capabilities.test.mjs +37 -0
  192. package/scaffold/mcp/tests/workflow-synced-registry.test.mjs +40 -1
  193. package/scaffold/scripts/dashboard.mjs +29 -18
  194. package/scaffold/scripts/doctor.sh +26 -8
  195. package/scaffold/scripts/ingest.mjs +37 -4
  196. package/scaffold/scripts/status.sh +13 -6
@@ -0,0 +1,66 @@
1
+ import { isAllowedEnterpriseEndpoint } from "../../core/secure-endpoint.js";
2
+ import { sanitizeAuditEntryForPush } from "../privacy/boundary.js";
3
+ const pending = [];
4
+ let activeContext = {};
5
+ export function setAuditPushContext(context) {
6
+ activeContext = { ...context };
7
+ }
8
+ export function queueAuditEvent(entry) {
9
+ pending.push(sanitizeAuditEntryForPush({
10
+ ...entry,
11
+ repo: entry.repo ?? activeContext.repo,
12
+ instance_id: entry.instance_id ?? activeContext.instance_id,
13
+ session_id: entry.session_id ?? activeContext.session_id,
14
+ }));
15
+ }
16
+ export function pendingCount() {
17
+ return pending.length;
18
+ }
19
+ export async function pushAuditEvents(baseUrl, apiKey) {
20
+ if (pending.length === 0) {
21
+ return { success: true, count: 0 };
22
+ }
23
+ if (!isAllowedEnterpriseEndpoint(baseUrl)) {
24
+ return {
25
+ success: false,
26
+ count: 0,
27
+ error: "insecure or invalid enterprise endpoint",
28
+ };
29
+ }
30
+ const auditUrl = `${baseUrl.replace(/\/$/, "")}/api/v1/audit/push`;
31
+ let pushedCount = 0;
32
+ while (pending.length > 0) {
33
+ const batch = pending.splice(0, 100);
34
+ try {
35
+ const response = await fetch(auditUrl, {
36
+ method: "POST",
37
+ headers: {
38
+ Authorization: `Bearer ${apiKey}`,
39
+ "Content-Type": "application/json",
40
+ Accept: "application/json",
41
+ },
42
+ body: JSON.stringify({
43
+ repo: activeContext.repo,
44
+ instance_id: activeContext.instance_id,
45
+ session_id: activeContext.session_id,
46
+ events: batch,
47
+ }),
48
+ signal: AbortSignal.timeout(10_000),
49
+ });
50
+ if (!response.ok) {
51
+ pending.unshift(...batch);
52
+ return { success: false, count: pushedCount, error: `HTTP ${response.status}` };
53
+ }
54
+ pushedCount += batch.length;
55
+ }
56
+ catch (err) {
57
+ pending.unshift(...batch);
58
+ return {
59
+ success: false,
60
+ count: pushedCount,
61
+ error: err instanceof Error ? err.message : "unknown error",
62
+ };
63
+ }
64
+ }
65
+ return { success: true, count: pushedCount };
66
+ }
@@ -0,0 +1,327 @@
1
+ import path from "node:path";
2
+ import { randomUUID } from "node:crypto";
3
+ import { createRequire } from "node:module";
4
+ import { loadEnterpriseConfig, resolveEnterpriseActivation, } from "../core/config.js";
5
+ import { deployBundledModel } from "./model/deploy.js";
6
+ import { TelemetryCollector } from "../core/telemetry/collector.js";
7
+ import { AuditWriter } from "../core/audit/writer.js";
8
+ import { pushAuditEvents, queueAuditEvent, setAuditPushContext } from "./audit/push.js";
9
+ import { PolicyStore } from "../core/policy/store.js";
10
+ import { syncFromCloud, syncFromLocal } from "./policy/sync.js";
11
+ import { registerEnterpriseTools } from "./tools/enterprise.js";
12
+ import { registerHarnessTools } from "./tools/harness.js";
13
+ import { pushViolations, setViolationPushContext } from "./violations/push.js";
14
+ import { pushReviewResults, setReviewPushContext } from "./reviews/push.js";
15
+ import { pushWorkflowSnapshot, setWorkflowPushContext } from "./workflow/push.js";
16
+ import { hasWorkflowState, loadWorkflowState } from "./workflow/state.js";
17
+ const require = createRequire(import.meta.url);
18
+ const pkg = require("../../package.json");
19
+ export const name = "cortex-enterprise";
20
+ export const version = pkg.version;
21
+ const timers = [];
22
+ let activeCollector = null;
23
+ let activeConfig = null;
24
+ let activeAuditWriter = null;
25
+ let activeInstanceId = null;
26
+ let activeSessionId = null;
27
+ let activeRepo = null;
28
+ let activeContextDir = null;
29
+ async function flushComplianceQueues(config, reason) {
30
+ const baseUrl = (config.enterprise.base_url || config.enterprise.endpoint).trim();
31
+ const apiKey = config.enterprise.api_key.trim();
32
+ if (!baseUrl || !apiKey)
33
+ return;
34
+ try {
35
+ const result = await pushAuditEvents(baseUrl, apiKey);
36
+ if (!result.success) {
37
+ process.stderr.write(`[cortex-enterprise] ${reason} audit push failed: ${result.error}\n`);
38
+ }
39
+ }
40
+ catch (err) {
41
+ process.stderr.write(`[cortex-enterprise] ${reason} audit push error: ${err}\n`);
42
+ }
43
+ try {
44
+ const result = await pushViolations(baseUrl, apiKey);
45
+ if (!result.success) {
46
+ process.stderr.write(`[cortex-enterprise] ${reason} violations push failed: ${result.error}\n`);
47
+ }
48
+ }
49
+ catch (err) {
50
+ process.stderr.write(`[cortex-enterprise] ${reason} violations push error: ${err}\n`);
51
+ }
52
+ try {
53
+ const result = await pushReviewResults(baseUrl, apiKey);
54
+ if (!result.success) {
55
+ process.stderr.write(`[cortex-enterprise] ${reason} reviews push failed: ${result.error}\n`);
56
+ }
57
+ if (result.attempted && activeContextDir && hasWorkflowState(activeContextDir)) {
58
+ const workflowState = loadWorkflowState(activeContextDir);
59
+ const workflowResult = await pushWorkflowSnapshot(baseUrl, apiKey, workflowState);
60
+ if (!workflowResult.success) {
61
+ process.stderr.write(`[cortex-enterprise] ${reason} workflow snapshot refresh after review push failed: ${workflowResult.error}\n`);
62
+ }
63
+ }
64
+ }
65
+ catch (err) {
66
+ process.stderr.write(`[cortex-enterprise] ${reason} reviews push error: ${err}\n`);
67
+ }
68
+ }
69
+ export function shutdown() {
70
+ for (const t of timers)
71
+ clearInterval(t);
72
+ timers.length = 0;
73
+ activeCollector = null;
74
+ activeConfig = null;
75
+ activeAuditWriter = null;
76
+ activeInstanceId = null;
77
+ activeSessionId = null;
78
+ activeRepo = null;
79
+ activeContextDir = null;
80
+ setAuditPushContext({});
81
+ setViolationPushContext({});
82
+ setReviewPushContext({});
83
+ setWorkflowPushContext({});
84
+ }
85
+ /**
86
+ * Telemetry hook called by cortex core after each tool execution.
87
+ * Wired up via the CortexPlugin.onToolCall interface.
88
+ */
89
+ export function onToolCall(toolName, resultCount, tokensSaved) {
90
+ activeCollector?.record(toolName, resultCount, tokensSaved);
91
+ }
92
+ export function onToolEvent(event) {
93
+ if (event.phase === "success" || event.phase === "error") {
94
+ activeCollector?.recordEvent({
95
+ tool: event.tool,
96
+ phase: event.phase,
97
+ result_count: event.result_count,
98
+ estimated_tokens_saved: event.estimated_tokens_saved,
99
+ duration_ms: event.duration_ms,
100
+ });
101
+ }
102
+ if ((event.phase === "success" || event.phase === "error") && activeAuditWriter) {
103
+ activeAuditWriter.log({
104
+ timestamp: event.timestamp,
105
+ tool: event.tool,
106
+ input: event.input,
107
+ result_count: event.result_count ?? 0,
108
+ entities_returned: event.entities_returned ?? [],
109
+ rules_applied: event.rules_applied ?? [],
110
+ duration_ms: event.duration_ms ?? 0,
111
+ status: event.phase,
112
+ error: event.error,
113
+ event_type: "tool_call",
114
+ evidence_level: "diagnostic",
115
+ resource_type: "context_tool",
116
+ repo: activeRepo ?? undefined,
117
+ instance_id: activeInstanceId ?? undefined,
118
+ session_id: activeSessionId ?? undefined,
119
+ metadata: event.query_length !== undefined
120
+ ? {
121
+ query_present: true,
122
+ query_length: event.query_length,
123
+ }
124
+ : undefined,
125
+ });
126
+ }
127
+ }
128
+ export function recordToolActivity(activity) {
129
+ const status = activity.status ?? "success";
130
+ activeAuditWriter?.log({
131
+ ...activity,
132
+ status,
133
+ repo: activity.repo ?? activeRepo ?? undefined,
134
+ instance_id: activity.instance_id ?? activeInstanceId ?? undefined,
135
+ session_id: activity.session_id ?? activeSessionId ?? undefined,
136
+ });
137
+ activeCollector?.recordEvent({
138
+ tool: activity.tool,
139
+ phase: status,
140
+ result_count: activity.result_count,
141
+ estimated_tokens_saved: activity.tokens_saved ?? 0,
142
+ duration_ms: activity.duration_ms,
143
+ });
144
+ }
145
+ /**
146
+ * Session-end hook called by cortex core on shutdown.
147
+ * Awaited with a timeout — this is the reliable telemetry push path.
148
+ */
149
+ export async function onSessionEnd() {
150
+ if (!activeConfig)
151
+ return;
152
+ const config = activeConfig;
153
+ // Telemetry push is owned by the daemon. MCP only persists in-memory
154
+ // metrics to disk so the daemon can pick them up on its next push tick.
155
+ if (config.telemetry.enabled && activeCollector) {
156
+ activeCollector.flush();
157
+ }
158
+ await flushComplianceQueues(config, "shutdown");
159
+ }
160
+ export async function onSessionEvent(event) {
161
+ if (event.phase === "start") {
162
+ activeCollector?.recordSessionStart();
163
+ return;
164
+ }
165
+ if (event.phase === "end") {
166
+ activeCollector?.recordSessionEnd(event.duration_ms ?? 0);
167
+ if (activeAuditWriter) {
168
+ activeAuditWriter.log({
169
+ timestamp: event.timestamp,
170
+ tool: "session.summary",
171
+ input: {
172
+ tool_calls: event.tool_calls ?? 0,
173
+ successful_tool_calls: event.successful_tool_calls ?? 0,
174
+ failed_tool_calls: event.failed_tool_calls ?? 0,
175
+ },
176
+ result_count: event.tool_calls ?? 0,
177
+ entities_returned: [],
178
+ rules_applied: [],
179
+ duration_ms: event.duration_ms ?? 0,
180
+ status: "success",
181
+ event_type: "session",
182
+ evidence_level: "diagnostic",
183
+ resource_type: "session",
184
+ repo: activeRepo ?? undefined,
185
+ instance_id: activeInstanceId ?? undefined,
186
+ session_id: activeSessionId ?? undefined,
187
+ });
188
+ }
189
+ }
190
+ }
191
+ export async function register(server) {
192
+ const projectRoot = process.env.CORTEX_PROJECT_ROOT?.trim() || process.cwd();
193
+ const contextDir = path.join(projectRoot, ".context");
194
+ activeContextDir = contextDir;
195
+ const config = loadEnterpriseConfig(contextDir);
196
+ const activation = resolveEnterpriseActivation(config);
197
+ if (!activation.active) {
198
+ process.stderr.write(`[cortex-enterprise] cloud features inactive: ${activation.reason}\n`);
199
+ }
200
+ activeConfig = config;
201
+ // Deploy bundled embedding model if not already cached
202
+ const modelDeployed = deployBundledModel(contextDir);
203
+ if (modelDeployed) {
204
+ process.stderr.write(`[cortex-enterprise] Bundled embedding model deployed\n`);
205
+ }
206
+ // Initialize subsystems
207
+ const collector = new TelemetryCollector(contextDir, version);
208
+ activeCollector = collector;
209
+ activeInstanceId = collector.getMetrics().instance_id;
210
+ activeSessionId = randomUUID();
211
+ activeRepo = path.basename(projectRoot);
212
+ const auditWriter = config.audit.enabled
213
+ ? new AuditWriter(contextDir, {
214
+ onEntry(entry) {
215
+ queueAuditEvent(entry);
216
+ },
217
+ })
218
+ : null;
219
+ activeAuditWriter = auditWriter;
220
+ const policyStore = new PolicyStore(contextDir);
221
+ setAuditPushContext({
222
+ repo: activeRepo ?? undefined,
223
+ instance_id: activeInstanceId ?? undefined,
224
+ session_id: activeSessionId ?? undefined,
225
+ });
226
+ setViolationPushContext({
227
+ repo: activeRepo ?? undefined,
228
+ instance_id: activeInstanceId ?? undefined,
229
+ session_id: activeSessionId ?? undefined,
230
+ });
231
+ setReviewPushContext({
232
+ repo: activeRepo ?? undefined,
233
+ instance_id: activeInstanceId ?? undefined,
234
+ session_id: activeSessionId ?? undefined,
235
+ context_dir: contextDir,
236
+ project_root: projectRoot,
237
+ });
238
+ setWorkflowPushContext({
239
+ repo: activeRepo ?? undefined,
240
+ instance_id: activeInstanceId ?? undefined,
241
+ session_id: activeSessionId ?? undefined,
242
+ });
243
+ // Initial policy sync
244
+ if (config.policy.enabled) {
245
+ if (config.policy.endpoint && config.policy.api_key) {
246
+ await syncFromCloud(config.policy.endpoint, config.policy.api_key, policyStore, {
247
+ instance_id: activeInstanceId ?? undefined,
248
+ session_id: activeSessionId ?? undefined,
249
+ });
250
+ process.stderr.write(`[cortex-enterprise] Policy sync: cloud\n`);
251
+ }
252
+ else {
253
+ syncFromLocal(policyStore);
254
+ const orgCount = policyStore.loadOrgPolicies().length;
255
+ if (orgCount > 0) {
256
+ process.stderr.write(`[cortex-enterprise] Policy sync: ${orgCount} org rules loaded\n`);
257
+ }
258
+ }
259
+ }
260
+ registerEnterpriseTools(server, collector, auditWriter, config, contextDir, policyStore, version);
261
+ // Cortex Harness MCP tools (cortex.workflow.*) — only registered for
262
+ // enterprise projects, since they depend on org-authored workflows
263
+ // synced from cortex-web (also enterprise-only).
264
+ registerHarnessTools(server);
265
+ // v2.0.0: globalThis.__cortexContextToolHook bridge removed.
266
+ // Enterprise is now in-process with cortex-mcp; tool events flow via
267
+ // plugin.ts's onToolEvent hook directly through activation.ts.
268
+ process.stderr.write(`[cortex-enterprise] v${version}\n`);
269
+ // Log active features
270
+ const features = [];
271
+ if (config.telemetry.enabled)
272
+ features.push("telemetry");
273
+ if (config.audit.enabled)
274
+ features.push("audit");
275
+ if (config.policy.enabled)
276
+ features.push("policy");
277
+ if (config.rbac.enabled)
278
+ features.push(`rbac(${config.rbac.default_role})`);
279
+ if (features.length > 0) {
280
+ process.stderr.write(`[cortex-enterprise] Active: ${features.join(", ")}\n`);
281
+ }
282
+ // Telemetry push is owned by the daemon (single network writer).
283
+ // MCP only persists in-memory metrics to disk on a tick so the daemon
284
+ // can read and push them.
285
+ if (config.telemetry.enabled) {
286
+ const intervalMs = config.telemetry.interval_minutes * 60000;
287
+ const timer = setInterval(() => {
288
+ try {
289
+ collector.flush();
290
+ }
291
+ catch (err) {
292
+ process.stderr.write(`[cortex-enterprise] Telemetry flush error: ${err}\n`);
293
+ }
294
+ }, intervalMs);
295
+ timer.unref();
296
+ timers.push(timer);
297
+ }
298
+ // Schedule compliance queue flushes independently from telemetry so
299
+ // policy evidence is still delivered when metrics collection is off.
300
+ if (config.policy.enabled && config.policy.endpoint && config.policy.api_key) {
301
+ const intervalMs = (config.telemetry.enabled
302
+ ? config.telemetry.interval_minutes
303
+ : config.policy.sync_interval_minutes) * 60000;
304
+ const timer = setInterval(async () => {
305
+ await flushComplianceQueues(config, "periodic");
306
+ }, intervalMs);
307
+ timer.unref();
308
+ timers.push(timer);
309
+ }
310
+ // Schedule policy sync
311
+ if (config.policy.enabled && config.policy.endpoint && config.policy.api_key) {
312
+ const intervalMs = config.policy.sync_interval_minutes * 60000;
313
+ const timer = setInterval(async () => {
314
+ try {
315
+ await syncFromCloud(config.policy.endpoint, config.policy.api_key, policyStore, {
316
+ instance_id: activeInstanceId ?? undefined,
317
+ session_id: activeSessionId ?? undefined,
318
+ });
319
+ }
320
+ catch (err) {
321
+ process.stderr.write(`[cortex-enterprise] Policy sync error: ${err}\n`);
322
+ }
323
+ }, intervalMs);
324
+ timer.unref();
325
+ timers.push(timer);
326
+ }
327
+ }
@@ -0,0 +1,27 @@
1
+ import { cpSync, existsSync, mkdirSync } from "node:fs";
2
+ import { join, dirname } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = dirname(__filename);
6
+ const BUNDLED_MODEL_DIR = join(__dirname, "..", "..", "models");
7
+ const MODEL_NAME = "Xenova/all-MiniLM-L6-v2";
8
+ /**
9
+ * If the project doesn't have the embedding model cached,
10
+ * copy it from the bundled models in this package.
11
+ * Returns true if a model was deployed, false if already present or no bundle.
12
+ */
13
+ export function deployBundledModel(contextDir) {
14
+ const targetDir = join(contextDir, "embeddings", "models", MODEL_NAME);
15
+ const sourceDir = join(BUNDLED_MODEL_DIR, MODEL_NAME);
16
+ // Already cached — nothing to do
17
+ if (existsSync(join(targetDir, "onnx", "model_quantized.onnx"))) {
18
+ return false;
19
+ }
20
+ // No bundled model in this package
21
+ if (!existsSync(join(sourceDir, "onnx", "model_quantized.onnx"))) {
22
+ return false;
23
+ }
24
+ mkdirSync(targetDir, { recursive: true });
25
+ cpSync(sourceDir, targetDir, { recursive: true });
26
+ return true;
27
+ }
@@ -0,0 +1,129 @@
1
+ import { z } from "zod";
2
+ import { isAllowedEnterpriseEndpoint } from "../../core/secure-endpoint.js";
3
+ const CloudPolicySchema = z.object({
4
+ id: z.string().min(1).max(200),
5
+ title: z.string().min(1).max(200).optional(),
6
+ kind: z.enum(["predefined", "custom"]).optional(),
7
+ status: z.enum(["draft", "active", "disabled", "archived"]).optional(),
8
+ severity: z.enum(["info", "warning", "error", "block"]).optional(),
9
+ description: z.string().max(1000).default(""),
10
+ priority: z.number().int().min(0).max(1000).default(50),
11
+ scope: z.string().max(200).default("global"),
12
+ enforce: z.boolean().default(true),
13
+ type: z.string().min(1).max(100).nullable().optional(),
14
+ config: z.record(z.string(), z.unknown()).nullable().optional(),
15
+ });
16
+ const CloudResponseSchema = z.object({
17
+ rules: z.array(CloudPolicySchema).default([]),
18
+ });
19
+ let lastSync = null;
20
+ export function getLastSync() {
21
+ return lastSync;
22
+ }
23
+ /**
24
+ * Pull org-wide policies from the Cortex Cloud API (connected edition).
25
+ * The actual cloud API is built in Phase 4 — this sends a GET
26
+ * and expects a JSON array of policy objects.
27
+ */
28
+ export async function syncFromCloud(endpoint, apiKey, store, context = {}) {
29
+ if (!endpoint || !apiKey) {
30
+ const result = {
31
+ success: false,
32
+ synced: 0,
33
+ source: "cloud",
34
+ timestamp: new Date().toISOString(),
35
+ error: "endpoint or api_key not configured",
36
+ };
37
+ lastSync = result;
38
+ return result;
39
+ }
40
+ if (!isAllowedEnterpriseEndpoint(endpoint)) {
41
+ const result = {
42
+ success: false,
43
+ synced: 0,
44
+ source: "cloud",
45
+ timestamp: new Date().toISOString(),
46
+ error: "insecure or invalid enterprise endpoint",
47
+ };
48
+ lastSync = result;
49
+ return result;
50
+ }
51
+ try {
52
+ const response = await fetch(endpoint, {
53
+ method: "GET",
54
+ headers: {
55
+ "Authorization": `Bearer ${apiKey}`,
56
+ "Accept": "application/json",
57
+ ...(context.instance_id
58
+ ? { "x-cortex-instance-id": context.instance_id }
59
+ : {}),
60
+ ...(context.session_id
61
+ ? { "x-cortex-session-id": context.session_id }
62
+ : {}),
63
+ },
64
+ signal: AbortSignal.timeout(10000),
65
+ });
66
+ if (!response.ok) {
67
+ const result = {
68
+ success: false,
69
+ synced: 0,
70
+ source: "cloud",
71
+ timestamp: new Date().toISOString(),
72
+ error: `HTTP ${response.status}`,
73
+ };
74
+ lastSync = result;
75
+ return result;
76
+ }
77
+ const raw = await response.json();
78
+ const data = CloudResponseSchema.parse(raw);
79
+ const policies = data.rules.map((r) => ({
80
+ id: r.id,
81
+ title: r.title ?? r.id,
82
+ kind: r.kind ?? null,
83
+ status: r.status ?? "active",
84
+ severity: r.severity ?? "block",
85
+ description: r.description,
86
+ priority: r.priority,
87
+ scope: r.scope,
88
+ enforce: r.enforce,
89
+ type: r.type ?? null,
90
+ config: r.config ?? null,
91
+ source: "org",
92
+ }));
93
+ store.writeOrgPolicies(policies);
94
+ const result = {
95
+ success: true,
96
+ synced: policies.length,
97
+ source: "cloud",
98
+ timestamp: new Date().toISOString(),
99
+ };
100
+ lastSync = result;
101
+ return result;
102
+ }
103
+ catch (err) {
104
+ const result = {
105
+ success: false,
106
+ synced: 0,
107
+ source: "cloud",
108
+ timestamp: new Date().toISOString(),
109
+ error: err instanceof Error ? err.message : "unknown error",
110
+ };
111
+ lastSync = result;
112
+ return result;
113
+ }
114
+ }
115
+ /**
116
+ * For air-gapped: just reload from local org-rules.yaml file.
117
+ * Returns the count of policies found.
118
+ */
119
+ export function syncFromLocal(store) {
120
+ const policies = store.loadOrgPolicies();
121
+ const result = {
122
+ success: true,
123
+ synced: policies.length,
124
+ source: "local",
125
+ timestamp: new Date().toISOString(),
126
+ };
127
+ lastSync = result;
128
+ return result;
129
+ }