@danielblomma/cortex-mcp 2.4.0 → 2.4.2
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 +226 -0
- package/README.md +24 -3
- package/bin/cli/arguments.mjs +60 -0
- package/bin/cli/context-passthrough.mjs +129 -0
- package/bin/cli/daemon.mjs +157 -0
- package/bin/cli/enterprise.mjs +516 -0
- package/bin/cli/help.mjs +88 -0
- package/bin/cli/hooks.mjs +199 -0
- package/bin/cli/mcp-command.mjs +87 -0
- package/bin/cli/paths.mjs +14 -0
- package/bin/cli/process.mjs +49 -0
- package/bin/cli/project-commands.mjs +237 -0
- package/bin/cli/project-runtime.mjs +34 -0
- package/bin/cli/query-command.mjs +18 -0
- package/bin/cli/router.mjs +66 -0
- package/bin/cli/run-command.mjs +44 -0
- package/bin/cli/scaffold-ownership.mjs +936 -0
- package/bin/cli/scaffold.mjs +687 -0
- package/bin/cli/stage-command.mjs +9 -0
- package/bin/cli/telemetry-command.mjs +18 -0
- package/bin/cli/trusted-runtime.mjs +18 -0
- package/bin/cortex.mjs +12 -1607
- package/bin/daemon-control.mjs +162 -0
- package/mcp-registry-submission.json +1 -1
- package/package.json +8 -3
- package/scaffold/mcp/dist/.cortex-build-hash +1 -0
- package/scaffold/mcp/dist/cli/enterprise-setup.js +134 -0
- package/scaffold/mcp/dist/cli/govern.js +937 -0
- package/scaffold/mcp/dist/cli/query.js +409 -0
- package/scaffold/mcp/dist/cli/run.js +295 -0
- package/scaffold/mcp/dist/cli/stage.js +308 -0
- package/scaffold/mcp/dist/cli/telemetry-test.js +141 -0
- package/scaffold/mcp/dist/cli/ungoverned-detector.js +133 -0
- package/scaffold/mcp/dist/contextEntities.js +282 -0
- package/scaffold/mcp/dist/core/audit/query.js +72 -0
- package/scaffold/mcp/dist/core/audit/writer.js +28 -0
- package/scaffold/mcp/dist/core/config.js +235 -0
- package/scaffold/mcp/dist/core/enterprise-host-identity.js +193 -0
- package/scaffold/mcp/dist/core/enterprise-identity.js +18 -0
- package/scaffold/mcp/dist/core/enterprise-rotation.js +158 -0
- package/scaffold/mcp/dist/core/govern-paths.js +21 -0
- package/scaffold/mcp/dist/core/index.js +14 -0
- package/scaffold/mcp/dist/core/license.js +278 -0
- package/scaffold/mcp/dist/core/policy/enforce.js +66 -0
- package/scaffold/mcp/dist/core/policy/injection.js +172 -0
- package/scaffold/mcp/dist/core/policy/store.js +179 -0
- package/scaffold/mcp/dist/core/rbac/check.js +30 -0
- package/scaffold/mcp/dist/core/secure-endpoint.js +25 -0
- package/scaffold/mcp/dist/core/telemetry/collector.js +285 -0
- package/scaffold/mcp/dist/core/telemetry/state-dir.js +31 -0
- package/scaffold/mcp/dist/core/validators/builtins.js +632 -0
- package/scaffold/mcp/dist/core/validators/config.js +44 -0
- package/scaffold/mcp/dist/core/validators/engine.js +120 -0
- package/scaffold/mcp/dist/core/validators/evaluators/code_comments.js +236 -0
- package/scaffold/mcp/dist/core/validators/evaluators/regex.js +116 -0
- package/scaffold/mcp/dist/core/workflow/artifact-io.js +103 -0
- package/scaffold/mcp/dist/core/workflow/capabilities.js +88 -0
- package/scaffold/mcp/dist/core/workflow/default-workflows.js +102 -0
- package/scaffold/mcp/dist/core/workflow/enforcement.js +164 -0
- package/scaffold/mcp/dist/core/workflow/envelope.js +132 -0
- package/scaffold/mcp/dist/core/workflow/index.js +11 -0
- package/scaffold/mcp/dist/core/workflow/mcp-tools.js +175 -0
- package/scaffold/mcp/dist/core/workflow/resolution.js +69 -0
- package/scaffold/mcp/dist/core/workflow/run-lifecycle.js +123 -0
- package/scaffold/mcp/dist/core/workflow/schemas.js +141 -0
- package/scaffold/mcp/dist/core/workflow/synced-capability-registry.js +60 -0
- package/scaffold/mcp/dist/core/workflow/synced-registry.js +60 -0
- package/scaffold/mcp/dist/daemon/capability-sync-checker.js +242 -0
- package/scaffold/mcp/dist/daemon/client.js +130 -0
- package/scaffold/mcp/dist/daemon/egress-proxy.js +288 -0
- package/scaffold/mcp/dist/daemon/global-host-events.js +222 -0
- package/scaffold/mcp/dist/daemon/heartbeat-pusher.js +116 -0
- package/scaffold/mcp/dist/daemon/heartbeat-tracker.js +165 -0
- package/scaffold/mcp/dist/daemon/host-events-pusher.js +249 -0
- package/scaffold/mcp/dist/daemon/main.js +449 -0
- package/scaffold/mcp/dist/daemon/paths.js +36 -0
- package/scaffold/mcp/dist/daemon/project-service-registry.js +78 -0
- package/scaffold/mcp/dist/daemon/protocol.js +8 -0
- package/scaffold/mcp/dist/daemon/server.js +180 -0
- package/scaffold/mcp/dist/daemon/skill-sync-checker.js +557 -0
- package/scaffold/mcp/dist/daemon/sync-checker.js +165 -0
- package/scaffold/mcp/dist/daemon/ungoverned-scanner.js +136 -0
- package/scaffold/mcp/dist/daemon/workflow-sync-checker.js +249 -0
- package/scaffold/mcp/dist/defaults.js +6 -0
- package/scaffold/mcp/dist/embed.js +627 -0
- package/scaffold/mcp/dist/embedScheduler.js +479 -0
- package/scaffold/mcp/dist/embeddings.js +167 -0
- package/scaffold/mcp/dist/enterprise/audit/push.js +66 -0
- package/scaffold/mcp/dist/enterprise/index.js +327 -0
- package/scaffold/mcp/dist/enterprise/model/deploy.js +27 -0
- package/scaffold/mcp/dist/enterprise/policy/sync.js +129 -0
- package/scaffold/mcp/dist/enterprise/privacy/boundary.js +184 -0
- package/scaffold/mcp/dist/enterprise/reviews/changed-files.js +33 -0
- package/scaffold/mcp/dist/enterprise/reviews/pattern-context.js +202 -0
- package/scaffold/mcp/dist/enterprise/reviews/policy-selection.js +46 -0
- package/scaffold/mcp/dist/enterprise/reviews/push.js +102 -0
- package/scaffold/mcp/dist/enterprise/reviews/trust-state.js +186 -0
- package/scaffold/mcp/dist/enterprise/telemetry/sync.js +57 -0
- package/scaffold/mcp/dist/enterprise/tools/enterprise.js +826 -0
- package/scaffold/mcp/dist/enterprise/tools/harness.js +40 -0
- package/scaffold/mcp/dist/enterprise/tools/walk.js +73 -0
- package/scaffold/mcp/dist/enterprise/violations/push.js +77 -0
- package/scaffold/mcp/dist/enterprise/workflow/push.js +44 -0
- package/scaffold/mcp/dist/enterprise/workflow/state.js +329 -0
- package/scaffold/mcp/dist/frontmatter.js +33 -0
- package/scaffold/mcp/dist/graph.js +769 -0
- package/scaffold/mcp/dist/graphCsv.js +55 -0
- package/scaffold/mcp/dist/graphMetrics.js +8 -0
- package/scaffold/mcp/dist/hooks/permission-request.js +89 -0
- package/scaffold/mcp/dist/hooks/post-tool-use.js +105 -0
- package/scaffold/mcp/dist/hooks/pre-compact.js +29 -0
- package/scaffold/mcp/dist/hooks/pre-tool-use.js +78 -0
- package/scaffold/mcp/dist/hooks/session-end.js +43 -0
- package/scaffold/mcp/dist/hooks/session-start.js +41 -0
- package/scaffold/mcp/dist/hooks/shared.js +194 -0
- package/scaffold/mcp/dist/hooks/stop.js +28 -0
- package/scaffold/mcp/dist/hooks/user-prompt-submit.js +33 -0
- package/scaffold/mcp/dist/impactPresentation.js +137 -0
- package/scaffold/mcp/dist/impactRanking.js +191 -0
- package/scaffold/mcp/dist/impactResponse.js +30 -0
- package/scaffold/mcp/dist/impactResults.js +105 -0
- package/scaffold/mcp/dist/impactSeed.js +20 -0
- package/scaffold/mcp/dist/impactTraversal.js +64 -0
- package/scaffold/mcp/dist/jsonl.js +77 -0
- package/scaffold/mcp/dist/loadGraph.js +759 -0
- package/scaffold/mcp/dist/lruCache.js +38 -0
- package/scaffold/mcp/dist/paths.js +97 -0
- package/scaffold/mcp/dist/patternEvidence.js +272 -0
- package/scaffold/mcp/dist/plugin.js +81 -0
- package/scaffold/mcp/dist/presets.js +78 -0
- package/scaffold/mcp/dist/relatedResponse.js +18 -0
- package/scaffold/mcp/dist/relatedTraversal.js +78 -0
- package/scaffold/mcp/dist/rules.js +23 -0
- package/scaffold/mcp/dist/search.js +212 -0
- package/scaffold/mcp/dist/searchCore.js +457 -0
- package/scaffold/mcp/dist/searchResults.js +230 -0
- package/scaffold/mcp/dist/server.js +317 -0
- package/scaffold/mcp/dist/types.js +1 -0
- package/scaffold/mcp/package-lock.json +321 -200
- package/scaffold/mcp/package.json +15 -6
- package/scaffold/mcp/src/cli/enterprise-setup.ts +82 -8
- package/scaffold/mcp/src/cli/govern.ts +137 -52
- package/scaffold/mcp/src/cli/run.ts +53 -19
- package/scaffold/mcp/src/cli/stage.ts +8 -3
- package/scaffold/mcp/src/core/config.ts +9 -2
- package/scaffold/mcp/src/core/enterprise-host-identity.ts +259 -0
- package/scaffold/mcp/src/core/enterprise-identity.ts +20 -0
- package/scaffold/mcp/src/core/enterprise-rotation.ts +185 -0
- package/scaffold/mcp/src/core/govern-paths.ts +30 -0
- package/scaffold/mcp/src/core/license.ts +186 -17
- package/scaffold/mcp/src/core/secure-endpoint.ts +23 -0
- package/scaffold/mcp/src/core/workflow/enforcement.ts +8 -1
- package/scaffold/mcp/src/core/workflow/mcp-tools.ts +3 -0
- package/scaffold/mcp/src/core/workflow/resolution.ts +9 -1
- package/scaffold/mcp/src/core/workflow/synced-capability-registry.ts +15 -0
- package/scaffold/mcp/src/core/workflow/synced-registry.ts +15 -0
- package/scaffold/mcp/src/daemon/capability-sync-checker.ts +38 -2
- package/scaffold/mcp/src/daemon/egress-proxy.ts +14 -8
- package/scaffold/mcp/src/daemon/global-host-events.ts +288 -0
- package/scaffold/mcp/src/daemon/heartbeat-pusher.ts +4 -0
- package/scaffold/mcp/src/daemon/heartbeat-tracker.ts +2 -0
- package/scaffold/mcp/src/daemon/host-events-pusher.ts +5 -0
- package/scaffold/mcp/src/daemon/main.ts +99 -25
- package/scaffold/mcp/src/daemon/project-service-registry.ts +107 -0
- package/scaffold/mcp/src/daemon/skill-sync-checker.ts +368 -31
- package/scaffold/mcp/src/daemon/sync-checker.ts +4 -0
- package/scaffold/mcp/src/daemon/ungoverned-scanner.ts +57 -25
- package/scaffold/mcp/src/daemon/workflow-sync-checker.ts +41 -2
- package/scaffold/mcp/src/enterprise/audit/push.ts +8 -0
- package/scaffold/mcp/src/enterprise/policy/sync.ts +12 -0
- package/scaffold/mcp/src/enterprise/reviews/push.ts +9 -0
- package/scaffold/mcp/src/enterprise/reviews/trust-state.ts +3 -1
- package/scaffold/mcp/src/enterprise/telemetry/sync.ts +9 -0
- package/scaffold/mcp/src/enterprise/violations/push.ts +9 -0
- package/scaffold/mcp/src/enterprise/workflow/push.ts +7 -0
- package/scaffold/mcp/src/plugin.ts +20 -0
- package/scaffold/mcp/tests/copilot-shim.test.mjs +19 -1
- package/scaffold/mcp/tests/egress-proxy.test.mjs +37 -0
- package/scaffold/mcp/tests/enterprise-identity-sync.test.mjs +401 -0
- package/scaffold/mcp/tests/enterprise-setup.test.mjs +189 -0
- package/scaffold/mcp/tests/global-host-events.test.mjs +81 -0
- package/scaffold/mcp/tests/govern-install.test.mjs +95 -2
- package/scaffold/mcp/tests/govern-repair.test.mjs +20 -3
- package/scaffold/mcp/tests/heartbeat-tracker.test.mjs +26 -0
- package/scaffold/mcp/tests/license.test.mjs +367 -0
- package/scaffold/mcp/tests/project-service-registry.test.mjs +172 -0
- package/scaffold/mcp/tests/review-trust-contract.test.mjs +18 -0
- package/scaffold/mcp/tests/secure-enterprise-endpoint.test.mjs +46 -0
- package/scaffold/mcp/tests/skill-sync-checker.test.mjs +446 -2
- package/scaffold/mcp/tests/ungoverned-scanner.test.mjs +104 -22
- package/scaffold/mcp/tests/workflow-cli.test.mjs +14 -1
- package/scaffold/mcp/tests/workflow-synced-capabilities.test.mjs +37 -0
- package/scaffold/mcp/tests/workflow-synced-registry.test.mjs +40 -1
- package/scaffold/ownership/baseline-v2.4.1.json +22 -0
- package/scaffold/ownership/current.json +4 -0
- package/scaffold/ownership/v1.json +456 -0
- package/scaffold/scripts/ingest-parsers.mjs +1 -387
- package/scaffold/scripts/ingest-worker.mjs +4 -1
- package/scaffold/scripts/ingest.mjs +5 -3899
- package/scaffold/scripts/lib/ingest/arguments.mjs +78 -0
- package/scaffold/scripts/lib/ingest/chunks.mjs +212 -0
- package/scaffold/scripts/lib/ingest/config.mjs +120 -0
- package/scaffold/scripts/lib/ingest/constants.mjs +222 -0
- package/scaffold/scripts/lib/ingest/files.mjs +387 -0
- package/scaffold/scripts/lib/ingest/incremental-state.mjs +131 -0
- package/scaffold/scripts/lib/ingest/io.mjs +78 -0
- package/scaffold/scripts/lib/ingest/main.mjs +76 -0
- package/scaffold/scripts/lib/ingest/parser-composition.mjs +140 -0
- package/scaffold/scripts/lib/ingest/parser-registry.mjs +387 -0
- package/scaffold/scripts/lib/ingest/pipeline-stages.mjs +1625 -0
- package/scaffold/scripts/lib/ingest/projects.mjs +272 -0
- package/scaffold/scripts/lib/ingest/relations.mjs +860 -0
- package/scaffold/scripts/lib/ingest/runtime-paths.mjs +11 -0
- package/scaffold/scripts/lib/ingest/workers.mjs +264 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
function isWindowChunkId(id) {
|
|
2
|
+
return id.includes(":window:");
|
|
3
|
+
}
|
|
4
|
+
function baseChunkId(id) {
|
|
5
|
+
const markerIndex = id.indexOf(":window:");
|
|
6
|
+
return markerIndex === -1 ? id : id.slice(0, markerIndex);
|
|
7
|
+
}
|
|
8
|
+
function groupRuleLinks(relations) {
|
|
9
|
+
const links = new Map();
|
|
10
|
+
for (const relation of relations) {
|
|
11
|
+
if (relation.relation !== "CONSTRAINS" && relation.relation !== "IMPLEMENTS") {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (relation.relation === "CONSTRAINS") {
|
|
15
|
+
const list = links.get(relation.to) ?? [];
|
|
16
|
+
list.push(relation.from);
|
|
17
|
+
links.set(relation.to, list);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
const list = links.get(relation.from) ?? [];
|
|
21
|
+
list.push(relation.to);
|
|
22
|
+
links.set(relation.from, list);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return links;
|
|
26
|
+
}
|
|
27
|
+
function buildRelationSearchSignals(data) {
|
|
28
|
+
const labelsById = new Map();
|
|
29
|
+
for (const document of data.documents) {
|
|
30
|
+
labelsById.set(document.id, document.path);
|
|
31
|
+
}
|
|
32
|
+
for (const chunk of data.chunks) {
|
|
33
|
+
labelsById.set(chunk.id, chunk.name || chunk.id);
|
|
34
|
+
}
|
|
35
|
+
for (const module of data.modules) {
|
|
36
|
+
labelsById.set(module.id, module.name || module.path);
|
|
37
|
+
}
|
|
38
|
+
for (const project of data.projects) {
|
|
39
|
+
labelsById.set(project.id, project.name || project.path);
|
|
40
|
+
}
|
|
41
|
+
const supportedRelations = new Set([
|
|
42
|
+
"CALLS_SQL",
|
|
43
|
+
"USES_CONFIG_KEY",
|
|
44
|
+
"USES_RESOURCE_KEY",
|
|
45
|
+
"USES_SETTING_KEY",
|
|
46
|
+
"USES_CONFIG",
|
|
47
|
+
"TRANSFORMS_CONFIG"
|
|
48
|
+
]);
|
|
49
|
+
const signalsByEntity = new Map();
|
|
50
|
+
for (const relation of data.relations) {
|
|
51
|
+
if (!supportedRelations.has(relation.relation)) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const sourceLabel = labelsById.get(relation.from) ?? relation.from;
|
|
55
|
+
const targetLabel = labelsById.get(relation.to) ?? relation.to;
|
|
56
|
+
const outgoingSignal = [relation.relation.toLowerCase(), relation.note, targetLabel].filter(Boolean).join(" ");
|
|
57
|
+
const incomingSignal = ["affected_by", relation.relation.toLowerCase(), relation.note, sourceLabel]
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.join(" ");
|
|
60
|
+
const outgoing = signalsByEntity.get(relation.from) ?? [];
|
|
61
|
+
outgoing.push(outgoingSignal);
|
|
62
|
+
signalsByEntity.set(relation.from, outgoing);
|
|
63
|
+
const incoming = signalsByEntity.get(relation.to) ?? [];
|
|
64
|
+
incoming.push(incomingSignal);
|
|
65
|
+
signalsByEntity.set(relation.to, incoming);
|
|
66
|
+
}
|
|
67
|
+
return new Map([...signalsByEntity.entries()].map(([id, parts]) => [id, parts.join("\n")]));
|
|
68
|
+
}
|
|
69
|
+
function buildSearchEntityIndexes(data) {
|
|
70
|
+
const fileRuleLinks = groupRuleLinks(data.relations);
|
|
71
|
+
const relationSignals = buildRelationSearchSignals(data);
|
|
72
|
+
const adrPathSet = new Set(data.adrs
|
|
73
|
+
.map((adr) => adr.path.trim().toLowerCase())
|
|
74
|
+
.filter((adrPath) => adrPath.length > 0));
|
|
75
|
+
const filePathById = new Map(data.documents.filter((document) => document.kind === "CODE").map((document) => [document.id, document.path]));
|
|
76
|
+
return { fileRuleLinks, relationSignals, adrPathSet, filePathById };
|
|
77
|
+
}
|
|
78
|
+
export function* iterateSearchEntities(data, includeContent) {
|
|
79
|
+
const { fileRuleLinks, relationSignals, adrPathSet, filePathById } = buildSearchEntityIndexes(data);
|
|
80
|
+
for (const document of data.documents) {
|
|
81
|
+
const normalizedPath = document.path.trim().toLowerCase();
|
|
82
|
+
if (document.kind === "ADR" && adrPathSet.has(normalizedPath)) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
yield {
|
|
86
|
+
id: document.id,
|
|
87
|
+
entity_type: "File",
|
|
88
|
+
kind: document.kind,
|
|
89
|
+
label: document.path,
|
|
90
|
+
path: document.path,
|
|
91
|
+
text: `${document.path}\n${document.excerpt}\n${document.content}\n${relationSignals.get(document.id) ?? ""}`,
|
|
92
|
+
status: document.status,
|
|
93
|
+
source_of_truth: document.source_of_truth,
|
|
94
|
+
trust_level: document.trust_level,
|
|
95
|
+
updated_at: document.updated_at,
|
|
96
|
+
snippet: document.excerpt,
|
|
97
|
+
matched_rules: fileRuleLinks.get(document.id) ?? [],
|
|
98
|
+
content: includeContent ? document.content : undefined
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
for (const rule of data.rules) {
|
|
102
|
+
yield {
|
|
103
|
+
id: rule.id,
|
|
104
|
+
entity_type: "Rule",
|
|
105
|
+
kind: "RULE",
|
|
106
|
+
label: rule.title || rule.id,
|
|
107
|
+
path: "",
|
|
108
|
+
text: `${rule.id}\n${rule.title}\n${rule.body}`,
|
|
109
|
+
status: rule.status,
|
|
110
|
+
source_of_truth: rule.source_of_truth,
|
|
111
|
+
trust_level: rule.trust_level,
|
|
112
|
+
updated_at: rule.updated_at,
|
|
113
|
+
snippet: rule.body.slice(0, 500),
|
|
114
|
+
matched_rules: [rule.id],
|
|
115
|
+
content: includeContent ? rule.body : undefined
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
for (const adr of data.adrs) {
|
|
119
|
+
yield {
|
|
120
|
+
id: adr.id,
|
|
121
|
+
entity_type: "ADR",
|
|
122
|
+
kind: "ADR",
|
|
123
|
+
label: adr.title || adr.id,
|
|
124
|
+
path: adr.path,
|
|
125
|
+
text: `${adr.path}\n${adr.title}\n${adr.body}`,
|
|
126
|
+
status: adr.status,
|
|
127
|
+
source_of_truth: adr.source_of_truth,
|
|
128
|
+
trust_level: adr.trust_level,
|
|
129
|
+
updated_at: adr.decision_date,
|
|
130
|
+
snippet: adr.body.slice(0, 500),
|
|
131
|
+
matched_rules: [],
|
|
132
|
+
content: includeContent ? adr.body : undefined
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
for (const chunk of data.chunks) {
|
|
136
|
+
const filePath = filePathById.get(chunk.file_id) ?? "";
|
|
137
|
+
yield {
|
|
138
|
+
id: chunk.id,
|
|
139
|
+
entity_type: "Chunk",
|
|
140
|
+
kind: chunk.kind || "chunk",
|
|
141
|
+
label: chunk.name || chunk.id,
|
|
142
|
+
path: filePath,
|
|
143
|
+
text: `${filePath}\n${chunk.name}\n${chunk.signature}\n${chunk.description}\n${chunk.body}\n${relationSignals.get(chunk.id) ?? ""}`,
|
|
144
|
+
status: chunk.status,
|
|
145
|
+
source_of_truth: chunk.source_of_truth,
|
|
146
|
+
trust_level: chunk.trust_level,
|
|
147
|
+
updated_at: chunk.updated_at,
|
|
148
|
+
snippet: chunk.description || chunk.body.slice(0, 500),
|
|
149
|
+
matched_rules: fileRuleLinks.get(chunk.file_id) ?? [],
|
|
150
|
+
content: includeContent ? chunk.body : undefined
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
for (const module of data.modules) {
|
|
154
|
+
yield {
|
|
155
|
+
id: module.id,
|
|
156
|
+
entity_type: "Module",
|
|
157
|
+
kind: "MODULE",
|
|
158
|
+
label: module.name,
|
|
159
|
+
path: module.path,
|
|
160
|
+
text: `${module.path}\n${module.name}\n${module.summary}\n${module.exported_symbols}`,
|
|
161
|
+
status: module.status,
|
|
162
|
+
source_of_truth: module.source_of_truth,
|
|
163
|
+
trust_level: module.trust_level,
|
|
164
|
+
updated_at: module.updated_at,
|
|
165
|
+
snippet: (module.summary || "").slice(0, 500),
|
|
166
|
+
matched_rules: [],
|
|
167
|
+
content: includeContent ? module.summary : undefined
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
for (const project of data.projects) {
|
|
171
|
+
yield {
|
|
172
|
+
id: project.id,
|
|
173
|
+
entity_type: "Project",
|
|
174
|
+
kind: project.kind.toUpperCase() || "PROJECT",
|
|
175
|
+
label: project.name || project.path,
|
|
176
|
+
path: project.path,
|
|
177
|
+
text: `${project.path}\n${project.name}\n${project.kind}\n${project.language}\n${project.target_framework}\n${project.summary}`,
|
|
178
|
+
status: project.status,
|
|
179
|
+
source_of_truth: project.source_of_truth,
|
|
180
|
+
trust_level: project.trust_level,
|
|
181
|
+
updated_at: project.updated_at,
|
|
182
|
+
snippet: (project.summary || "").slice(0, 500),
|
|
183
|
+
matched_rules: [],
|
|
184
|
+
content: includeContent ? project.summary : undefined
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
export function buildSearchEntities(data, includeContent) {
|
|
189
|
+
return Array.from(iterateSearchEntities(data, includeContent));
|
|
190
|
+
}
|
|
191
|
+
export function buildChunkPartOfRelations(data) {
|
|
192
|
+
const relations = [];
|
|
193
|
+
for (const chunk of data.chunks) {
|
|
194
|
+
if (isWindowChunkId(chunk.id)) {
|
|
195
|
+
relations.push({
|
|
196
|
+
from: chunk.id,
|
|
197
|
+
to: baseChunkId(chunk.id),
|
|
198
|
+
relation: "PART_OF",
|
|
199
|
+
note: "Overlap window belongs to base chunk"
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (!chunk.file_id) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
relations.push({
|
|
206
|
+
from: chunk.id,
|
|
207
|
+
to: chunk.file_id,
|
|
208
|
+
relation: "PART_OF",
|
|
209
|
+
note: "Chunk belongs to file"
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return relations;
|
|
213
|
+
}
|
|
214
|
+
export function entityCatalog(data) {
|
|
215
|
+
const catalog = new Map();
|
|
216
|
+
const fileById = new Map(data.documents.map((document) => [document.id, document]));
|
|
217
|
+
for (const file of data.documents) {
|
|
218
|
+
catalog.set(file.id, {
|
|
219
|
+
id: file.id,
|
|
220
|
+
type: "File",
|
|
221
|
+
label: file.path,
|
|
222
|
+
status: file.status,
|
|
223
|
+
source_of_truth: file.source_of_truth
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
for (const rule of data.rules) {
|
|
227
|
+
catalog.set(rule.id, {
|
|
228
|
+
id: rule.id,
|
|
229
|
+
type: "Rule",
|
|
230
|
+
label: rule.title,
|
|
231
|
+
status: rule.status,
|
|
232
|
+
source_of_truth: rule.source_of_truth
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
for (const adr of data.adrs) {
|
|
236
|
+
catalog.set(adr.id, {
|
|
237
|
+
id: adr.id,
|
|
238
|
+
type: "ADR",
|
|
239
|
+
label: adr.title || adr.id,
|
|
240
|
+
status: adr.status,
|
|
241
|
+
source_of_truth: adr.source_of_truth
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
for (const chunk of data.chunks) {
|
|
245
|
+
const filePath = fileById.get(chunk.file_id)?.path ?? "";
|
|
246
|
+
const chunkEntity = {
|
|
247
|
+
id: chunk.id,
|
|
248
|
+
type: "Chunk",
|
|
249
|
+
label: chunk.name || chunk.id,
|
|
250
|
+
status: chunk.status,
|
|
251
|
+
source_of_truth: chunk.source_of_truth
|
|
252
|
+
};
|
|
253
|
+
if (filePath) {
|
|
254
|
+
chunkEntity.path = filePath;
|
|
255
|
+
}
|
|
256
|
+
catalog.set(chunk.id, chunkEntity);
|
|
257
|
+
}
|
|
258
|
+
for (const module of data.modules) {
|
|
259
|
+
catalog.set(module.id, {
|
|
260
|
+
id: module.id,
|
|
261
|
+
type: "Module",
|
|
262
|
+
label: module.name,
|
|
263
|
+
status: module.status,
|
|
264
|
+
source_of_truth: module.source_of_truth,
|
|
265
|
+
path: module.path
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
for (const project of data.projects) {
|
|
269
|
+
catalog.set(project.id, {
|
|
270
|
+
id: project.id,
|
|
271
|
+
type: "Project",
|
|
272
|
+
label: project.name || project.path,
|
|
273
|
+
status: project.status,
|
|
274
|
+
source_of_truth: project.source_of_truth,
|
|
275
|
+
path: project.path
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
return catalog;
|
|
279
|
+
}
|
|
280
|
+
export function buildEntitySearchMap(data) {
|
|
281
|
+
return new Map(buildSearchEntities(data, false).map((entity) => [entity.id, entity]));
|
|
282
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
4
|
+
export function queryAuditLog(contextDir, query) {
|
|
5
|
+
const auditDir = join(contextDir, "audit");
|
|
6
|
+
const limit = query.limit ?? 50;
|
|
7
|
+
if (query.from && !DATE_RE.test(query.from))
|
|
8
|
+
return [];
|
|
9
|
+
if (query.to && !DATE_RE.test(query.to))
|
|
10
|
+
return [];
|
|
11
|
+
let files;
|
|
12
|
+
try {
|
|
13
|
+
files = readdirSync(auditDir)
|
|
14
|
+
.filter(f => f.endsWith(".jsonl"))
|
|
15
|
+
.sort();
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
// Filter files by date range
|
|
21
|
+
if (query.from) {
|
|
22
|
+
const fromFile = `${query.from}.jsonl`;
|
|
23
|
+
files = files.filter(f => f >= fromFile);
|
|
24
|
+
}
|
|
25
|
+
if (query.to) {
|
|
26
|
+
const toFile = `${query.to}.jsonl`;
|
|
27
|
+
files = files.filter(f => f <= toFile);
|
|
28
|
+
}
|
|
29
|
+
const results = [];
|
|
30
|
+
// Read newest files first
|
|
31
|
+
for (const file of files.reverse()) {
|
|
32
|
+
if (results.length >= limit)
|
|
33
|
+
break;
|
|
34
|
+
try {
|
|
35
|
+
const raw = readFileSync(join(auditDir, file), "utf8").trim();
|
|
36
|
+
if (!raw)
|
|
37
|
+
continue;
|
|
38
|
+
const entries = raw
|
|
39
|
+
.split("\n")
|
|
40
|
+
.map(line => {
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(line);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
.filter(Boolean)
|
|
49
|
+
.reverse(); // newest first within file
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
if (results.length >= limit)
|
|
52
|
+
break;
|
|
53
|
+
// Apply tool filter
|
|
54
|
+
if (query.tool && entry.tool !== query.tool)
|
|
55
|
+
continue;
|
|
56
|
+
if (query.event_type && entry.event_type !== query.event_type)
|
|
57
|
+
continue;
|
|
58
|
+
if (query.evidence_level && entry.evidence_level !== query.evidence_level)
|
|
59
|
+
continue;
|
|
60
|
+
if (query.status && entry.status !== query.status)
|
|
61
|
+
continue;
|
|
62
|
+
if (query.session_id && entry.session_id !== query.session_id)
|
|
63
|
+
continue;
|
|
64
|
+
results.push(entry);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// skip unreadable files
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return results;
|
|
72
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { appendFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
export class AuditWriter {
|
|
4
|
+
auditDir;
|
|
5
|
+
onEntry;
|
|
6
|
+
initialized = false;
|
|
7
|
+
constructor(contextDir, options = {}) {
|
|
8
|
+
this.auditDir = join(contextDir, "audit");
|
|
9
|
+
this.onEntry = options.onEntry ?? null;
|
|
10
|
+
}
|
|
11
|
+
log(entry) {
|
|
12
|
+
const date = entry.timestamp.slice(0, 10); // YYYY-MM-DD
|
|
13
|
+
const filePath = join(this.auditDir, `${date}.jsonl`);
|
|
14
|
+
const line = JSON.stringify(entry) + "\n";
|
|
15
|
+
this.onEntry?.(entry);
|
|
16
|
+
// Fire-and-forget async write — don't block the tool handler
|
|
17
|
+
this.writeAsync(filePath, line).catch(() => {
|
|
18
|
+
process.stderr.write("[cortex-enterprise] Failed to write audit entry\n");
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
async writeAsync(filePath, line) {
|
|
22
|
+
if (!this.initialized) {
|
|
23
|
+
await mkdir(this.auditDir, { recursive: true });
|
|
24
|
+
this.initialized = true;
|
|
25
|
+
}
|
|
26
|
+
await appendFile(filePath, line);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { parseValidatorsConfig } from "./validators/config.js";
|
|
4
|
+
import { isAllowedEnterpriseEndpoint } from "./secure-endpoint.js";
|
|
5
|
+
const DEFAULT_FRAMEWORKS = ["iso27001", "iso42001", "soc2"];
|
|
6
|
+
const EU_ADDON_FRAMEWORKS = ["gdpr", "ai_act", "nis2"];
|
|
7
|
+
const VALID_FRAMEWORKS = new Set([
|
|
8
|
+
...DEFAULT_FRAMEWORKS,
|
|
9
|
+
...EU_ADDON_FRAMEWORKS,
|
|
10
|
+
]);
|
|
11
|
+
const VALID_MODES = new Set(["off", "advisory", "enforced"]);
|
|
12
|
+
const VALID_TIERS = new Set(["prevent", "wrap", "detect", "off"]);
|
|
13
|
+
const DEFAULTS = {
|
|
14
|
+
enterprise: {
|
|
15
|
+
endpoint: "",
|
|
16
|
+
api_key: "",
|
|
17
|
+
base_url: "",
|
|
18
|
+
},
|
|
19
|
+
telemetry: {
|
|
20
|
+
enabled: false,
|
|
21
|
+
endpoint: "",
|
|
22
|
+
api_key: "",
|
|
23
|
+
interval_minutes: 10,
|
|
24
|
+
},
|
|
25
|
+
audit: {
|
|
26
|
+
enabled: true,
|
|
27
|
+
retention_days: 90,
|
|
28
|
+
},
|
|
29
|
+
policy: {
|
|
30
|
+
enabled: true,
|
|
31
|
+
endpoint: "",
|
|
32
|
+
api_key: "",
|
|
33
|
+
sync_interval_minutes: 240,
|
|
34
|
+
},
|
|
35
|
+
rbac: {
|
|
36
|
+
enabled: false,
|
|
37
|
+
default_role: "developer",
|
|
38
|
+
},
|
|
39
|
+
validators: {},
|
|
40
|
+
compliance: {
|
|
41
|
+
frameworks: [],
|
|
42
|
+
eu_addons: false,
|
|
43
|
+
},
|
|
44
|
+
govern: {
|
|
45
|
+
mode: "off",
|
|
46
|
+
sync_on_startup: true,
|
|
47
|
+
sync_interval_minutes: 60,
|
|
48
|
+
tier_claude: "prevent",
|
|
49
|
+
tier_codex: "prevent",
|
|
50
|
+
tier_copilot: "wrap",
|
|
51
|
+
detect_ungoverned: true,
|
|
52
|
+
govern_endpoint: "",
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const VALID_ROLES = ["admin", "developer", "readonly"];
|
|
56
|
+
function isValidRole(value) {
|
|
57
|
+
return VALID_ROLES.includes(value);
|
|
58
|
+
}
|
|
59
|
+
function stripInlineComment(value) {
|
|
60
|
+
// Strip # comments that aren't inside quotes
|
|
61
|
+
const singleMatch = value.match(/^'([^']*)'(\s*#.*)?$/);
|
|
62
|
+
if (singleMatch)
|
|
63
|
+
return singleMatch[1];
|
|
64
|
+
const doubleMatch = value.match(/^"([^"]*)"(\s*#.*)?$/);
|
|
65
|
+
if (doubleMatch)
|
|
66
|
+
return doubleMatch[1];
|
|
67
|
+
// Unquoted: strip from first # preceded by whitespace
|
|
68
|
+
const commentIdx = value.search(/\s+#/);
|
|
69
|
+
return commentIdx >= 0 ? value.slice(0, commentIdx).trimEnd() : value;
|
|
70
|
+
}
|
|
71
|
+
function parseInlineList(value) {
|
|
72
|
+
const trimmed = value.trim();
|
|
73
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]"))
|
|
74
|
+
return null;
|
|
75
|
+
const inner = trimmed.slice(1, -1).trim();
|
|
76
|
+
if (!inner)
|
|
77
|
+
return [];
|
|
78
|
+
return inner
|
|
79
|
+
.split(",")
|
|
80
|
+
.map((part) => part.trim().replace(/^["']|["']$/g, ""))
|
|
81
|
+
.filter(Boolean);
|
|
82
|
+
}
|
|
83
|
+
function parseSimpleYaml(text) {
|
|
84
|
+
const result = {};
|
|
85
|
+
let section = "";
|
|
86
|
+
for (const line of text.split("\n")) {
|
|
87
|
+
const trimmed = line.trimEnd();
|
|
88
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
89
|
+
continue;
|
|
90
|
+
const sectionMatch = trimmed.match(/^(\w+):\s*$/);
|
|
91
|
+
if (sectionMatch) {
|
|
92
|
+
section = sectionMatch[1];
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const kvMatch = trimmed.match(/^\s+(\w+):\s*(.+?)\s*$/);
|
|
96
|
+
if (kvMatch && section) {
|
|
97
|
+
result[`${section}.${kvMatch[1]}`] = stripInlineComment(kvMatch[2]);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const topMatch = trimmed.match(/^(\w+):\s*(.+?)\s*$/);
|
|
101
|
+
if (topMatch) {
|
|
102
|
+
result[topMatch[1]] = stripInlineComment(topMatch[2]);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
function isLikelyApiKey(value) {
|
|
108
|
+
return /^(?:ctx|ent)_[A-Za-z0-9._-]{8,}$/.test(value);
|
|
109
|
+
}
|
|
110
|
+
function isLikelyHttpUrl(value) {
|
|
111
|
+
try {
|
|
112
|
+
const parsed = new URL(value);
|
|
113
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export function resolveEnterpriseActivation(config) {
|
|
120
|
+
const apiKey = config.enterprise.api_key.trim();
|
|
121
|
+
const endpoint = (config.enterprise.base_url ||
|
|
122
|
+
config.enterprise.endpoint).trim();
|
|
123
|
+
if (!apiKey) {
|
|
124
|
+
return { active: false, reason: "missing_api_key", api_key: null, endpoint: endpoint || null };
|
|
125
|
+
}
|
|
126
|
+
if (!endpoint) {
|
|
127
|
+
return { active: false, reason: "missing_endpoint", api_key: apiKey, endpoint: null };
|
|
128
|
+
}
|
|
129
|
+
if (!isLikelyApiKey(apiKey)) {
|
|
130
|
+
return { active: false, reason: "invalid_api_key_format", api_key: apiKey, endpoint };
|
|
131
|
+
}
|
|
132
|
+
if (!isLikelyHttpUrl(endpoint) ||
|
|
133
|
+
!isAllowedEnterpriseEndpoint(endpoint)) {
|
|
134
|
+
return { active: false, reason: "invalid_endpoint_format", api_key: apiKey, endpoint };
|
|
135
|
+
}
|
|
136
|
+
return { active: true, reason: "active", api_key: apiKey, endpoint };
|
|
137
|
+
}
|
|
138
|
+
function deriveEndpoint(baseUrl, suffix) {
|
|
139
|
+
if (!baseUrl)
|
|
140
|
+
return "";
|
|
141
|
+
return baseUrl.replace(/\/$/, "") + suffix;
|
|
142
|
+
}
|
|
143
|
+
function isValidTier(value) {
|
|
144
|
+
return value !== undefined && VALID_TIERS.has(value);
|
|
145
|
+
}
|
|
146
|
+
function isValidMode(value) {
|
|
147
|
+
return value !== undefined && VALID_MODES.has(value);
|
|
148
|
+
}
|
|
149
|
+
function resolveFrameworks(rawList, euAddons) {
|
|
150
|
+
const parsed = rawList ? parseInlineList(rawList) : null;
|
|
151
|
+
const base = parsed && parsed.length > 0
|
|
152
|
+
? parsed.filter((f) => VALID_FRAMEWORKS.has(f))
|
|
153
|
+
: DEFAULT_FRAMEWORKS.slice();
|
|
154
|
+
if (!euAddons)
|
|
155
|
+
return base;
|
|
156
|
+
const merged = new Set(base);
|
|
157
|
+
for (const f of EU_ADDON_FRAMEWORKS)
|
|
158
|
+
merged.add(f);
|
|
159
|
+
return Array.from(merged);
|
|
160
|
+
}
|
|
161
|
+
export function loadEnterpriseConfig(contextDir) {
|
|
162
|
+
let raw;
|
|
163
|
+
try {
|
|
164
|
+
raw = readFileSync(join(contextDir, "enterprise.yml"), "utf8");
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
try {
|
|
168
|
+
raw = readFileSync(join(contextDir, "enterprise.yaml"), "utf8");
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return DEFAULTS;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const fields = parseSimpleYaml(raw);
|
|
175
|
+
const enterpriseApiKey = fields["enterprise.api_key"] ?? DEFAULTS.enterprise.api_key;
|
|
176
|
+
const baseUrl = (fields["enterprise.base_url"] ?? fields["enterprise.endpoint"] ?? "").replace(/\/$/, "");
|
|
177
|
+
const enterpriseEndpoint = fields["enterprise.endpoint"] ?? baseUrl;
|
|
178
|
+
const telemetryEndpoint = fields["enterprise.endpoint_telemetry"] ??
|
|
179
|
+
fields["telemetry.endpoint"] ??
|
|
180
|
+
deriveEndpoint(baseUrl, "/api/v1/telemetry/push");
|
|
181
|
+
const telemetryApiKey = fields["telemetry.api_key"] ?? enterpriseApiKey;
|
|
182
|
+
const policyEndpoint = fields["enterprise.endpoint_policy"] ??
|
|
183
|
+
fields["policy.endpoint"] ??
|
|
184
|
+
deriveEndpoint(baseUrl, "/api/v1/policies/sync");
|
|
185
|
+
const policyApiKey = fields["policy.api_key"] ?? enterpriseApiKey;
|
|
186
|
+
const governEndpoint = fields["enterprise.endpoint_govern"] ??
|
|
187
|
+
deriveEndpoint(baseUrl, "/api/v1/govern");
|
|
188
|
+
const euAddons = fields["compliance.eu_addons"] === "true";
|
|
189
|
+
const frameworks = resolveFrameworks(fields["compliance.frameworks"], euAddons);
|
|
190
|
+
const governMode = isValidMode(fields["govern.mode"]) ? fields["govern.mode"] : DEFAULTS.govern.mode;
|
|
191
|
+
return {
|
|
192
|
+
enterprise: {
|
|
193
|
+
endpoint: enterpriseEndpoint,
|
|
194
|
+
api_key: enterpriseApiKey,
|
|
195
|
+
base_url: baseUrl,
|
|
196
|
+
},
|
|
197
|
+
telemetry: {
|
|
198
|
+
endpoint: telemetryEndpoint,
|
|
199
|
+
api_key: telemetryApiKey,
|
|
200
|
+
enabled: fields["telemetry.enabled"] !== undefined
|
|
201
|
+
? fields["telemetry.enabled"] === "true"
|
|
202
|
+
: !!(telemetryEndpoint && telemetryApiKey),
|
|
203
|
+
interval_minutes: parseInt(fields["telemetry.interval_minutes"] ?? "", 10) || DEFAULTS.telemetry.interval_minutes,
|
|
204
|
+
},
|
|
205
|
+
audit: {
|
|
206
|
+
enabled: fields["audit.enabled"] !== "false",
|
|
207
|
+
retention_days: parseInt(fields["audit.retention_days"] ?? "", 10) || DEFAULTS.audit.retention_days,
|
|
208
|
+
},
|
|
209
|
+
policy: {
|
|
210
|
+
enabled: fields["policy.enabled"] !== "false",
|
|
211
|
+
endpoint: policyEndpoint,
|
|
212
|
+
api_key: policyApiKey,
|
|
213
|
+
sync_interval_minutes: parseInt(fields["policy.sync_interval_minutes"] ?? "", 10) || DEFAULTS.policy.sync_interval_minutes,
|
|
214
|
+
},
|
|
215
|
+
rbac: {
|
|
216
|
+
enabled: fields["rbac.enabled"] === "true",
|
|
217
|
+
default_role: isValidRole(fields["rbac.default_role"]) ? fields["rbac.default_role"] : DEFAULTS.rbac.default_role,
|
|
218
|
+
},
|
|
219
|
+
validators: parseValidatorsConfig(fields),
|
|
220
|
+
compliance: {
|
|
221
|
+
frameworks,
|
|
222
|
+
eu_addons: euAddons,
|
|
223
|
+
},
|
|
224
|
+
govern: {
|
|
225
|
+
mode: governMode,
|
|
226
|
+
sync_on_startup: fields["govern.sync_on_startup"] !== "false",
|
|
227
|
+
sync_interval_minutes: parseInt(fields["govern.sync_interval_minutes"] ?? "", 10) || DEFAULTS.govern.sync_interval_minutes,
|
|
228
|
+
tier_claude: isValidTier(fields["govern.tier_claude"]) ? fields["govern.tier_claude"] : DEFAULTS.govern.tier_claude,
|
|
229
|
+
tier_codex: isValidTier(fields["govern.tier_codex"]) ? fields["govern.tier_codex"] : DEFAULTS.govern.tier_codex,
|
|
230
|
+
tier_copilot: isValidTier(fields["govern.tier_copilot"]) ? fields["govern.tier_copilot"] : DEFAULTS.govern.tier_copilot,
|
|
231
|
+
detect_ungoverned: fields["govern.detect_ungoverned"] !== "false",
|
|
232
|
+
govern_endpoint: governEndpoint,
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|