@runcontext/core 0.1.0 → 0.2.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/dist/index.cjs CHANGED
@@ -1,84 +1,173 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;// src/schema/concept.ts
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;// src/schema/osi.ts
2
2
  var _zod = require('zod');
3
- var evidenceSchema = _zod.z.object({ type: _zod.z.string(), ref: _zod.z.string() });
4
- var exampleSchema = _zod.z.object({ label: _zod.z.string(), content: _zod.z.string(), kind: _zod.z.enum(["do", "dont"]) });
5
- var conceptFileSchema = _zod.z.object({
6
- id: _zod.z.string(),
7
- name: _zod.z.string().optional(),
8
- domain: _zod.z.string().optional(),
9
- product_id: _zod.z.string().optional(),
10
- definition: _zod.z.string(),
11
- owner: _zod.z.string().optional(),
12
- status: _zod.z.enum(["draft", "certified", "deprecated"]).optional(),
13
- certified: _zod.z.boolean().optional(),
14
- tags: _zod.z.array(_zod.z.string()).optional(),
15
- evidence: _zod.z.array(evidenceSchema).optional(),
16
- depends_on: _zod.z.array(_zod.z.string()).optional(),
17
- examples: _zod.z.array(exampleSchema).optional(),
18
- description: _zod.z.string().optional()
3
+ var dialectEnum = _zod.z.enum(["ANSI_SQL", "SNOWFLAKE", "MDX", "TABLEAU", "DATABRICKS"]);
4
+ var vendorEnum = _zod.z.enum(["COMMON", "SNOWFLAKE", "SALESFORCE", "DBT", "DATABRICKS"]);
5
+ var aiContextObjectSchema = _zod.z.object({
6
+ instructions: _zod.z.string().optional(),
7
+ synonyms: _zod.z.array(_zod.z.string()).optional(),
8
+ examples: _zod.z.array(_zod.z.string()).optional()
19
9
  });
20
-
21
- // src/schema/product.ts
22
-
23
- var productFileSchema = _zod.z.object({
24
- id: _zod.z.string(),
25
- name: _zod.z.string().optional(),
26
- description: _zod.z.string(),
27
- owner: _zod.z.string().optional(),
28
- tags: _zod.z.array(_zod.z.string()).optional(),
29
- status: _zod.z.enum(["draft", "certified", "deprecated"]).optional()
10
+ var aiContextSchema = _zod.z.union([_zod.z.string(), aiContextObjectSchema]);
11
+ var customExtensionSchema = _zod.z.object({
12
+ vendor_name: vendorEnum,
13
+ data: _zod.z.string()
14
+ });
15
+ var dialectExpressionSchema = _zod.z.object({
16
+ dialect: dialectEnum,
17
+ expression: _zod.z.string()
18
+ });
19
+ var expressionSchema = _zod.z.object({
20
+ dialects: _zod.z.array(dialectExpressionSchema)
21
+ });
22
+ var dimensionSchema = _zod.z.object({
23
+ is_time: _zod.z.boolean().optional()
24
+ });
25
+ var osiFieldSchema = _zod.z.object({
26
+ name: _zod.z.string(),
27
+ expression: expressionSchema,
28
+ dimension: dimensionSchema.optional(),
29
+ label: _zod.z.string().optional(),
30
+ description: _zod.z.string().optional(),
31
+ ai_context: aiContextSchema.optional(),
32
+ custom_extensions: _zod.z.array(customExtensionSchema).optional()
33
+ });
34
+ var osiDatasetSchema = _zod.z.object({
35
+ name: _zod.z.string(),
36
+ source: _zod.z.string(),
37
+ primary_key: _zod.z.array(_zod.z.string()).optional(),
38
+ unique_keys: _zod.z.array(_zod.z.array(_zod.z.string())).optional(),
39
+ description: _zod.z.string().optional(),
40
+ ai_context: aiContextSchema.optional(),
41
+ fields: _zod.z.array(osiFieldSchema).optional(),
42
+ custom_extensions: _zod.z.array(customExtensionSchema).optional()
43
+ });
44
+ var osiRelationshipSchema = _zod.z.object({
45
+ name: _zod.z.string(),
46
+ from: _zod.z.string(),
47
+ to: _zod.z.string(),
48
+ from_columns: _zod.z.array(_zod.z.string()),
49
+ to_columns: _zod.z.array(_zod.z.string()),
50
+ ai_context: aiContextSchema.optional(),
51
+ custom_extensions: _zod.z.array(customExtensionSchema).optional()
52
+ });
53
+ var osiMetricSchema = _zod.z.object({
54
+ name: _zod.z.string(),
55
+ expression: expressionSchema,
56
+ description: _zod.z.string().optional(),
57
+ ai_context: aiContextSchema.optional(),
58
+ custom_extensions: _zod.z.array(customExtensionSchema).optional()
59
+ });
60
+ var osiSemanticModelSchema = _zod.z.object({
61
+ name: _zod.z.string(),
62
+ description: _zod.z.string().optional(),
63
+ ai_context: aiContextSchema.optional(),
64
+ datasets: _zod.z.array(osiDatasetSchema),
65
+ relationships: _zod.z.array(osiRelationshipSchema).optional(),
66
+ metrics: _zod.z.array(osiMetricSchema).optional(),
67
+ custom_extensions: _zod.z.array(customExtensionSchema).optional()
68
+ });
69
+ var osiDocumentSchema = _zod.z.object({
70
+ version: _zod.z.literal("1.0"),
71
+ semantic_model: _zod.z.array(osiSemanticModelSchema)
30
72
  });
31
73
 
32
- // src/schema/policy.ts
33
-
34
- var policyWhenSchema = _zod.z.object({
35
- tags_any: _zod.z.array(_zod.z.string()).optional(),
36
- concept_ids: _zod.z.array(_zod.z.string()).optional(),
37
- status: _zod.z.enum(["draft", "certified", "deprecated"]).optional()
38
- }).passthrough();
39
- var policyThenSchema = _zod.z.object({
40
- require_role: _zod.z.string().optional(),
41
- deny: _zod.z.boolean().optional(),
42
- warn: _zod.z.string().optional()
43
- }).passthrough();
44
- var policyRuleSchema = _zod.z.object({
45
- priority: _zod.z.number(),
46
- when: policyWhenSchema,
47
- then: policyThenSchema
74
+ // src/schema/governance.ts
75
+
76
+ var trustStatusEnum = _zod.z.enum(["endorsed", "warning", "deprecated"]);
77
+ var securityClassificationEnum = _zod.z.enum(["public", "internal", "confidential", "secret"]);
78
+ var tableTypeEnum = _zod.z.enum(["fact", "dimension", "bridge", "snapshot", "event", "aggregate", "view"]);
79
+ var semanticRoleEnum = _zod.z.enum(["metric", "dimension", "identifier", "date"]);
80
+ var defaultAggregationEnum = _zod.z.enum(["SUM", "AVG", "COUNT", "COUNT_DISTINCT", "MIN", "MAX"]);
81
+ var datasetGovernanceSchema = _zod.z.object({
82
+ grain: _zod.z.string(),
83
+ refresh: _zod.z.string().optional(),
84
+ table_type: tableTypeEnum,
85
+ security: securityClassificationEnum.optional()
48
86
  });
49
- var policyFileSchema = _zod.z.object({
50
- id: _zod.z.string(),
51
- name: _zod.z.string().optional(),
52
- description: _zod.z.string(),
53
- owner: _zod.z.string().optional(),
87
+ var fieldGovernanceSchema = _zod.z.object({
88
+ semantic_role: semanticRoleEnum,
89
+ default_aggregation: defaultAggregationEnum.optional(),
90
+ additive: _zod.z.boolean().optional(),
91
+ default_filter: _zod.z.string().optional(),
92
+ sample_values: _zod.z.array(_zod.z.string()).optional()
93
+ });
94
+ var dottedFieldsRecord = _zod.z.record(_zod.z.string(), fieldGovernanceSchema).refine(
95
+ (rec) => Object.keys(rec).every((key) => /^[^.]+\.[^.]+$/.test(key)),
96
+ { message: 'Field keys must use "dataset.field" dot notation (e.g., "orders.amount")' }
97
+ );
98
+ var governanceFileSchema = _zod.z.object({
99
+ model: _zod.z.string(),
100
+ owner: _zod.z.string(),
101
+ trust: trustStatusEnum.optional(),
102
+ security: securityClassificationEnum.optional(),
54
103
  tags: _zod.z.array(_zod.z.string()).optional(),
55
- status: _zod.z.enum(["draft", "certified", "deprecated"]).optional(),
56
- rules: _zod.z.array(policyRuleSchema)
104
+ datasets: _zod.z.record(_zod.z.string(), datasetGovernanceSchema).optional(),
105
+ fields: dottedFieldsRecord.optional()
57
106
  });
58
107
 
59
- // src/schema/entity.ts
108
+ // src/schema/rules.ts
60
109
 
61
- var entityFieldSchema = _zod.z.object({
110
+ var goldenQuerySchema = _zod.z.object({
111
+ question: _zod.z.string(),
112
+ sql: _zod.z.string(),
113
+ dialect: _zod.z.string().optional(),
114
+ tags: _zod.z.array(_zod.z.string()).optional()
115
+ });
116
+ var businessRuleSchema = _zod.z.object({
62
117
  name: _zod.z.string(),
63
- description: _zod.z.string().optional(),
64
- type: _zod.z.string().optional()
118
+ definition: _zod.z.string(),
119
+ enforcement: _zod.z.array(_zod.z.string()).optional(),
120
+ avoid: _zod.z.array(_zod.z.string()).optional(),
121
+ tables: _zod.z.array(_zod.z.string()).optional(),
122
+ applied_always: _zod.z.boolean().optional()
65
123
  });
66
- var entityFileSchema = _zod.z.object({
67
- id: _zod.z.string(),
68
- name: _zod.z.string().optional(),
69
- definition: _zod.z.string().optional(),
70
- description: _zod.z.string().optional(),
71
- owner: _zod.z.string().optional(),
72
- tags: _zod.z.array(_zod.z.string()).optional(),
73
- status: _zod.z.enum(["draft", "certified", "deprecated"]).optional(),
74
- fields: _zod.z.array(entityFieldSchema).optional()
124
+ var guardrailFilterSchema = _zod.z.object({
125
+ name: _zod.z.string(),
126
+ filter: _zod.z.string(),
127
+ tables: _zod.z.array(_zod.z.string()).optional(),
128
+ reason: _zod.z.string()
129
+ });
130
+ var hierarchySchema = _zod.z.object({
131
+ name: _zod.z.string(),
132
+ levels: _zod.z.array(_zod.z.string()),
133
+ dataset: _zod.z.string(),
134
+ field: _zod.z.string().optional()
135
+ });
136
+ var rulesFileSchema = _zod.z.object({
137
+ model: _zod.z.string(),
138
+ golden_queries: _zod.z.array(goldenQuerySchema).optional(),
139
+ business_rules: _zod.z.array(businessRuleSchema).optional(),
140
+ guardrail_filters: _zod.z.array(guardrailFilterSchema).optional(),
141
+ hierarchies: _zod.z.array(hierarchySchema).optional()
142
+ });
143
+
144
+ // src/schema/lineage.ts
145
+
146
+ var lineageTypeEnum = _zod.z.enum(["pipeline", "dashboard", "ml_model", "api", "manual"]);
147
+ var upstreamEntrySchema = _zod.z.object({
148
+ source: _zod.z.string(),
149
+ type: lineageTypeEnum,
150
+ pipeline: _zod.z.string().optional(),
151
+ tool: _zod.z.string().optional(),
152
+ refresh: _zod.z.string().optional(),
153
+ notes: _zod.z.string().optional()
154
+ });
155
+ var downstreamEntrySchema = _zod.z.object({
156
+ target: _zod.z.string(),
157
+ type: lineageTypeEnum,
158
+ tool: _zod.z.string().optional(),
159
+ notes: _zod.z.string().optional()
160
+ });
161
+ var lineageFileSchema = _zod.z.object({
162
+ model: _zod.z.string(),
163
+ upstream: _zod.z.array(upstreamEntrySchema).optional(),
164
+ downstream: _zod.z.array(downstreamEntrySchema).optional()
75
165
  });
76
166
 
77
167
  // src/schema/term.ts
78
168
 
79
169
  var termFileSchema = _zod.z.object({
80
170
  id: _zod.z.string(),
81
- term: _zod.z.string().optional(),
82
171
  definition: _zod.z.string(),
83
172
  synonyms: _zod.z.array(_zod.z.string()).optional(),
84
173
  maps_to: _zod.z.array(_zod.z.string()).optional(),
@@ -93,558 +182,1202 @@ var ownerFileSchema = _zod.z.object({
93
182
  display_name: _zod.z.string(),
94
183
  email: _zod.z.string().optional(),
95
184
  team: _zod.z.string().optional(),
96
- tags: _zod.z.array(_zod.z.string()).optional()
185
+ description: _zod.z.string().optional()
97
186
  });
98
187
 
99
- // src/config/defaults.ts
100
- var DEFAULT_CONFIG = {
101
- project: { id: "my-context", displayName: "My Context", version: "0.1.0" },
102
- paths: {
103
- rootDir: ".",
104
- contextDir: "./context",
105
- distDir: "./dist",
106
- cacheDir: "./.contextkit-cache"
107
- },
108
- site: { enabled: true, title: "Context Site", basePath: "/" },
109
- lint: { defaultSeverity: "warning", rules: {} },
110
- mcp: {
111
- enabled: true,
112
- transport: ["stdio"],
113
- http: { port: 7331, host: "127.0.0.1" }
114
- },
115
- plugins: []
116
- };
188
+ // src/schema/config.ts
117
189
 
118
- // src/config/loader.ts
119
- var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs);
120
- var _path = require('path');
121
- var _yaml = require('yaml');
122
- function deepMerge(base, override) {
123
- const result = { ...base };
124
- for (const key of Object.keys(override)) {
125
- const baseVal = result[key];
126
- const overrideVal = override[key];
127
- if (overrideVal !== null && typeof overrideVal === "object" && !Array.isArray(overrideVal) && baseVal !== null && typeof baseVal === "object" && !Array.isArray(baseVal)) {
128
- result[key] = deepMerge(
129
- baseVal,
130
- overrideVal
131
- );
132
- } else {
133
- result[key] = overrideVal;
134
- }
135
- }
136
- return result;
137
- }
138
- function resolveConfig(partial) {
139
- return deepMerge(
140
- DEFAULT_CONFIG,
141
- partial
142
- );
143
- }
144
- var CONFIG_FILENAMES = [
145
- "contextkit.config.ts",
146
- "contextkit.config.js",
147
- "contextkit.config.yaml",
148
- "contextkit.config.yml"
149
- ];
150
- async function loadConfig(rootDir = ".") {
151
- const absRoot = _path.resolve.call(void 0, rootDir);
152
- for (const filename of CONFIG_FILENAMES) {
153
- const filepath = _path.join.call(void 0, absRoot, filename);
154
- if (!_fs.existsSync.call(void 0, filepath)) {
155
- continue;
156
- }
157
- if (filename.endsWith(".yaml") || filename.endsWith(".yml")) {
158
- const raw = _fs.readFileSync.call(void 0, filepath, "utf-8");
159
- const parsed = _yaml.parse.call(void 0, raw);
160
- return resolveConfig(_nullishCoalesce(parsed, () => ( {})));
161
- }
162
- if (filename.endsWith(".ts") || filename.endsWith(".js")) {
163
- const mod = await Promise.resolve().then(() => _interopRequireWildcard(require(filepath)));
164
- const partial = _nullishCoalesce(mod.default, () => ( mod));
165
- return resolveConfig(partial);
166
- }
167
- }
168
- return { ...DEFAULT_CONFIG };
169
- }
190
+ var metadataTierEnum = _zod.z.enum(["none", "bronze", "silver", "gold"]);
191
+ var severityEnum = _zod.z.enum(["error", "warning"]);
192
+ var severityOrOffEnum = _zod.z.union([severityEnum, _zod.z.literal("off")]);
193
+ var lintConfigSchema = _zod.z.object({
194
+ severity_overrides: _zod.z.record(_zod.z.string(), severityOrOffEnum).optional()
195
+ });
196
+ var siteConfigSchema = _zod.z.object({
197
+ title: _zod.z.string().optional(),
198
+ base_path: _zod.z.string().optional()
199
+ });
200
+ var mcpConfigSchema = _zod.z.object({
201
+ transport: _zod.z.enum(["stdio", "http"]).optional(),
202
+ port: _zod.z.number().optional()
203
+ });
204
+ var contextKitConfigSchema = _zod.z.object({
205
+ context_dir: _zod.z.string().default("context"),
206
+ output_dir: _zod.z.string().default("dist"),
207
+ minimum_tier: metadataTierEnum.optional(),
208
+ lint: lintConfigSchema.optional(),
209
+ site: siteConfigSchema.optional(),
210
+ mcp: mcpConfigSchema.optional()
211
+ });
170
212
 
171
213
  // src/parser/discover.ts
172
214
  var _glob = require('glob');
173
- var CONTEXT_PATTERNS = [
174
- "**/*.ctx.yaml",
175
- "**/*.ctx.yml",
176
- "**/*.policy.yaml",
177
- "**/*.policy.yml",
178
- "**/*.owner.yaml",
179
- "**/*.owner.yml",
180
- "**/*.term.yaml",
181
- "**/*.term.yml",
182
- "**/*.entity.yaml",
183
- "**/*.entity.yml"
184
- ];
215
+ var PATTERNS = {
216
+ model: "**/*.osi.yaml",
217
+ governance: "**/*.governance.yaml",
218
+ rules: "**/*.rules.yaml",
219
+ lineage: "**/*.lineage.yaml",
220
+ term: "**/*.term.yaml",
221
+ owner: "**/*.owner.yaml"
222
+ };
185
223
  async function discoverFiles(contextDir) {
186
- const files = await _glob.glob.call(void 0, CONTEXT_PATTERNS, { cwd: contextDir, absolute: true, nodir: true });
187
- return files.sort();
224
+ const files = [];
225
+ for (const [kind, pattern] of Object.entries(PATTERNS)) {
226
+ const matches = await _glob.glob.call(void 0, pattern, { cwd: contextDir, absolute: true });
227
+ for (const match of matches) {
228
+ files.push({ path: match, kind });
229
+ }
230
+ }
231
+ return files.sort((a, b) => a.path.localeCompare(b.path));
188
232
  }
189
233
 
190
234
  // src/parser/parse.ts
235
+ var _promises = require('fs/promises');
236
+ var _yaml = require('yaml'); var yaml = _interopRequireWildcard(_yaml);
237
+ async function parseFile(filePath, kind) {
238
+ const content = await _promises.readFile.call(void 0, filePath, "utf-8");
239
+ const data = _yaml.parse.call(void 0, content);
240
+ return {
241
+ kind,
242
+ data,
243
+ source: { file: filePath, line: 1, column: 1 }
244
+ };
245
+ }
191
246
 
247
+ // src/compiler/validate.ts
248
+ var SCHEMA_MAP = {
249
+ model: osiDocumentSchema,
250
+ governance: governanceFileSchema,
251
+ rules: rulesFileSchema,
252
+ lineage: lineageFileSchema,
253
+ term: termFileSchema,
254
+ owner: ownerFileSchema
255
+ };
256
+ function zodErrorToDiagnostics(err, source) {
257
+ return err.issues.map((issue) => ({
258
+ ruleId: "schema/invalid",
259
+ severity: "error",
260
+ message: `${issue.path.join(".")}: ${issue.message}`,
261
+ location: { file: source.file, line: source.line, column: source.column },
262
+ fixable: false
263
+ }));
264
+ }
265
+ function validate(parsed) {
266
+ const schema = SCHEMA_MAP[parsed.kind];
267
+ const parseResult = schema.safeParse(parsed.data);
268
+ if (!parseResult.success) {
269
+ return {
270
+ kind: parsed.kind,
271
+ data: void 0,
272
+ diagnostics: zodErrorToDiagnostics(parseResult.error, parsed.source)
273
+ };
274
+ }
275
+ let data = parseResult.data;
276
+ if (parsed.kind === "model") {
277
+ const doc = data;
278
+ data = doc.semantic_model[0];
279
+ }
280
+ return {
281
+ kind: parsed.kind,
282
+ data,
283
+ diagnostics: []
284
+ };
285
+ }
192
286
 
193
-
194
- function inferFileType(filePath) {
195
- const name = _path.basename.call(void 0, filePath);
196
- if (name.endsWith(".policy.yaml") || name.endsWith(".policy.yml")) return "policy";
197
- if (name.endsWith(".owner.yaml") || name.endsWith(".owner.yml")) return "owner";
198
- if (name.endsWith(".term.yaml") || name.endsWith(".term.yml")) return "term";
199
- if (name.endsWith(".entity.yaml") || name.endsWith(".entity.yml")) return "entity";
200
- if (filePath.includes("/products/")) return "product";
201
- if (filePath.includes("/entities/")) return "entity";
202
- if (filePath.includes("/glossary/")) return "term";
203
- return "concept";
287
+ // src/compiler/resolve.ts
288
+ function diag(ruleId, message, file) {
289
+ return {
290
+ ruleId,
291
+ severity: "error",
292
+ message,
293
+ location: { file, line: 1, column: 1 },
294
+ fixable: false
295
+ };
204
296
  }
205
- async function parseFile(filePath) {
206
- const content = _fs.readFileSync.call(void 0, filePath, "utf-8");
207
- const data = _yaml.parse.call(void 0, content);
208
- if (typeof data !== "object" || data === null) {
209
- throw new Error(`Invalid YAML in ${filePath}: expected an object`);
297
+ function datasetNames(model) {
298
+ return new Set(model.datasets.map((d) => d.name));
299
+ }
300
+ function fieldNamesInDataset(model, datasetName) {
301
+ const dataset = model.datasets.find((d) => d.name === datasetName);
302
+ if (!_optionalChain([dataset, 'optionalAccess', _ => _.fields])) return /* @__PURE__ */ new Set();
303
+ return new Set(dataset.fields.map((f) => f.name));
304
+ }
305
+ function resolveReferences(graph) {
306
+ const diagnostics = [];
307
+ for (const [key, gov] of graph.governance) {
308
+ const file = `governance:${key}`;
309
+ if (!graph.models.has(gov.model)) {
310
+ diagnostics.push(
311
+ diag("references/model-exists", `Governance references model "${gov.model}" which does not exist`, file)
312
+ );
313
+ }
314
+ if (!graph.owners.has(gov.owner)) {
315
+ diagnostics.push(
316
+ diag("references/owner-exists", `Governance references owner "${gov.owner}" which does not exist`, file)
317
+ );
318
+ }
319
+ if (gov.datasets) {
320
+ const model = graph.models.get(gov.model);
321
+ const validDatasets = model ? datasetNames(model) : /* @__PURE__ */ new Set();
322
+ for (const dsName of Object.keys(gov.datasets)) {
323
+ if (!validDatasets.has(dsName)) {
324
+ diagnostics.push(
325
+ diag("references/dataset-exists", `Governance references dataset "${dsName}" which does not exist in model "${gov.model}"`, file)
326
+ );
327
+ }
328
+ }
329
+ }
330
+ if (gov.fields) {
331
+ const model = graph.models.get(gov.model);
332
+ for (const fieldKey of Object.keys(gov.fields)) {
333
+ const parts = fieldKey.split(".");
334
+ if (parts.length !== 2) continue;
335
+ const [dsName, fieldName] = parts;
336
+ const validDatasets = model ? datasetNames(model) : /* @__PURE__ */ new Set();
337
+ if (!validDatasets.has(dsName)) {
338
+ diagnostics.push(
339
+ diag("references/dataset-exists", `Governance field key "${fieldKey}" references dataset "${dsName}" which does not exist in model "${gov.model}"`, file)
340
+ );
341
+ } else if (model) {
342
+ const validFields = fieldNamesInDataset(model, dsName);
343
+ if (!validFields.has(fieldName)) {
344
+ diagnostics.push(
345
+ diag("references/field-exists", `Governance field key "${fieldKey}" references field "${fieldName}" which does not exist in dataset "${dsName}"`, file)
346
+ );
347
+ }
348
+ }
349
+ }
350
+ }
351
+ }
352
+ for (const [key, rules] of graph.rules) {
353
+ const file = `rules:${key}`;
354
+ if (!graph.models.has(rules.model)) {
355
+ diagnostics.push(
356
+ diag("references/model-exists", `Rules file references model "${rules.model}" which does not exist`, file)
357
+ );
358
+ }
359
+ const model = graph.models.get(rules.model);
360
+ const validDatasets = model ? datasetNames(model) : /* @__PURE__ */ new Set();
361
+ if (rules.business_rules) {
362
+ for (const rule of rules.business_rules) {
363
+ if (rule.tables) {
364
+ for (const table of rule.tables) {
365
+ if (!validDatasets.has(table)) {
366
+ diagnostics.push(
367
+ diag("references/table-exists", `Business rule "${rule.name}" references table "${table}" which does not exist in model "${rules.model}"`, file)
368
+ );
369
+ }
370
+ }
371
+ }
372
+ }
373
+ }
374
+ if (rules.guardrail_filters) {
375
+ for (const filter of rules.guardrail_filters) {
376
+ if (filter.tables) {
377
+ for (const table of filter.tables) {
378
+ if (!validDatasets.has(table)) {
379
+ diagnostics.push(
380
+ diag("references/table-exists", `Guardrail filter "${filter.name}" references table "${table}" which does not exist in model "${rules.model}"`, file)
381
+ );
382
+ }
383
+ }
384
+ }
385
+ }
386
+ }
387
+ if (rules.hierarchies) {
388
+ for (const hierarchy of rules.hierarchies) {
389
+ if (!validDatasets.has(hierarchy.dataset)) {
390
+ diagnostics.push(
391
+ diag("references/table-exists", `Hierarchy "${hierarchy.name}" references dataset "${hierarchy.dataset}" which does not exist in model "${rules.model}"`, file)
392
+ );
393
+ }
394
+ }
395
+ }
396
+ }
397
+ for (const [key, lineage] of graph.lineage) {
398
+ const file = `lineage:${key}`;
399
+ if (!graph.models.has(lineage.model)) {
400
+ diagnostics.push(
401
+ diag("references/model-exists", `Lineage file references model "${lineage.model}" which does not exist`, file)
402
+ );
403
+ }
210
404
  }
211
- return { filePath, fileType: inferFileType(filePath), data };
405
+ for (const [key, term] of graph.terms) {
406
+ const file = `term:${key}`;
407
+ if (term.owner && !graph.owners.has(term.owner)) {
408
+ diagnostics.push(
409
+ diag("references/owner-exists", `Term "${term.id}" references owner "${term.owner}" which does not exist`, file)
410
+ );
411
+ }
412
+ if (term.maps_to) {
413
+ for (const targetId of term.maps_to) {
414
+ if (!graph.terms.has(targetId)) {
415
+ diagnostics.push(
416
+ diag("references/term-exists", `Term "${term.id}" maps_to term "${targetId}" which does not exist`, file)
417
+ );
418
+ }
419
+ }
420
+ }
421
+ }
422
+ return diagnostics;
212
423
  }
213
424
 
214
- // src/graph/builder.ts
425
+ // src/compiler/graph.ts
215
426
  function createEmptyGraph() {
216
427
  return {
217
- nodes: /* @__PURE__ */ new Map(),
218
- edges: [],
428
+ models: /* @__PURE__ */ new Map(),
429
+ governance: /* @__PURE__ */ new Map(),
430
+ rules: /* @__PURE__ */ new Map(),
431
+ lineage: /* @__PURE__ */ new Map(),
432
+ terms: /* @__PURE__ */ new Map(),
433
+ owners: /* @__PURE__ */ new Map(),
434
+ tiers: /* @__PURE__ */ new Map(),
219
435
  indexes: {
220
- byKind: /* @__PURE__ */ new Map(),
221
436
  byOwner: /* @__PURE__ */ new Map(),
222
437
  byTag: /* @__PURE__ */ new Map(),
223
- byStatus: /* @__PURE__ */ new Map(),
224
- dependents: /* @__PURE__ */ new Map()
438
+ byTrust: /* @__PURE__ */ new Map(),
439
+ modelToGovernance: /* @__PURE__ */ new Map(),
440
+ modelToRules: /* @__PURE__ */ new Map(),
441
+ modelToLineage: /* @__PURE__ */ new Map()
225
442
  }
226
443
  };
227
444
  }
228
- function buildGraph(nodes) {
445
+ function pushToIndex(map, key, value) {
446
+ const existing = map.get(key);
447
+ if (existing) {
448
+ existing.push(value);
449
+ } else {
450
+ map.set(key, [value]);
451
+ }
452
+ }
453
+ function buildGraph(results) {
229
454
  const graph = createEmptyGraph();
230
- for (const node of nodes) {
231
- graph.nodes.set(node.id, node);
455
+ for (const result of results) {
456
+ const hasErrors = result.diagnostics.some((d) => d.severity === "error");
457
+ if (hasErrors || result.data == null) continue;
458
+ switch (result.kind) {
459
+ case "model": {
460
+ const model = result.data;
461
+ graph.models.set(model.name, model);
462
+ break;
463
+ }
464
+ case "governance": {
465
+ const gov = result.data;
466
+ graph.governance.set(gov.model, gov);
467
+ break;
468
+ }
469
+ case "rules": {
470
+ const rules = result.data;
471
+ graph.rules.set(rules.model, rules);
472
+ break;
473
+ }
474
+ case "lineage": {
475
+ const lineage = result.data;
476
+ graph.lineage.set(lineage.model, lineage);
477
+ break;
478
+ }
479
+ case "term": {
480
+ const term = result.data;
481
+ graph.terms.set(term.id, term);
482
+ break;
483
+ }
484
+ case "owner": {
485
+ const owner = result.data;
486
+ graph.owners.set(owner.id, owner);
487
+ break;
488
+ }
489
+ }
232
490
  }
233
- for (const node of nodes) {
234
- appendIndex(graph.indexes.byKind, node.kind, node.id);
235
- if (node.owner) {
236
- appendIndex(graph.indexes.byOwner, node.owner, node.id);
491
+ for (const [govKey, gov] of graph.governance) {
492
+ pushToIndex(graph.indexes.byOwner, gov.owner, govKey);
493
+ if (gov.tags) {
494
+ for (const tag of gov.tags) {
495
+ pushToIndex(graph.indexes.byTag, tag, govKey);
496
+ }
497
+ }
498
+ if (gov.trust) {
499
+ pushToIndex(graph.indexes.byTrust, gov.trust, govKey);
500
+ }
501
+ graph.indexes.modelToGovernance.set(gov.model, govKey);
502
+ }
503
+ for (const [rulesKey, rules] of graph.rules) {
504
+ graph.indexes.modelToRules.set(rules.model, rulesKey);
505
+ }
506
+ for (const [lineageKey, lineage] of graph.lineage) {
507
+ graph.indexes.modelToLineage.set(lineage.model, lineageKey);
508
+ }
509
+ return graph;
510
+ }
511
+
512
+ // src/tier/checks.ts
513
+ function allFieldKeys(model) {
514
+ const keys = [];
515
+ for (const ds of model.datasets) {
516
+ for (const f of _nullishCoalesce(ds.fields, () => ( []))) {
517
+ keys.push(`${ds.name}.${f.name}`);
237
518
  }
238
- if (node.tags) {
239
- for (const tag of node.tags) {
240
- appendIndex(graph.indexes.byTag, tag, node.id);
519
+ }
520
+ return keys;
521
+ }
522
+ function pass(id, label) {
523
+ return { id, label, passed: true };
524
+ }
525
+ function fail(id, label, detail) {
526
+ return { id, label, passed: false, detail };
527
+ }
528
+ function checkBronze(modelName, graph) {
529
+ const results = [];
530
+ const model = graph.models.get(modelName);
531
+ const gov = graph.governance.get(modelName);
532
+ {
533
+ const id = "bronze/model-description";
534
+ const label = "Model has name and description";
535
+ if (model && model.name && model.description) {
536
+ results.push(pass(id, label));
537
+ } else {
538
+ results.push(fail(id, label, "Model is missing name or description"));
539
+ }
540
+ }
541
+ {
542
+ const id = "bronze/dataset-descriptions";
543
+ const label = "All datasets have descriptions";
544
+ if (!model || model.datasets.length === 0) {
545
+ results.push(fail(id, label, "No model or datasets found"));
546
+ } else {
547
+ const missing = model.datasets.filter((ds) => !ds.description);
548
+ if (missing.length === 0) {
549
+ results.push(pass(id, label));
550
+ } else {
551
+ results.push(fail(id, label, `Missing descriptions: ${missing.map((d) => d.name).join(", ")}`));
552
+ }
553
+ }
554
+ }
555
+ {
556
+ const id = "bronze/field-descriptions";
557
+ const label = "All fields have descriptions";
558
+ if (!model) {
559
+ results.push(fail(id, label, "No model found"));
560
+ } else {
561
+ const allFields = model.datasets.flatMap((ds) => _nullishCoalesce(ds.fields, () => ( [])));
562
+ if (allFields.length === 0) {
563
+ results.push(fail(id, label, "No fields defined across any dataset"));
564
+ } else {
565
+ const missing = [];
566
+ for (const ds of model.datasets) {
567
+ for (const f of _nullishCoalesce(ds.fields, () => ( []))) {
568
+ if (!f.description) {
569
+ missing.push(`${ds.name}.${f.name}`);
570
+ }
571
+ }
572
+ }
573
+ if (missing.length === 0) {
574
+ results.push(pass(id, label));
575
+ } else {
576
+ results.push(fail(id, label, `Missing descriptions: ${missing.join(", ")}`));
577
+ }
241
578
  }
242
579
  }
243
- if (node.status) {
244
- appendIndex(graph.indexes.byStatus, node.status, node.id);
580
+ }
581
+ {
582
+ const id = "bronze/owner-resolvable";
583
+ const label = "Owner assigned and resolvable";
584
+ if (gov && gov.owner && graph.owners.has(gov.owner)) {
585
+ results.push(pass(id, label));
586
+ } else if (gov && gov.owner) {
587
+ results.push(fail(id, label, `Owner '${gov.owner}' not found in owners`));
588
+ } else {
589
+ results.push(fail(id, label, "No governance or owner assigned"));
245
590
  }
246
591
  }
247
- for (const node of nodes) {
248
- if (node.owner) {
249
- graph.edges.push({ from: node.id, to: node.owner, type: "owned_by" });
592
+ {
593
+ const id = "bronze/security-classification";
594
+ const label = "Security classification set";
595
+ if (gov && gov.security) {
596
+ results.push(pass(id, label));
597
+ } else {
598
+ results.push(fail(id, label, "No security classification in governance"));
250
599
  }
251
- if (node.kind === "concept") {
252
- const concept = node;
253
- if (concept.dependsOn) {
254
- for (const dep of concept.dependsOn) {
255
- graph.edges.push({ from: concept.id, to: dep, type: "depends_on" });
256
- appendIndex(graph.indexes.dependents, dep, concept.id);
600
+ }
601
+ {
602
+ const id = "bronze/dataset-grain";
603
+ const label = "All datasets have grain statements";
604
+ if (!gov || !gov.datasets) {
605
+ results.push(fail(id, label, "No governance datasets"));
606
+ } else if (!model) {
607
+ results.push(fail(id, label, "No model found"));
608
+ } else {
609
+ const missing = [];
610
+ for (const ds of model.datasets) {
611
+ const dsGov = gov.datasets[ds.name];
612
+ if (!dsGov || !dsGov.grain) {
613
+ missing.push(ds.name);
257
614
  }
258
615
  }
259
- if (concept.productId) {
260
- graph.edges.push({ from: concept.id, to: concept.productId, type: "belongs_to" });
616
+ if (missing.length === 0) {
617
+ results.push(pass(id, label));
618
+ } else {
619
+ results.push(fail(id, label, `Missing grain: ${missing.join(", ")}`));
261
620
  }
262
621
  }
263
- if (node.kind === "term") {
264
- const term = node;
265
- if (term.mapsTo) {
266
- for (const target of term.mapsTo) {
267
- graph.edges.push({ from: term.id, to: target, type: "maps_to" });
622
+ }
623
+ {
624
+ const id = "bronze/dataset-table-type";
625
+ const label = "All datasets have table_type";
626
+ if (!gov || !gov.datasets) {
627
+ results.push(fail(id, label, "No governance datasets"));
628
+ } else if (!model) {
629
+ results.push(fail(id, label, "No model found"));
630
+ } else {
631
+ const missing = [];
632
+ for (const ds of model.datasets) {
633
+ const dsGov = gov.datasets[ds.name];
634
+ if (!dsGov || !dsGov.table_type) {
635
+ missing.push(ds.name);
268
636
  }
269
637
  }
638
+ if (missing.length === 0) {
639
+ results.push(pass(id, label));
640
+ } else {
641
+ results.push(fail(id, label, `Missing table_type: ${missing.join(", ")}`));
642
+ }
270
643
  }
271
644
  }
272
- return graph;
645
+ return results;
273
646
  }
274
- function appendIndex(map, key, value) {
275
- const existing = map.get(key);
276
- if (existing) {
277
- existing.push(value);
278
- } else {
279
- map.set(key, [value]);
647
+ function checkSilver(modelName, graph) {
648
+ const results = [];
649
+ const model = graph.models.get(modelName);
650
+ const gov = graph.governance.get(modelName);
651
+ const lineageKey = graph.indexes.modelToLineage.get(modelName);
652
+ const lineage = lineageKey ? graph.lineage.get(lineageKey) : void 0;
653
+ {
654
+ const id = "silver/trust-status";
655
+ const label = "Trust status is set";
656
+ if (gov && gov.trust) {
657
+ results.push(pass(id, label));
658
+ } else {
659
+ results.push(fail(id, label, "No trust status in governance"));
660
+ }
280
661
  }
281
- }
282
-
283
- // src/compiler/validate.ts
284
- var SCHEMAS = {
285
- concept: conceptFileSchema,
286
- product: productFileSchema,
287
- policy: policyFileSchema,
288
- entity: entityFileSchema,
289
- term: termFileSchema,
290
- owner: ownerFileSchema
291
- };
292
- function validateFile(parsed) {
293
- const schema = SCHEMAS[parsed.fileType];
294
- const result = schema.safeParse(parsed.data);
295
- if (!result.success) {
296
- const diagnostics = result.error.issues.map((issue) => ({
297
- ruleId: "schema/invalid",
298
- severity: "error",
299
- message: `${issue.path.join(".")}: ${issue.message}`,
300
- source: { file: parsed.filePath, line: 1, col: 1 },
301
- fixable: false
302
- }));
303
- return { diagnostics };
304
- }
305
- const data = result.data;
306
- const source = { file: parsed.filePath, line: 1, col: 1 };
307
- let node;
308
- switch (parsed.fileType) {
309
- case "concept": {
310
- const d = data;
311
- node = {
312
- id: d.id,
313
- kind: "concept",
314
- source,
315
- definition: d.definition,
316
- owner: d.owner,
317
- tags: d.tags,
318
- status: d.status,
319
- certified: d.certified,
320
- productId: d.product_id,
321
- dependsOn: d.depends_on,
322
- evidence: d.evidence,
323
- examples: d.examples,
324
- description: d.description
325
- };
326
- break;
327
- }
328
- case "product": {
329
- const d = data;
330
- node = {
331
- id: d.id,
332
- kind: "product",
333
- source,
334
- description: d.description,
335
- owner: d.owner,
336
- tags: d.tags,
337
- status: d.status
338
- };
339
- break;
340
- }
341
- case "policy": {
342
- const d = data;
343
- node = {
344
- id: d.id,
345
- kind: "policy",
346
- source,
347
- description: d.description,
348
- owner: d.owner,
349
- tags: d.tags,
350
- status: d.status,
351
- rules: d.rules.map((r) => ({
352
- priority: r.priority,
353
- when: {
354
- tagsAny: r.when.tags_any,
355
- conceptIds: r.when.concept_ids,
356
- status: r.when.status
357
- },
358
- then: {
359
- requireRole: r.then.require_role,
360
- deny: r.then.deny,
361
- warn: r.then.warn
662
+ {
663
+ const id = "silver/min-tags";
664
+ const label = "At least 2 tags";
665
+ if (gov && gov.tags && gov.tags.length >= 2) {
666
+ results.push(pass(id, label));
667
+ } else {
668
+ const count = _nullishCoalesce(_optionalChain([gov, 'optionalAccess', _2 => _2.tags, 'optionalAccess', _3 => _3.length]), () => ( 0));
669
+ results.push(fail(id, label, `Found ${count} tag(s), need at least 2`));
670
+ }
671
+ }
672
+ {
673
+ const id = "silver/glossary-linked";
674
+ const label = "Glossary term linked";
675
+ let linked = false;
676
+ const govTags = new Set(_nullishCoalesce(_optionalChain([gov, 'optionalAccess', _4 => _4.tags]), () => ( [])));
677
+ const govOwner = _optionalChain([gov, 'optionalAccess', _5 => _5.owner]);
678
+ for (const [, term] of graph.terms) {
679
+ if (term.tags) {
680
+ for (const tag of term.tags) {
681
+ if (govTags.has(tag)) {
682
+ linked = true;
683
+ break;
362
684
  }
363
- }))
364
- };
365
- break;
366
- }
367
- case "entity": {
368
- const d = data;
369
- node = {
370
- id: d.id,
371
- kind: "entity",
372
- source,
373
- definition: d.definition,
374
- owner: d.owner,
375
- tags: d.tags,
376
- status: d.status,
377
- fields: d.fields,
378
- description: d.description
379
- };
380
- break;
381
- }
382
- case "term": {
383
- const d = data;
384
- node = {
385
- id: d.id,
386
- kind: "term",
387
- source,
388
- definition: d.definition,
389
- owner: d.owner,
390
- tags: d.tags,
391
- synonyms: d.synonyms,
392
- mapsTo: d.maps_to
393
- };
394
- break;
395
- }
396
- case "owner": {
397
- const d = data;
398
- node = {
399
- id: d.id,
400
- kind: "owner",
401
- source,
402
- displayName: d.display_name,
403
- email: d.email,
404
- team: d.team,
405
- tags: d.tags
406
- };
407
- break;
408
- }
409
- }
410
- return { node, diagnostics: [] };
685
+ }
686
+ }
687
+ if (term.owner && term.owner === govOwner) {
688
+ linked = true;
689
+ }
690
+ if (linked) break;
691
+ }
692
+ if (graph.terms.size === 0) {
693
+ results.push(fail(id, label, "No glossary terms defined"));
694
+ } else if (linked) {
695
+ results.push(pass(id, label));
696
+ } else {
697
+ results.push(fail(id, label, "No glossary term shares tags or owner with this model"));
698
+ }
699
+ }
700
+ {
701
+ const id = "silver/upstream-lineage";
702
+ const label = "Upstream lineage exists";
703
+ if (lineage && lineage.upstream && lineage.upstream.length > 0) {
704
+ results.push(pass(id, label));
705
+ } else {
706
+ results.push(fail(id, label, "No upstream lineage defined"));
707
+ }
708
+ }
709
+ {
710
+ const id = "silver/dataset-refresh";
711
+ const label = "All datasets have refresh cadence";
712
+ if (!gov || !gov.datasets || !model) {
713
+ results.push(fail(id, label, "No governance datasets or model"));
714
+ } else {
715
+ const missing = [];
716
+ for (const ds of model.datasets) {
717
+ const dsGov = gov.datasets[ds.name];
718
+ if (!dsGov || !dsGov.refresh) {
719
+ missing.push(ds.name);
720
+ }
721
+ }
722
+ if (missing.length === 0) {
723
+ results.push(pass(id, label));
724
+ } else {
725
+ results.push(fail(id, label, `Missing refresh: ${missing.join(", ")}`));
726
+ }
727
+ }
728
+ }
729
+ {
730
+ const id = "silver/sample-values";
731
+ const label = "At least 2 fields have sample_values";
732
+ let count = 0;
733
+ if (gov && gov.fields) {
734
+ for (const [, fg] of Object.entries(gov.fields)) {
735
+ if (fg.sample_values && fg.sample_values.length > 0) {
736
+ count++;
737
+ }
738
+ }
739
+ }
740
+ if (count >= 2) {
741
+ results.push(pass(id, label));
742
+ } else {
743
+ results.push(fail(id, label, `Found ${count} field(s) with sample_values, need at least 2`));
744
+ }
745
+ }
746
+ return results;
747
+ }
748
+ function checkGold(modelName, graph) {
749
+ const results = [];
750
+ const model = graph.models.get(modelName);
751
+ const gov = graph.governance.get(modelName);
752
+ const rulesKey = graph.indexes.modelToRules.get(modelName);
753
+ const rules = rulesKey ? graph.rules.get(rulesKey) : void 0;
754
+ const fieldKeys = model ? allFieldKeys(model) : [];
755
+ {
756
+ const id = "gold/field-semantic-role";
757
+ const label = "Every field has semantic_role";
758
+ if (!gov || !gov.fields) {
759
+ results.push(fail(id, label, "No governance fields"));
760
+ } else if (fieldKeys.length === 0) {
761
+ results.push(fail(id, label, "Model has no fields to verify"));
762
+ } else {
763
+ const missing = fieldKeys.filter((k) => !_optionalChain([gov, 'access', _6 => _6.fields, 'access', _7 => _7[k], 'optionalAccess', _8 => _8.semantic_role]));
764
+ if (missing.length === 0) {
765
+ results.push(pass(id, label));
766
+ } else {
767
+ results.push(fail(id, label, `Missing semantic_role: ${missing.join(", ")}`));
768
+ }
769
+ }
770
+ }
771
+ {
772
+ const id = "gold/metric-aggregation";
773
+ const label = "Every metric field has default_aggregation";
774
+ if (!gov || !gov.fields) {
775
+ results.push(fail(id, label, "No governance fields"));
776
+ } else {
777
+ const metricFields = Object.entries(gov.fields).filter(([, fg]) => fg.semantic_role === "metric");
778
+ if (metricFields.length === 0) {
779
+ results.push(fail(id, label, "No metric fields found"));
780
+ } else {
781
+ const missing = metricFields.filter(([, fg]) => !fg.default_aggregation);
782
+ if (missing.length === 0) {
783
+ results.push(pass(id, label));
784
+ } else {
785
+ results.push(fail(id, label, `Missing default_aggregation: ${missing.map(([k]) => k).join(", ")}`));
786
+ }
787
+ }
788
+ }
789
+ }
790
+ {
791
+ const id = "gold/metric-additive";
792
+ const label = "Every metric field has additive flag";
793
+ if (!gov || !gov.fields) {
794
+ results.push(fail(id, label, "No governance fields"));
795
+ } else {
796
+ const metricFields = Object.entries(gov.fields).filter(([, fg]) => fg.semantic_role === "metric");
797
+ if (metricFields.length === 0) {
798
+ results.push(fail(id, label, "No metric fields found"));
799
+ } else {
800
+ const missing = metricFields.filter(([, fg]) => fg.additive === void 0);
801
+ if (missing.length === 0) {
802
+ results.push(pass(id, label));
803
+ } else {
804
+ results.push(fail(id, label, `Missing additive flag: ${missing.map(([k]) => k).join(", ")}`));
805
+ }
806
+ }
807
+ }
808
+ }
809
+ {
810
+ const id = "gold/guardrail-filter";
811
+ const label = "At least 1 guardrail_filter exists";
812
+ if (rules && rules.guardrail_filters && rules.guardrail_filters.length >= 1) {
813
+ results.push(pass(id, label));
814
+ } else {
815
+ results.push(fail(id, label, "No guardrail filters found"));
816
+ }
817
+ }
818
+ {
819
+ const id = "gold/golden-queries";
820
+ const label = "At least 3 golden_queries exist";
821
+ const count = _nullishCoalesce(_optionalChain([rules, 'optionalAccess', _9 => _9.golden_queries, 'optionalAccess', _10 => _10.length]), () => ( 0));
822
+ if (count >= 3) {
823
+ results.push(pass(id, label));
824
+ } else {
825
+ results.push(fail(id, label, `Found ${count} golden queries, need at least 3`));
826
+ }
827
+ }
828
+ {
829
+ const id = "gold/business-rule";
830
+ const label = "At least 1 business_rule exists";
831
+ if (rules && rules.business_rules && rules.business_rules.length >= 1) {
832
+ results.push(pass(id, label));
833
+ } else {
834
+ results.push(fail(id, label, "No business rules found"));
835
+ }
836
+ }
837
+ {
838
+ const id = "gold/hierarchy";
839
+ const label = "At least 1 hierarchy exists";
840
+ if (rules && rules.hierarchies && rules.hierarchies.length >= 1) {
841
+ results.push(pass(id, label));
842
+ } else {
843
+ results.push(fail(id, label, "No hierarchies found"));
844
+ }
845
+ }
846
+ {
847
+ const id = "gold/default-filter";
848
+ const label = "At least 1 field has default_filter";
849
+ let found = false;
850
+ if (gov && gov.fields) {
851
+ for (const [, fg] of Object.entries(gov.fields)) {
852
+ if (fg.default_filter) {
853
+ found = true;
854
+ break;
855
+ }
856
+ }
857
+ }
858
+ if (found) {
859
+ results.push(pass(id, label));
860
+ } else {
861
+ results.push(fail(id, label, "No fields have default_filter"));
862
+ }
863
+ }
864
+ {
865
+ const id = "gold/trust-endorsed";
866
+ const label = "Trust is endorsed";
867
+ if (gov && gov.trust === "endorsed") {
868
+ results.push(pass(id, label));
869
+ } else {
870
+ results.push(fail(id, label, `Trust is '${_nullishCoalesce(_optionalChain([gov, 'optionalAccess', _11 => _11.trust]), () => ( "unset"))}', not 'endorsed'`));
871
+ }
872
+ }
873
+ {
874
+ const id = "gold/security-controls";
875
+ const label = "Security controls adequate";
876
+ const hasModelSecurity = !!(gov && gov.security);
877
+ let hasDatasetSecurity = false;
878
+ if (gov && gov.datasets) {
879
+ for (const [, dsGov] of Object.entries(gov.datasets)) {
880
+ if (dsGov.security) {
881
+ hasDatasetSecurity = true;
882
+ break;
883
+ }
884
+ }
885
+ }
886
+ if (hasModelSecurity || hasDatasetSecurity) {
887
+ results.push(pass(id, label));
888
+ } else {
889
+ results.push(fail(id, label, "No security classification at model or dataset level"));
890
+ }
891
+ }
892
+ return results;
411
893
  }
412
894
 
413
- // src/compiler/normalize.ts
414
- function toKebabCase(input) {
415
- return input.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[_\s]+/g, "-").replace(/-+/g, "-").toLowerCase();
895
+ // src/tier/compute.ts
896
+ function computeTier(modelName, graph) {
897
+ const bronzeChecks = checkBronze(modelName, graph);
898
+ const silverChecks = checkSilver(modelName, graph);
899
+ const goldChecks = checkGold(modelName, graph);
900
+ const bronzePassed = bronzeChecks.every((c) => c.passed);
901
+ const silverPassed = silverChecks.every((c) => c.passed);
902
+ const goldPassed = goldChecks.every((c) => c.passed);
903
+ let tier;
904
+ if (bronzePassed && silverPassed && goldPassed) {
905
+ tier = "gold";
906
+ } else if (bronzePassed && silverPassed) {
907
+ tier = "silver";
908
+ } else if (bronzePassed) {
909
+ tier = "bronze";
910
+ } else {
911
+ tier = "none";
912
+ }
913
+ return {
914
+ model: modelName,
915
+ tier,
916
+ bronze: { passed: bronzePassed, checks: bronzeChecks },
917
+ silver: { passed: silverPassed, checks: silverChecks },
918
+ gold: { passed: goldPassed, checks: goldChecks }
919
+ };
416
920
  }
417
- function normalizeNode(node) {
418
- const normalized = { ...node, id: toKebabCase(node.id) };
419
- if (normalized.tags) {
420
- normalized.tags = normalized.tags.map((t) => t.toLowerCase());
921
+ function computeAllTiers(graph) {
922
+ for (const modelName of graph.models.keys()) {
923
+ const score = computeTier(modelName, graph);
924
+ graph.tiers.set(modelName, score);
421
925
  }
422
- return normalized;
423
926
  }
424
927
 
425
928
  // src/compiler/pipeline.ts
426
929
  async function compile(options) {
427
- const diagnostics = [];
428
- const nodes = [];
429
- const files = await discoverFiles(options.contextDir);
430
- for (const filePath of files) {
431
- const parsed = await parseFile(filePath);
432
- const { node, diagnostics: fileDiags } = validateFile(parsed);
433
- diagnostics.push(...fileDiags);
434
- if (!node) {
435
- continue;
436
- }
437
- const normalized = normalizeNode(node);
438
- nodes.push(normalized);
439
- }
440
- const graph = buildGraph(nodes);
441
- return { graph, diagnostics };
930
+ const allDiagnostics = [];
931
+ const discovered = await discoverFiles(options.contextDir);
932
+ const parsed = await Promise.all(
933
+ discovered.map((f) => parseFile(f.path, f.kind))
934
+ );
935
+ const validated = parsed.map(validate);
936
+ for (const result of validated) {
937
+ allDiagnostics.push(...result.diagnostics);
938
+ }
939
+ const graph = buildGraph(validated);
940
+ const refDiagnostics = resolveReferences(graph);
941
+ allDiagnostics.push(...refDiagnostics);
942
+ computeAllTiers(graph);
943
+ return { graph, diagnostics: allDiagnostics };
442
944
  }
443
945
 
444
946
  // src/compiler/emit.ts
445
- var _child_process = require('child_process');
446
- function getGitRevision() {
447
- try {
448
- return _child_process.execFileSync.call(void 0, "git", ["rev-parse", "--short", "HEAD"], {
449
- encoding: "utf-8",
450
- stdio: ["pipe", "pipe", "pipe"]
451
- }).trim();
452
- } catch (e2) {
453
- return "unknown";
947
+ function mapToRecord(map) {
948
+ const record = {};
949
+ for (const [key, value] of map) {
950
+ record[key] = value;
454
951
  }
952
+ return record;
953
+ }
954
+ function emitManifest(graph, _config) {
955
+ return {
956
+ version: "0.2.0",
957
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
958
+ models: mapToRecord(graph.models),
959
+ governance: mapToRecord(graph.governance),
960
+ rules: mapToRecord(graph.rules),
961
+ lineage: mapToRecord(graph.lineage),
962
+ terms: mapToRecord(graph.terms),
963
+ owners: mapToRecord(graph.owners),
964
+ tiers: mapToRecord(graph.tiers)
965
+ };
455
966
  }
456
- function emitManifest(graph, config) {
457
- const concepts = [];
458
- const products = [];
459
- const policies = [];
460
- const entities = [];
461
- const terms = [];
462
- const owners = [];
463
- const byId = {};
464
- for (const node of graph.nodes.values()) {
465
- switch (node.kind) {
466
- case "concept": {
467
- const c = node;
468
- byId[c.id] = { kind: "concept", index: concepts.length };
469
- concepts.push({
470
- id: c.id,
471
- definition: c.definition,
472
- productId: c.productId,
473
- certified: c.certified,
474
- owner: c.owner,
475
- tags: c.tags,
476
- dependsOn: c.dependsOn
967
+
968
+ // src/linter/engine.ts
969
+ var LintEngine = (_class = class {
970
+ __init() {this.rules = []}
971
+ __init2() {this.overrides = {}}
972
+ constructor(overrides) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);
973
+ if (overrides) this.overrides = overrides;
974
+ }
975
+ register(rule) {
976
+ this.rules.push(rule);
977
+ }
978
+ run(graph) {
979
+ const diagnostics = [];
980
+ for (const rule of this.rules) {
981
+ const override = this.overrides[rule.id];
982
+ if (override === "off") continue;
983
+ const results = rule.run(graph);
984
+ for (const d of results) {
985
+ diagnostics.push({
986
+ ...d,
987
+ severity: _nullishCoalesce(override, () => ( rule.defaultSeverity))
477
988
  });
478
- break;
479
989
  }
480
- case "product": {
481
- const p = node;
482
- byId[p.id] = { kind: "product", index: products.length };
483
- products.push({
484
- id: p.id,
485
- description: p.description,
486
- owner: p.owner,
487
- tags: p.tags
990
+ }
991
+ return diagnostics.sort(
992
+ (a, b) => a.location.file.localeCompare(b.location.file) || a.location.line - b.location.line
993
+ );
994
+ }
995
+ }, _class);
996
+
997
+ // src/linter/rules/naming-id-kebab-case.ts
998
+ var KEBAB_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
999
+ var namingIdKebabCase = {
1000
+ id: "naming/id-kebab-case",
1001
+ defaultSeverity: "warning",
1002
+ description: "Owner and term IDs must be kebab-case",
1003
+ fixable: false,
1004
+ run(graph) {
1005
+ const diagnostics = [];
1006
+ for (const [key, owner] of graph.owners) {
1007
+ if (!KEBAB_RE.test(owner.id)) {
1008
+ diagnostics.push({
1009
+ ruleId: this.id,
1010
+ severity: this.defaultSeverity,
1011
+ message: `Owner ID "${owner.id}" is not kebab-case`,
1012
+ location: { file: `owner:${key}`, line: 1, column: 1 },
1013
+ fixable: false
488
1014
  });
489
- break;
490
1015
  }
491
- case "policy": {
492
- const p = node;
493
- byId[p.id] = { kind: "policy", index: policies.length };
494
- policies.push({
495
- id: p.id,
496
- description: p.description,
497
- rules: p.rules,
498
- owner: p.owner,
499
- tags: p.tags
1016
+ }
1017
+ for (const [key, term] of graph.terms) {
1018
+ if (!KEBAB_RE.test(term.id)) {
1019
+ diagnostics.push({
1020
+ ruleId: this.id,
1021
+ severity: this.defaultSeverity,
1022
+ message: `Term ID "${term.id}" is not kebab-case`,
1023
+ location: { file: `term:${key}`, line: 1, column: 1 },
1024
+ fixable: false
1025
+ });
1026
+ }
1027
+ }
1028
+ return diagnostics;
1029
+ }
1030
+ };
1031
+
1032
+ // src/linter/rules/descriptions-required.ts
1033
+ var descriptionsRequired = {
1034
+ id: "osi/descriptions-required",
1035
+ defaultSeverity: "warning",
1036
+ description: "OSI models, datasets, and fields must have descriptions",
1037
+ fixable: false,
1038
+ run(graph) {
1039
+ const diagnostics = [];
1040
+ for (const [key, model] of graph.models) {
1041
+ const file = `model:${key}`;
1042
+ if (!model.description) {
1043
+ diagnostics.push({
1044
+ ruleId: this.id,
1045
+ severity: this.defaultSeverity,
1046
+ message: `Model "${model.name}" is missing a description`,
1047
+ location: { file, line: 1, column: 1 },
1048
+ fixable: false
500
1049
  });
501
- break;
502
1050
  }
503
- case "entity": {
504
- const e = node;
505
- byId[e.id] = { kind: "entity", index: entities.length };
506
- entities.push({
507
- id: e.id,
508
- definition: e.definition,
509
- fields: e.fields,
510
- owner: e.owner,
511
- tags: e.tags
1051
+ for (const dataset of model.datasets) {
1052
+ if (!dataset.description) {
1053
+ diagnostics.push({
1054
+ ruleId: this.id,
1055
+ severity: this.defaultSeverity,
1056
+ message: `Dataset "${dataset.name}" in model "${model.name}" is missing a description`,
1057
+ location: { file, line: 1, column: 1 },
1058
+ fixable: false
1059
+ });
1060
+ }
1061
+ if (dataset.fields) {
1062
+ for (const field of dataset.fields) {
1063
+ if (!field.description) {
1064
+ diagnostics.push({
1065
+ ruleId: this.id,
1066
+ severity: this.defaultSeverity,
1067
+ message: `Field "${field.name}" in dataset "${dataset.name}" of model "${model.name}" is missing a description`,
1068
+ location: { file, line: 1, column: 1 },
1069
+ fixable: false
1070
+ });
1071
+ }
1072
+ }
1073
+ }
1074
+ }
1075
+ }
1076
+ return diagnostics;
1077
+ }
1078
+ };
1079
+
1080
+ // src/linter/rules/ownership-required.ts
1081
+ var ownershipRequired = {
1082
+ id: "governance/ownership-required",
1083
+ defaultSeverity: "error",
1084
+ description: "Every governance file must have an owner field set",
1085
+ fixable: false,
1086
+ run(graph) {
1087
+ const diagnostics = [];
1088
+ for (const [key, gov] of graph.governance) {
1089
+ if (!gov.owner) {
1090
+ diagnostics.push({
1091
+ ruleId: this.id,
1092
+ severity: this.defaultSeverity,
1093
+ message: `Governance for model "${gov.model}" is missing an owner`,
1094
+ location: { file: `governance:${key}`, line: 1, column: 1 },
1095
+ fixable: false
512
1096
  });
513
- break;
514
1097
  }
515
- case "term": {
516
- const t = node;
517
- byId[t.id] = { kind: "term", index: terms.length };
518
- terms.push({
519
- id: t.id,
520
- definition: t.definition,
521
- synonyms: t.synonyms,
522
- mapsTo: t.mapsTo,
523
- owner: t.owner,
524
- tags: t.tags
1098
+ }
1099
+ return diagnostics;
1100
+ }
1101
+ };
1102
+
1103
+ // src/linter/rules/references-resolvable.ts
1104
+ var referencesResolvable = {
1105
+ id: "references/resolvable",
1106
+ defaultSeverity: "error",
1107
+ description: "All cross-file references must resolve to existing entities",
1108
+ fixable: false,
1109
+ run(graph) {
1110
+ return resolveReferences(graph);
1111
+ }
1112
+ };
1113
+
1114
+ // src/linter/rules/glossary-no-duplicate-terms.ts
1115
+ var glossaryNoDuplicateTerms = {
1116
+ id: "glossary/no-duplicate-synonyms",
1117
+ defaultSeverity: "warning",
1118
+ description: "No two terms should share the same synonym string",
1119
+ fixable: false,
1120
+ run(graph) {
1121
+ const diagnostics = [];
1122
+ const seen = /* @__PURE__ */ new Map();
1123
+ for (const [key, term] of graph.terms) {
1124
+ if (!term.synonyms) continue;
1125
+ for (const synonym of term.synonyms) {
1126
+ const lower = synonym.toLowerCase();
1127
+ const existing = seen.get(lower);
1128
+ if (existing && existing !== term.id) {
1129
+ diagnostics.push({
1130
+ ruleId: this.id,
1131
+ severity: this.defaultSeverity,
1132
+ message: `Synonym "${synonym}" is used by both term "${existing}" and term "${term.id}"`,
1133
+ location: { file: `term:${key}`, line: 1, column: 1 },
1134
+ fixable: false
1135
+ });
1136
+ } else {
1137
+ seen.set(lower, term.id);
1138
+ }
1139
+ }
1140
+ }
1141
+ return diagnostics;
1142
+ }
1143
+ };
1144
+
1145
+ // src/linter/rules/no-secrets.ts
1146
+ var SECRET_PATTERNS = [
1147
+ /password\s*[=:]\s*\S+/i,
1148
+ /api[_-]?key\s*[=:]\s*\S+/i,
1149
+ /secret\s*[=:]\s*\S+/i,
1150
+ /token\s*[=:]\s*\S+/i,
1151
+ /\bsk-[a-zA-Z0-9]{10,}\b/,
1152
+ /\bAKIA[A-Z0-9]{16}\b/
1153
+ // AWS access key
1154
+ ];
1155
+ function checkString(value, context, file, diagnostics, ruleId, severity) {
1156
+ if (!value) return;
1157
+ for (const pattern of SECRET_PATTERNS) {
1158
+ if (pattern.test(value)) {
1159
+ diagnostics.push({
1160
+ ruleId,
1161
+ severity,
1162
+ message: `Potential secret detected in ${context}: matches pattern ${pattern.source}`,
1163
+ location: { file, line: 1, column: 1 },
1164
+ fixable: false
1165
+ });
1166
+ return;
1167
+ }
1168
+ }
1169
+ }
1170
+ function aiContextToString(ctx) {
1171
+ if (!ctx) return void 0;
1172
+ if (typeof ctx === "string") return ctx;
1173
+ const parts = [];
1174
+ if (ctx.instructions) parts.push(ctx.instructions);
1175
+ if (ctx.synonyms) parts.push(ctx.synonyms.join(" "));
1176
+ if (ctx.examples) parts.push(ctx.examples.join(" "));
1177
+ return parts.join(" ") || void 0;
1178
+ }
1179
+ var noSecrets = {
1180
+ id: "security/no-secrets",
1181
+ defaultSeverity: "error",
1182
+ description: "Scan string values for patterns that look like secrets",
1183
+ fixable: false,
1184
+ run(graph) {
1185
+ const diagnostics = [];
1186
+ for (const [key, model] of graph.models) {
1187
+ const file = `model:${key}`;
1188
+ checkString(model.description, `model "${model.name}" description`, file, diagnostics, this.id, this.defaultSeverity);
1189
+ checkString(aiContextToString(model.ai_context), `model "${model.name}" ai_context`, file, diagnostics, this.id, this.defaultSeverity);
1190
+ for (const ds of model.datasets) {
1191
+ checkString(ds.description, `dataset "${ds.name}" description`, file, diagnostics, this.id, this.defaultSeverity);
1192
+ checkString(aiContextToString(ds.ai_context), `dataset "${ds.name}" ai_context`, file, diagnostics, this.id, this.defaultSeverity);
1193
+ if (ds.fields) {
1194
+ for (const f of ds.fields) {
1195
+ checkString(f.description, `field "${f.name}" description`, file, diagnostics, this.id, this.defaultSeverity);
1196
+ checkString(aiContextToString(f.ai_context), `field "${f.name}" ai_context`, file, diagnostics, this.id, this.defaultSeverity);
1197
+ }
1198
+ }
1199
+ }
1200
+ }
1201
+ for (const [key, gov] of graph.governance) {
1202
+ const file = `governance:${key}`;
1203
+ if (gov.datasets) {
1204
+ for (const [dsName, ds] of Object.entries(gov.datasets)) {
1205
+ checkString(ds.grain, `dataset "${dsName}" grain`, file, diagnostics, this.id, this.defaultSeverity);
1206
+ }
1207
+ }
1208
+ }
1209
+ for (const [key, rules] of graph.rules) {
1210
+ const file = `rules:${key}`;
1211
+ if (rules.business_rules) {
1212
+ for (const br of rules.business_rules) {
1213
+ checkString(br.definition, `business rule "${br.name}" definition`, file, diagnostics, this.id, this.defaultSeverity);
1214
+ }
1215
+ }
1216
+ }
1217
+ return diagnostics;
1218
+ }
1219
+ };
1220
+
1221
+ // src/linter/rules/osi-valid-schema.ts
1222
+ var osiValidSchema = {
1223
+ id: "osi/valid-schema",
1224
+ defaultSeverity: "error",
1225
+ description: "OSI models must have at least one dataset",
1226
+ fixable: false,
1227
+ run(graph) {
1228
+ const diagnostics = [];
1229
+ for (const [key, model] of graph.models) {
1230
+ if (!model.datasets || model.datasets.length === 0) {
1231
+ diagnostics.push({
1232
+ ruleId: this.id,
1233
+ severity: this.defaultSeverity,
1234
+ message: `Model "${model.name}" has no datasets`,
1235
+ location: { file: `model:${key}`, line: 1, column: 1 },
1236
+ fixable: false
525
1237
  });
526
- break;
527
1238
  }
528
- case "owner": {
529
- const o = node;
530
- byId[o.id] = { kind: "owner", index: owners.length };
531
- owners.push({
532
- id: o.id,
533
- displayName: o.displayName,
534
- email: o.email,
535
- team: o.team
1239
+ }
1240
+ return diagnostics;
1241
+ }
1242
+ };
1243
+
1244
+ // src/linter/rules/governance-model-exists.ts
1245
+ var governanceModelExists = {
1246
+ id: "governance/model-exists",
1247
+ defaultSeverity: "error",
1248
+ description: "Every governance file must reference a model that exists in the graph",
1249
+ fixable: false,
1250
+ run(graph) {
1251
+ const diagnostics = [];
1252
+ for (const [key, gov] of graph.governance) {
1253
+ if (!graph.models.has(gov.model)) {
1254
+ diagnostics.push({
1255
+ ruleId: this.id,
1256
+ severity: this.defaultSeverity,
1257
+ message: `Governance references model "${gov.model}" which does not exist`,
1258
+ location: { file: `governance:${key}`, line: 1, column: 1 },
1259
+ fixable: false
536
1260
  });
537
- break;
538
1261
  }
539
1262
  }
1263
+ return diagnostics;
1264
+ }
1265
+ };
1266
+
1267
+ // src/linter/rules/governance-datasets-exist.ts
1268
+ var governanceDatasetsExist = {
1269
+ id: "governance/datasets-exist",
1270
+ defaultSeverity: "error",
1271
+ description: "Every dataset key in governance must exist as a dataset in the referenced OSI model",
1272
+ fixable: false,
1273
+ run(graph) {
1274
+ const diagnostics = [];
1275
+ for (const [key, gov] of graph.governance) {
1276
+ if (!gov.datasets) continue;
1277
+ const model = graph.models.get(gov.model);
1278
+ if (!model) continue;
1279
+ const validDatasets = new Set(model.datasets.map((d) => d.name));
1280
+ for (const dsName of Object.keys(gov.datasets)) {
1281
+ if (!validDatasets.has(dsName)) {
1282
+ diagnostics.push({
1283
+ ruleId: this.id,
1284
+ severity: this.defaultSeverity,
1285
+ message: `Governance dataset "${dsName}" does not exist in model "${gov.model}"`,
1286
+ location: { file: `governance:${key}`, line: 1, column: 1 },
1287
+ fixable: false
1288
+ });
1289
+ }
1290
+ }
1291
+ }
1292
+ return diagnostics;
540
1293
  }
541
- return {
542
- schemaVersion: "1.0.0",
543
- project: {
544
- id: config.project.id,
545
- displayName: config.project.displayName,
546
- version: config.project.version
547
- },
548
- build: {
549
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
550
- version: getGitRevision(),
551
- nodeCount: graph.nodes.size
552
- },
553
- concepts,
554
- products,
555
- policies,
556
- entities,
557
- terms,
558
- owners,
559
- indexes: { byId }
560
- };
561
- }
1294
+ };
562
1295
 
563
- // src/linter/engine.ts
564
- var LintEngine = (_class = class {
565
- __init() {this.rules = []}
566
-
567
- constructor(overrides) {;_class.prototype.__init.call(this);
568
- this.overrides = _nullishCoalesce(overrides, () => ( {}));
569
- }
570
- /** Register a lint rule with the engine. */
571
- register(rule) {
572
- this.rules.push(rule);
573
- }
574
- /**
575
- * Run all enabled rules against the graph and return sorted diagnostics.
576
- *
577
- * Diagnostics are sorted by source file (ascending) then line (ascending).
578
- */
1296
+ // src/linter/rules/governance-fields-exist.ts
1297
+ var governanceFieldsExist = {
1298
+ id: "governance/fields-exist",
1299
+ defaultSeverity: "error",
1300
+ description: "Every field key in governance must exist as a field in the referenced OSI model dataset",
1301
+ fixable: false,
579
1302
  run(graph) {
580
- const allDiagnostics = [];
581
- for (const rule of this.rules) {
582
- const override = this.overrides[rule.id];
583
- if (override === "off") {
584
- continue;
585
- }
586
- const diagnostics = rule.run(graph);
587
- const severity = _nullishCoalesce(override, () => ( rule.defaultSeverity));
588
- for (const diag of diagnostics) {
589
- allDiagnostics.push({ ...diag, severity });
1303
+ const diagnostics = [];
1304
+ for (const [key, gov] of graph.governance) {
1305
+ if (!gov.fields) continue;
1306
+ const model = graph.models.get(gov.model);
1307
+ if (!model) continue;
1308
+ for (const fieldKey of Object.keys(gov.fields)) {
1309
+ const parts = fieldKey.split(".");
1310
+ if (parts.length !== 2) continue;
1311
+ const [dsName, fieldName] = parts;
1312
+ const dataset = model.datasets.find((d) => d.name === dsName);
1313
+ if (!dataset) {
1314
+ diagnostics.push({
1315
+ ruleId: this.id,
1316
+ severity: this.defaultSeverity,
1317
+ message: `Governance field "${fieldKey}" references dataset "${dsName}" which does not exist in model "${gov.model}"`,
1318
+ location: { file: `governance:${key}`, line: 1, column: 1 },
1319
+ fixable: false
1320
+ });
1321
+ continue;
1322
+ }
1323
+ const validFields = new Set(_nullishCoalesce(_optionalChain([dataset, 'access', _12 => _12.fields, 'optionalAccess', _13 => _13.map, 'call', _14 => _14((f) => f.name)]), () => ( [])));
1324
+ if (!validFields.has(fieldName)) {
1325
+ diagnostics.push({
1326
+ ruleId: this.id,
1327
+ severity: this.defaultSeverity,
1328
+ message: `Governance field "${fieldKey}" references field "${fieldName}" which does not exist in dataset "${dsName}"`,
1329
+ location: { file: `governance:${key}`, line: 1, column: 1 },
1330
+ fixable: false
1331
+ });
1332
+ }
590
1333
  }
591
1334
  }
592
- allDiagnostics.sort((a, b) => {
593
- const fileCmp = a.source.file.localeCompare(b.source.file);
594
- if (fileCmp !== 0) return fileCmp;
595
- return a.source.line - b.source.line;
596
- });
597
- return allDiagnostics;
1335
+ return diagnostics;
598
1336
  }
599
- }, _class);
1337
+ };
600
1338
 
601
- // src/linter/rules/schema-valid-yaml.ts
602
- var schemaValidYaml = {
603
- id: "schema/valid-yaml",
604
- defaultSeverity: "error",
1339
+ // src/linter/rules/governance-grain-required.ts
1340
+ var governanceGrainRequired = {
1341
+ id: "governance/grain-required",
1342
+ defaultSeverity: "warning",
1343
+ description: "Every dataset in governance must have a grain statement",
605
1344
  fixable: false,
606
- description: "Validates YAML against Zod schemas",
607
- run() {
608
- return [];
1345
+ run(graph) {
1346
+ const diagnostics = [];
1347
+ for (const [key, gov] of graph.governance) {
1348
+ if (!gov.datasets) continue;
1349
+ for (const [dsName, ds] of Object.entries(gov.datasets)) {
1350
+ if (!ds.grain) {
1351
+ diagnostics.push({
1352
+ ruleId: this.id,
1353
+ severity: this.defaultSeverity,
1354
+ message: `Dataset "${dsName}" in governance for model "${gov.model}" is missing a grain statement`,
1355
+ location: { file: `governance:${key}`, line: 1, column: 1 },
1356
+ fixable: false
1357
+ });
1358
+ }
1359
+ }
1360
+ }
1361
+ return diagnostics;
609
1362
  }
610
1363
  };
611
1364
 
612
- // src/linter/rules/naming-id-kebab-case.ts
613
- var KEBAB_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
614
- function toKebabCase2(input) {
615
- return input.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/[_\s]+/g, "-").replace(/-+/g, "-").toLowerCase().replace(/^-+|-+$/g, "");
616
- }
617
- var namingIdKebabCase = {
618
- id: "naming/id-kebab-case",
619
- defaultSeverity: "error",
620
- fixable: true,
621
- description: "IDs must be kebab-case",
1365
+ // src/linter/rules/governance-security-required.ts
1366
+ var governanceSecurityRequired = {
1367
+ id: "governance/security-required",
1368
+ defaultSeverity: "warning",
1369
+ description: "Every governance file must have a security classification",
1370
+ fixable: false,
622
1371
  run(graph) {
623
1372
  const diagnostics = [];
624
- for (const [, node] of graph.nodes) {
625
- if (!KEBAB_RE.test(node.id)) {
626
- const suggested = toKebabCase2(node.id);
1373
+ for (const [key, gov] of graph.governance) {
1374
+ if (!gov.security) {
627
1375
  diagnostics.push({
628
- ruleId: "naming/id-kebab-case",
629
- severity: "error",
630
- message: `ID "${node.id}" is not kebab-case`,
631
- source: node.source,
632
- fixable: true,
633
- fix: {
634
- description: `Rename to "${suggested}"`,
635
- edits: [
636
- {
637
- file: node.source.file,
638
- range: {
639
- startLine: node.source.line,
640
- startCol: node.source.col,
641
- endLine: node.source.line,
642
- endCol: node.source.col
643
- },
644
- newText: suggested
645
- }
646
- ]
647
- }
1376
+ ruleId: this.id,
1377
+ severity: this.defaultSeverity,
1378
+ message: `Governance for model "${gov.model}" is missing a security classification`,
1379
+ location: { file: `governance:${key}`, line: 1, column: 1 },
1380
+ fixable: false
648
1381
  });
649
1382
  }
650
1383
  }
@@ -652,39 +1385,22 @@ var namingIdKebabCase = {
652
1385
  }
653
1386
  };
654
1387
 
655
- // src/linter/rules/ownership-required.ts
656
- var REQUIRES_OWNER = /* @__PURE__ */ new Set(["concept", "product", "entity"]);
657
- var ownershipRequired = {
658
- id: "ownership/required",
659
- defaultSeverity: "error",
660
- fixable: true,
661
- description: "Concepts, products, and entities require an owner",
1388
+ // src/linter/rules/governance-trust-required.ts
1389
+ var governanceTrustRequired = {
1390
+ id: "governance/trust-required",
1391
+ defaultSeverity: "warning",
1392
+ description: "Governance files must have a trust status set (endorsed/warning/deprecated)",
1393
+ fixable: false,
662
1394
  run(graph) {
663
1395
  const diagnostics = [];
664
- for (const [, node] of graph.nodes) {
665
- if (!REQUIRES_OWNER.has(node.kind)) continue;
666
- if (!node.owner) {
1396
+ for (const [key, gov] of graph.governance) {
1397
+ if (!gov.trust) {
667
1398
  diagnostics.push({
668
- ruleId: "ownership/required",
669
- severity: "error",
670
- message: `${node.kind} "${node.id}" is missing a required owner`,
671
- source: node.source,
672
- fixable: true,
673
- fix: {
674
- description: "Add owner field",
675
- edits: [
676
- {
677
- file: node.source.file,
678
- range: {
679
- startLine: node.source.line,
680
- startCol: node.source.col,
681
- endLine: node.source.line,
682
- endCol: node.source.col
683
- },
684
- newText: "owner: TODO\n"
685
- }
686
- ]
687
- }
1399
+ ruleId: this.id,
1400
+ severity: this.defaultSeverity,
1401
+ message: `Governance for model "${gov.model}" is missing a trust status (endorsed/warning/deprecated)`,
1402
+ location: { file: `governance:${key}`, line: 1, column: 1 },
1403
+ fixable: false
688
1404
  });
689
1405
  }
690
1406
  }
@@ -692,111 +1408,134 @@ var ownershipRequired = {
692
1408
  }
693
1409
  };
694
1410
 
695
- // src/linter/rules/descriptions-required.ts
696
- function hasDescriptionOrDefinition(node) {
697
- if (typeof node.description === "string" && node.description.length > 0) {
698
- return true;
699
- }
700
- if (typeof node.definition === "string" && node.definition.length > 0) {
701
- return true;
702
- }
703
- return false;
704
- }
705
- var descriptionsRequired = {
706
- id: "descriptions/required",
1411
+ // src/linter/rules/governance-refresh-required.ts
1412
+ var governanceRefreshRequired = {
1413
+ id: "governance/refresh-required",
707
1414
  defaultSeverity: "warning",
708
- fixable: true,
709
- description: "All nodes should have a description or definition",
1415
+ description: "All governed datasets must have a refresh cadence set",
1416
+ fixable: false,
710
1417
  run(graph) {
711
1418
  const diagnostics = [];
712
- for (const [, node] of graph.nodes) {
713
- if (!hasDescriptionOrDefinition(node)) {
714
- diagnostics.push({
715
- ruleId: "descriptions/required",
716
- severity: "warning",
717
- message: `${node.kind} "${node.id}" is missing a description or definition`,
718
- source: node.source,
719
- fixable: true,
720
- fix: {
721
- description: "Add description field",
722
- edits: [
723
- {
724
- file: node.source.file,
725
- range: {
726
- startLine: node.source.line,
727
- startCol: node.source.col,
728
- endLine: node.source.line,
729
- endCol: node.source.col
730
- },
731
- newText: "description: TODO\n"
732
- }
733
- ]
734
- }
735
- });
1419
+ for (const [key, gov] of graph.governance) {
1420
+ if (!gov.datasets) continue;
1421
+ for (const [dsName, ds] of Object.entries(gov.datasets)) {
1422
+ if (!ds.refresh) {
1423
+ diagnostics.push({
1424
+ ruleId: this.id,
1425
+ severity: this.defaultSeverity,
1426
+ message: `Dataset "${dsName}" in governance for model "${gov.model}" is missing a refresh cadence`,
1427
+ location: { file: `governance:${key}`, line: 1, column: 1 },
1428
+ fixable: false
1429
+ });
1430
+ }
736
1431
  }
737
1432
  }
738
1433
  return diagnostics;
739
1434
  }
740
1435
  };
741
1436
 
742
- // src/linter/rules/references-resolvable.ts
743
- var referencesResolvable = {
744
- id: "references/resolvable",
745
- defaultSeverity: "error",
1437
+ // src/linter/rules/lineage-upstream-required.ts
1438
+ var lineageUpstreamRequired = {
1439
+ id: "lineage/upstream-required",
1440
+ defaultSeverity: "warning",
1441
+ description: "Every governed model should have a lineage file with at least one upstream entry",
746
1442
  fixable: false,
747
- description: "All cross-node references must resolve to existing nodes",
748
1443
  run(graph) {
749
1444
  const diagnostics = [];
750
- for (const [, node] of graph.nodes) {
751
- if (node.kind === "policy" || node.kind === "owner") continue;
752
- if (node.owner && !graph.nodes.has(node.owner)) {
1445
+ for (const [modelName] of graph.governance) {
1446
+ const lineage = graph.lineage.get(modelName);
1447
+ if (!lineage || !lineage.upstream || lineage.upstream.length === 0) {
753
1448
  diagnostics.push({
754
- ruleId: "references/resolvable",
755
- severity: "error",
756
- message: `${node.kind} "${node.id}" references owner "${node.owner}" which does not exist`,
757
- source: node.source,
1449
+ ruleId: this.id,
1450
+ severity: this.defaultSeverity,
1451
+ message: `Governed model "${modelName}" is missing lineage with at least one upstream entry`,
1452
+ location: { file: `governance:${modelName}`, line: 1, column: 1 },
758
1453
  fixable: false
759
1454
  });
760
1455
  }
761
- if (node.kind === "concept") {
762
- const concept = node;
763
- if (concept.dependsOn) {
764
- for (const dep of concept.dependsOn) {
765
- if (!graph.nodes.has(dep)) {
766
- diagnostics.push({
767
- ruleId: "references/resolvable",
768
- severity: "error",
769
- message: `concept "${concept.id}" depends on "${dep}" which does not exist`,
770
- source: concept.source,
771
- fixable: false
772
- });
773
- }
1456
+ }
1457
+ return diagnostics;
1458
+ }
1459
+ };
1460
+
1461
+ // src/linter/rules/governance-semantic-role-required.ts
1462
+ var governanceSemanticRoleRequired = {
1463
+ id: "governance/semantic-role-required",
1464
+ defaultSeverity: "warning",
1465
+ description: "Every field in every dataset of a governed model must have a governance entry with semantic_role",
1466
+ fixable: false,
1467
+ run(graph) {
1468
+ const diagnostics = [];
1469
+ for (const [modelName, model] of graph.models) {
1470
+ const gov = graph.governance.get(modelName);
1471
+ if (!gov) continue;
1472
+ const govFields = _nullishCoalesce(gov.fields, () => ( {}));
1473
+ for (const dataset of model.datasets) {
1474
+ if (!dataset.fields) continue;
1475
+ for (const field of dataset.fields) {
1476
+ const fieldKey = `${dataset.name}.${field.name}`;
1477
+ const govField = govFields[fieldKey];
1478
+ if (!govField || !govField.semantic_role) {
1479
+ diagnostics.push({
1480
+ ruleId: this.id,
1481
+ severity: this.defaultSeverity,
1482
+ message: `Field "${fieldKey}" in model "${modelName}" is missing a semantic_role in governance`,
1483
+ location: { file: `governance:${modelName}`, line: 1, column: 1 },
1484
+ fixable: false
1485
+ });
774
1486
  }
775
1487
  }
776
- if (concept.productId && !graph.nodes.has(concept.productId)) {
1488
+ }
1489
+ }
1490
+ return diagnostics;
1491
+ }
1492
+ };
1493
+
1494
+ // src/linter/rules/governance-aggregation-required.ts
1495
+ var governanceAggregationRequired = {
1496
+ id: "governance/aggregation-required",
1497
+ defaultSeverity: "warning",
1498
+ description: 'Every field with semantic_role "metric" must have default_aggregation set',
1499
+ fixable: false,
1500
+ run(graph) {
1501
+ const diagnostics = [];
1502
+ for (const [key, gov] of graph.governance) {
1503
+ if (!gov.fields) continue;
1504
+ for (const [fieldName, field] of Object.entries(gov.fields)) {
1505
+ if (field.semantic_role === "metric" && !field.default_aggregation) {
777
1506
  diagnostics.push({
778
- ruleId: "references/resolvable",
779
- severity: "error",
780
- message: `concept "${concept.id}" references product "${concept.productId}" which does not exist`,
781
- source: concept.source,
1507
+ ruleId: this.id,
1508
+ severity: this.defaultSeverity,
1509
+ message: `Metric field "${fieldName}" in governance for model "${gov.model}" is missing default_aggregation`,
1510
+ location: { file: `governance:${key}`, line: 1, column: 1 },
782
1511
  fixable: false
783
1512
  });
784
1513
  }
785
1514
  }
786
- if (node.kind === "term") {
787
- const term = node;
788
- if (term.mapsTo) {
789
- for (const target of term.mapsTo) {
790
- if (!graph.nodes.has(target)) {
791
- diagnostics.push({
792
- ruleId: "references/resolvable",
793
- severity: "error",
794
- message: `term "${term.id}" maps to "${target}" which does not exist`,
795
- source: term.source,
796
- fixable: false
797
- });
798
- }
799
- }
1515
+ }
1516
+ return diagnostics;
1517
+ }
1518
+ };
1519
+
1520
+ // src/linter/rules/governance-additive-required.ts
1521
+ var governanceAdditiveRequired = {
1522
+ id: "governance/additive-required",
1523
+ defaultSeverity: "warning",
1524
+ description: 'Every field with semantic_role "metric" must have additive flag set',
1525
+ fixable: false,
1526
+ run(graph) {
1527
+ const diagnostics = [];
1528
+ for (const [key, gov] of graph.governance) {
1529
+ if (!gov.fields) continue;
1530
+ for (const [fieldName, field] of Object.entries(gov.fields)) {
1531
+ if (field.semantic_role === "metric" && field.additive == null) {
1532
+ diagnostics.push({
1533
+ ruleId: this.id,
1534
+ severity: this.defaultSeverity,
1535
+ message: `Metric field "${fieldName}" in governance for model "${gov.model}" is missing additive flag`,
1536
+ location: { file: `governance:${key}`, line: 1, column: 1 },
1537
+ fixable: false
1538
+ });
800
1539
  }
801
1540
  }
802
1541
  }
@@ -804,57 +1543,46 @@ var referencesResolvable = {
804
1543
  }
805
1544
  };
806
1545
 
807
- // src/linter/rules/glossary-no-duplicate-terms.ts
808
- var glossaryNoDuplicateTerms = {
809
- id: "glossary/no-duplicate-terms",
1546
+ // src/linter/rules/rules-golden-queries-minimum.ts
1547
+ var rulesGoldenQueriesMinimum = {
1548
+ id: "rules/golden-queries-minimum",
810
1549
  defaultSeverity: "warning",
1550
+ description: "Models with a rules file must have at least 3 golden queries",
811
1551
  fixable: false,
812
- description: "Term definitions must be unique across the glossary",
813
1552
  run(graph) {
814
1553
  const diagnostics = [];
815
- const termIds = _nullishCoalesce(graph.indexes.byKind.get("term"), () => ( []));
816
- const seen = /* @__PURE__ */ new Map();
817
- for (const termId of termIds) {
818
- const node = graph.nodes.get(termId);
819
- if (!node || node.kind !== "term") continue;
820
- const term = node;
821
- const normalized = term.definition.trim().toLowerCase();
822
- const firstId = seen.get(normalized);
823
- if (firstId !== void 0) {
1554
+ for (const [key, rules] of graph.rules) {
1555
+ const count = _nullishCoalesce(_optionalChain([rules, 'access', _15 => _15.golden_queries, 'optionalAccess', _16 => _16.length]), () => ( 0));
1556
+ if (count < 3) {
824
1557
  diagnostics.push({
825
- ruleId: "glossary/no-duplicate-terms",
826
- severity: "warning",
827
- message: `term "${term.id}" has the same definition as term "${firstId}"`,
828
- source: term.source,
1558
+ ruleId: this.id,
1559
+ severity: this.defaultSeverity,
1560
+ message: `Rules for model "${rules.model}" has ${count} golden queries (minimum 3 required)`,
1561
+ location: { file: `rules:${key}`, line: 1, column: 1 },
829
1562
  fixable: false
830
1563
  });
831
- } else {
832
- seen.set(normalized, term.id);
833
1564
  }
834
1565
  }
835
1566
  return diagnostics;
836
1567
  }
837
1568
  };
838
1569
 
839
- // src/linter/rules/concepts-certified-requires-evidence.ts
840
- var conceptsCertifiedRequiresEvidence = {
841
- id: "concepts/certified-requires-evidence",
842
- defaultSeverity: "error",
1570
+ // src/linter/rules/rules-business-rules-exist.ts
1571
+ var rulesBusinessRulesExist = {
1572
+ id: "rules/business-rules-exist",
1573
+ defaultSeverity: "warning",
1574
+ description: "Models with a rules file must have at least 1 business rule",
843
1575
  fixable: false,
844
- description: "Certified concepts must include at least one evidence entry",
845
1576
  run(graph) {
846
1577
  const diagnostics = [];
847
- const conceptIds = _nullishCoalesce(graph.indexes.byKind.get("concept"), () => ( []));
848
- for (const id of conceptIds) {
849
- const node = graph.nodes.get(id);
850
- if (!node || node.kind !== "concept") continue;
851
- const concept = node;
852
- if (concept.certified && (!concept.evidence || concept.evidence.length === 0)) {
1578
+ for (const [key, rules] of graph.rules) {
1579
+ const count = _nullishCoalesce(_optionalChain([rules, 'access', _17 => _17.business_rules, 'optionalAccess', _18 => _18.length]), () => ( 0));
1580
+ if (count < 1) {
853
1581
  diagnostics.push({
854
- ruleId: "concepts/certified-requires-evidence",
855
- severity: "error",
856
- message: `concept "${concept.id}" is certified but has no evidence`,
857
- source: concept.source,
1582
+ ruleId: this.id,
1583
+ severity: this.defaultSeverity,
1584
+ message: `Rules for model "${rules.model}" has no business rules (at least 1 required)`,
1585
+ location: { file: `rules:${key}`, line: 1, column: 1 },
858
1586
  fixable: false
859
1587
  });
860
1588
  }
@@ -863,107 +1591,110 @@ var conceptsCertifiedRequiresEvidence = {
863
1591
  }
864
1592
  };
865
1593
 
866
- // src/linter/rules/policies-unknown-subject.ts
867
- var policiesUnknownSubject = {
868
- id: "policies/unknown-subject",
1594
+ // src/linter/rules/rules-guardrails-exist.ts
1595
+ var rulesGuardrailsExist = {
1596
+ id: "rules/guardrails-exist",
869
1597
  defaultSeverity: "warning",
1598
+ description: "Models with a rules file must have at least 1 guardrail filter",
870
1599
  fixable: false,
871
- description: "Policy selectors must reference known concepts and tags",
872
1600
  run(graph) {
873
1601
  const diagnostics = [];
874
- const policyIds = _nullishCoalesce(graph.indexes.byKind.get("policy"), () => ( []));
875
- for (const policyId of policyIds) {
876
- const node = graph.nodes.get(policyId);
877
- if (!node || node.kind !== "policy") continue;
878
- const policy = node;
879
- for (const rule of policy.rules) {
880
- if (rule.when.conceptIds) {
881
- for (const conceptId of rule.when.conceptIds) {
882
- const target = graph.nodes.get(conceptId);
883
- if (!target || target.kind !== "concept") {
884
- diagnostics.push({
885
- ruleId: "policies/unknown-subject",
886
- severity: "warning",
887
- message: `policy "${policy.id}" references unknown concept "${conceptId}"`,
888
- source: policy.source,
889
- fixable: false
890
- });
891
- }
892
- }
893
- }
894
- if (rule.when.tagsAny) {
895
- for (const tag of rule.when.tagsAny) {
896
- if (!graph.indexes.byTag.has(tag)) {
897
- diagnostics.push({
898
- ruleId: "policies/unknown-subject",
899
- severity: "warning",
900
- message: `policy "${policy.id}" references unknown tag "${tag}"`,
901
- source: policy.source,
902
- fixable: false
903
- });
904
- }
905
- }
906
- }
1602
+ for (const [key, rules] of graph.rules) {
1603
+ const count = _nullishCoalesce(_optionalChain([rules, 'access', _19 => _19.guardrail_filters, 'optionalAccess', _20 => _20.length]), () => ( 0));
1604
+ if (count < 1) {
1605
+ diagnostics.push({
1606
+ ruleId: this.id,
1607
+ severity: this.defaultSeverity,
1608
+ message: `Rules for model "${rules.model}" has no guardrail filters (at least 1 required)`,
1609
+ location: { file: `rules:${key}`, line: 1, column: 1 },
1610
+ fixable: false
1611
+ });
907
1612
  }
908
1613
  }
909
1614
  return diagnostics;
910
1615
  }
911
1616
  };
912
1617
 
913
- // src/linter/rules/policies-deny-overrides-allow.ts
914
- var policiesDenyOverridesAllow = {
915
- id: "policies/deny-overrides-allow",
1618
+ // src/linter/rules/rules-hierarchies-exist.ts
1619
+ var rulesHierarchiesExist = {
1620
+ id: "rules/hierarchies-exist",
916
1621
  defaultSeverity: "warning",
1622
+ description: "Models with a rules file must have at least 1 hierarchy",
917
1623
  fixable: false,
918
- description: "Deny rules should have higher priority than allow rules",
919
1624
  run(graph) {
920
1625
  const diagnostics = [];
921
- const policyIds = _nullishCoalesce(graph.indexes.byKind.get("policy"), () => ( []));
922
- for (const policyId of policyIds) {
923
- const node = graph.nodes.get(policyId);
924
- if (!node || node.kind !== "policy") continue;
925
- const policy = node;
926
- let maxAllowPriority = -Infinity;
927
- for (const rule of policy.rules) {
928
- if (!rule.then.deny) {
929
- maxAllowPriority = Math.max(maxAllowPriority, rule.priority);
930
- }
931
- }
932
- for (const rule of policy.rules) {
933
- if (rule.then.deny && maxAllowPriority > -Infinity && rule.priority <= maxAllowPriority) {
934
- diagnostics.push({
935
- ruleId: "policies/deny-overrides-allow",
936
- severity: "warning",
937
- message: `policy "${policy.id}" has a deny rule with priority ${rule.priority} that does not override allow rules (max allow priority: ${maxAllowPriority})`,
938
- source: policy.source,
939
- fixable: false
940
- });
941
- }
1626
+ for (const [key, rules] of graph.rules) {
1627
+ const count = _nullishCoalesce(_optionalChain([rules, 'access', _21 => _21.hierarchies, 'optionalAccess', _22 => _22.length]), () => ( 0));
1628
+ if (count < 1) {
1629
+ diagnostics.push({
1630
+ ruleId: this.id,
1631
+ severity: this.defaultSeverity,
1632
+ message: `Rules for model "${rules.model}" has no hierarchies (at least 1 required)`,
1633
+ location: { file: `rules:${key}`, line: 1, column: 1 },
1634
+ fixable: false
1635
+ });
942
1636
  }
943
1637
  }
944
1638
  return diagnostics;
945
1639
  }
946
1640
  };
947
1641
 
948
- // src/linter/rules/docs-examples-required.ts
949
- var docsExamplesRequired = {
950
- id: "docs/examples-required",
1642
+ // src/linter/rules/tier-bronze.ts
1643
+ var tierBronze = {
1644
+ id: "tier/bronze-requirements",
951
1645
  defaultSeverity: "warning",
1646
+ description: "Checks all Bronze tier requirements as a composite",
952
1647
  fixable: false,
953
- description: "Certified concepts should include at least one example",
954
1648
  run(graph) {
955
1649
  const diagnostics = [];
956
- const conceptIds = _nullishCoalesce(graph.indexes.byKind.get("concept"), () => ( []));
957
- for (const id of conceptIds) {
958
- const node = graph.nodes.get(id);
959
- if (!node || node.kind !== "concept") continue;
960
- const concept = node;
961
- if (concept.certified && (!concept.examples || concept.examples.length === 0)) {
1650
+ for (const [modelName, model] of graph.models) {
1651
+ const missing = [];
1652
+ if (!model.description) {
1653
+ missing.push("model description");
1654
+ }
1655
+ for (const dataset of model.datasets) {
1656
+ if (!dataset.description) {
1657
+ missing.push(`dataset "${dataset.name}" description`);
1658
+ }
1659
+ if (dataset.fields) {
1660
+ for (const field of dataset.fields) {
1661
+ if (!field.description) {
1662
+ missing.push(`field "${dataset.name}.${field.name}" description`);
1663
+ }
1664
+ }
1665
+ }
1666
+ }
1667
+ const gov = graph.governance.get(modelName);
1668
+ if (!gov) {
1669
+ missing.push("governance file");
1670
+ } else {
1671
+ if (!gov.owner) {
1672
+ missing.push("governance owner");
1673
+ }
1674
+ if (!gov.security) {
1675
+ missing.push("security classification");
1676
+ }
1677
+ if (gov.datasets) {
1678
+ for (const [dsName, ds] of Object.entries(gov.datasets)) {
1679
+ if (!ds.grain) {
1680
+ missing.push(`dataset "${dsName}" grain`);
1681
+ }
1682
+ if (!ds.table_type) {
1683
+ missing.push(`dataset "${dsName}" table_type`);
1684
+ }
1685
+ }
1686
+ } else {
1687
+ if (model.datasets.length > 0) {
1688
+ missing.push("governance datasets");
1689
+ }
1690
+ }
1691
+ }
1692
+ if (missing.length > 0) {
962
1693
  diagnostics.push({
963
- ruleId: "docs/examples-required",
964
- severity: "warning",
965
- message: `concept "${concept.id}" is certified but has no examples`,
966
- source: concept.source,
1694
+ ruleId: this.id,
1695
+ severity: this.defaultSeverity,
1696
+ message: `Model "${modelName}" is missing Bronze requirements: ${missing.join(", ")}`,
1697
+ location: { file: `model:${modelName}`, line: 1, column: 1 },
967
1698
  fixable: false
968
1699
  });
969
1700
  }
@@ -972,25 +1703,56 @@ var docsExamplesRequired = {
972
1703
  }
973
1704
  };
974
1705
 
975
- // src/linter/rules/deprecation-require-sunset.ts
976
- var deprecationRequireSunset = {
977
- id: "deprecation/require-sunset",
1706
+ // src/linter/rules/tier-silver.ts
1707
+ var tierSilver = {
1708
+ id: "tier/silver-requirements",
978
1709
  defaultSeverity: "warning",
1710
+ description: "Checks all Silver tier requirements (beyond Bronze) as a composite",
979
1711
  fixable: false,
980
- description: "Deprecated nodes must include a sunset date tag",
981
1712
  run(graph) {
982
1713
  const diagnostics = [];
983
- const deprecatedIds = _nullishCoalesce(graph.indexes.byStatus.get("deprecated"), () => ( []));
984
- for (const id of deprecatedIds) {
985
- const node = graph.nodes.get(id);
986
- if (!node) continue;
987
- const hasSunset = _nullishCoalesce(_optionalChain([node, 'access', _ => _.tags, 'optionalAccess', _2 => _2.some, 'call', _3 => _3((tag) => tag.startsWith("sunset:"))]), () => ( false));
988
- if (!hasSunset) {
1714
+ for (const [modelName, gov] of graph.governance) {
1715
+ const missing = [];
1716
+ if (!gov.trust) {
1717
+ missing.push("trust status");
1718
+ }
1719
+ if (!gov.tags || gov.tags.length < 2) {
1720
+ missing.push(`tags (need >=2, have ${_nullishCoalesce(_optionalChain([gov, 'access', _23 => _23.tags, 'optionalAccess', _24 => _24.length]), () => ( 0))})`);
1721
+ }
1722
+ const hasGlossaryLink = Array.from(graph.terms.values()).some(
1723
+ (term) => term.owner === gov.owner
1724
+ );
1725
+ if (!hasGlossaryLink) {
1726
+ missing.push("glossary linked");
1727
+ }
1728
+ const lineage = graph.lineage.get(modelName);
1729
+ if (!lineage || !lineage.upstream || lineage.upstream.length === 0) {
1730
+ missing.push("upstream lineage");
1731
+ }
1732
+ if (gov.datasets) {
1733
+ for (const [dsName, ds] of Object.entries(gov.datasets)) {
1734
+ if (!ds.refresh) {
1735
+ missing.push(`refresh cadence on dataset "${dsName}"`);
1736
+ }
1737
+ }
1738
+ }
1739
+ let sampleValuesCount = 0;
1740
+ if (gov.fields) {
1741
+ for (const field of Object.values(gov.fields)) {
1742
+ if (field.sample_values && field.sample_values.length > 0) {
1743
+ sampleValuesCount++;
1744
+ }
1745
+ }
1746
+ }
1747
+ if (sampleValuesCount < 2) {
1748
+ missing.push(`sample values on >=2 fields (have ${sampleValuesCount})`);
1749
+ }
1750
+ if (missing.length > 0) {
989
1751
  diagnostics.push({
990
- ruleId: "deprecation/require-sunset",
991
- severity: "warning",
992
- message: `${node.kind} "${node.id}" is deprecated but has no sunset:* tag`,
993
- source: node.source,
1752
+ ruleId: this.id,
1753
+ severity: this.defaultSeverity,
1754
+ message: `Model "${modelName}" is missing Silver requirements: ${missing.join(", ")}`,
1755
+ location: { file: `governance:${modelName}`, line: 1, column: 1 },
994
1756
  fixable: false
995
1757
  });
996
1758
  }
@@ -999,70 +1761,87 @@ var deprecationRequireSunset = {
999
1761
  }
1000
1762
  };
1001
1763
 
1002
- // src/linter/rules/packaging-no-secrets.ts
1003
- var SECRET_PATTERNS = [
1004
- { label: "AWS access key", re: /AKIA[0-9A-Z]{16}/ },
1005
- { label: "password assignment", re: /password\s*[:=]\s*\S+/i },
1006
- { label: "secret assignment", re: /secret\s*[:=]\s*\S+/i },
1007
- { label: "token assignment", re: /token\s*[:=]\s*\S+/i },
1008
- { label: "API key (long hex/alnum)", re: /(?:api[_-]?key|apikey)\s*[:=]\s*[A-Za-z0-9]{16,}/ }
1009
- ];
1010
- function detectSecret(text) {
1011
- for (const { label, re } of SECRET_PATTERNS) {
1012
- if (re.test(text)) return label;
1013
- }
1014
- return void 0;
1015
- }
1016
- var packagingNoSecrets = {
1017
- id: "packaging/no-secrets",
1018
- defaultSeverity: "error",
1764
+ // src/linter/rules/tier-gold.ts
1765
+ var tierGold = {
1766
+ id: "tier/gold-requirements",
1767
+ defaultSeverity: "warning",
1768
+ description: "Checks all Gold tier requirements (beyond Silver) as a composite",
1019
1769
  fixable: false,
1020
- description: "Node content must not contain secret patterns (API keys, passwords, tokens)",
1021
1770
  run(graph) {
1022
1771
  const diagnostics = [];
1023
- for (const [, node] of graph.nodes) {
1024
- if (node.description) {
1025
- const match = detectSecret(node.description);
1026
- if (match) {
1027
- diagnostics.push({
1028
- ruleId: "packaging/no-secrets",
1029
- severity: "error",
1030
- message: `${node.kind} "${node.id}" description contains a potential ${match}`,
1031
- source: node.source,
1032
- fixable: false
1033
- });
1034
- }
1772
+ for (const [modelName, gov] of graph.governance) {
1773
+ const missing = [];
1774
+ const model = graph.models.get(modelName);
1775
+ if (gov.trust !== "endorsed") {
1776
+ missing.push("trust must be endorsed");
1035
1777
  }
1036
- const asRecord = node;
1037
- if (typeof asRecord.definition === "string") {
1038
- const match = detectSecret(asRecord.definition);
1039
- if (match) {
1040
- diagnostics.push({
1041
- ruleId: "packaging/no-secrets",
1042
- severity: "error",
1043
- message: `${node.kind} "${node.id}" definition contains a potential ${match}`,
1044
- source: node.source,
1045
- fixable: false
1046
- });
1778
+ if (model) {
1779
+ const govFields = _nullishCoalesce(gov.fields, () => ( {}));
1780
+ for (const dataset of model.datasets) {
1781
+ if (!dataset.fields) continue;
1782
+ for (const field of dataset.fields) {
1783
+ const fieldKey = `${dataset.name}.${field.name}`;
1784
+ const govField = govFields[fieldKey];
1785
+ if (!govField || !govField.semantic_role) {
1786
+ missing.push(`semantic_role for "${fieldKey}"`);
1787
+ }
1788
+ }
1047
1789
  }
1048
1790
  }
1049
- if (node.kind === "concept") {
1050
- const concept = node;
1051
- if (concept.examples) {
1052
- for (const example of concept.examples) {
1053
- const match = detectSecret(example.content);
1054
- if (match) {
1055
- diagnostics.push({
1056
- ruleId: "packaging/no-secrets",
1057
- severity: "error",
1058
- message: `concept "${concept.id}" example "${example.label}" contains a potential ${match}`,
1059
- source: concept.source,
1060
- fixable: false
1061
- });
1791
+ if (gov.fields) {
1792
+ for (const [fieldName, field] of Object.entries(gov.fields)) {
1793
+ if (field.semantic_role === "metric") {
1794
+ if (!field.default_aggregation) {
1795
+ missing.push(`aggregation for metric "${fieldName}"`);
1062
1796
  }
1797
+ if (field.additive == null) {
1798
+ missing.push(`additive for metric "${fieldName}"`);
1799
+ }
1800
+ }
1801
+ }
1802
+ }
1803
+ let hasDefaultFilter = false;
1804
+ if (gov.fields) {
1805
+ for (const field of Object.values(gov.fields)) {
1806
+ if (field.default_filter) {
1807
+ hasDefaultFilter = true;
1808
+ break;
1063
1809
  }
1064
1810
  }
1065
1811
  }
1812
+ if (!hasDefaultFilter) {
1813
+ missing.push("default filters");
1814
+ }
1815
+ const rules = graph.rules.get(modelName);
1816
+ if (!rules) {
1817
+ missing.push("rules file (golden queries, business rules, guardrails, hierarchies)");
1818
+ } else {
1819
+ const goldenCount = _nullishCoalesce(_optionalChain([rules, 'access', _25 => _25.golden_queries, 'optionalAccess', _26 => _26.length]), () => ( 0));
1820
+ if (goldenCount < 3) {
1821
+ missing.push(`>=3 golden queries (have ${goldenCount})`);
1822
+ }
1823
+ const bizCount = _nullishCoalesce(_optionalChain([rules, 'access', _27 => _27.business_rules, 'optionalAccess', _28 => _28.length]), () => ( 0));
1824
+ if (bizCount < 1) {
1825
+ missing.push("business rules");
1826
+ }
1827
+ const guardCount = _nullishCoalesce(_optionalChain([rules, 'access', _29 => _29.guardrail_filters, 'optionalAccess', _30 => _30.length]), () => ( 0));
1828
+ if (guardCount < 1) {
1829
+ missing.push("guardrail filters");
1830
+ }
1831
+ const hierCount = _nullishCoalesce(_optionalChain([rules, 'access', _31 => _31.hierarchies, 'optionalAccess', _32 => _32.length]), () => ( 0));
1832
+ if (hierCount < 1) {
1833
+ missing.push("hierarchies");
1834
+ }
1835
+ }
1836
+ if (missing.length > 0) {
1837
+ diagnostics.push({
1838
+ ruleId: this.id,
1839
+ severity: this.defaultSeverity,
1840
+ message: `Model "${modelName}" is missing Gold requirements: ${missing.join(", ")}`,
1841
+ location: { file: `governance:${modelName}`, line: 1, column: 1 },
1842
+ fixable: false
1843
+ });
1844
+ }
1066
1845
  }
1067
1846
  return diagnostics;
1068
1847
  }
@@ -1070,84 +1849,100 @@ var packagingNoSecrets = {
1070
1849
 
1071
1850
  // src/linter/rules/index.ts
1072
1851
  var ALL_RULES = [
1073
- schemaValidYaml,
1852
+ // Bronze (12)
1074
1853
  namingIdKebabCase,
1075
- ownershipRequired,
1076
1854
  descriptionsRequired,
1855
+ ownershipRequired,
1077
1856
  referencesResolvable,
1078
1857
  glossaryNoDuplicateTerms,
1079
- conceptsCertifiedRequiresEvidence,
1080
- policiesUnknownSubject,
1081
- policiesDenyOverridesAllow,
1082
- docsExamplesRequired,
1083
- deprecationRequireSunset,
1084
- packagingNoSecrets
1858
+ noSecrets,
1859
+ osiValidSchema,
1860
+ governanceModelExists,
1861
+ governanceDatasetsExist,
1862
+ governanceFieldsExist,
1863
+ governanceGrainRequired,
1864
+ governanceSecurityRequired,
1865
+ // Silver (3)
1866
+ governanceTrustRequired,
1867
+ governanceRefreshRequired,
1868
+ lineageUpstreamRequired,
1869
+ // Gold (7)
1870
+ governanceSemanticRoleRequired,
1871
+ governanceAggregationRequired,
1872
+ governanceAdditiveRequired,
1873
+ rulesGoldenQueriesMinimum,
1874
+ rulesBusinessRulesExist,
1875
+ rulesGuardrailsExist,
1876
+ rulesHierarchiesExist,
1877
+ // Composite tier (3)
1878
+ tierBronze,
1879
+ tierSilver,
1880
+ tierGold
1085
1881
  ];
1086
1882
 
1087
- // src/fixer/apply.ts
1883
+ // src/config/defaults.ts
1884
+ var DEFAULT_CONFIG = {
1885
+ context_dir: "context",
1886
+ output_dir: "dist"
1887
+ };
1888
+
1889
+ // src/config/loader.ts
1890
+ var _fs = require('fs'); var fs = _interopRequireWildcard(_fs);
1891
+ var _path = require('path'); var path = _interopRequireWildcard(_path);
1892
+
1893
+ var CONFIG_FILENAME = "contextkit.config.yaml";
1894
+ function loadConfig(rootDir) {
1895
+ const configPath = path.join(rootDir, CONFIG_FILENAME);
1896
+ if (!fs.existsSync(configPath)) {
1897
+ return { ...DEFAULT_CONFIG };
1898
+ }
1899
+ const raw = fs.readFileSync(configPath, "utf-8");
1900
+ const parsed = yaml.parse(raw);
1901
+ if (parsed == null) {
1902
+ return { ...DEFAULT_CONFIG };
1903
+ }
1904
+ const validated = contextKitConfigSchema.parse(parsed);
1905
+ return validated;
1906
+ }
1088
1907
 
1089
- function applyFixes(diagnostics) {
1908
+ // src/fixer/apply.ts
1909
+ function applyFixes(diagnostics, readFile2) {
1090
1910
  const editsByFile = /* @__PURE__ */ new Map();
1091
- for (const diag of diagnostics) {
1092
- if (!diag.fixable || !diag.fix) continue;
1093
- for (const edit of diag.fix.edits) {
1094
- const existing = editsByFile.get(edit.file);
1095
- if (existing) {
1096
- existing.push(edit);
1097
- } else {
1098
- editsByFile.set(edit.file, [edit]);
1099
- }
1911
+ for (const diag2 of diagnostics) {
1912
+ if (!diag2.fixable || !diag2.fix) continue;
1913
+ const file = diag2.location.file;
1914
+ if (!editsByFile.has(file)) {
1915
+ editsByFile.set(file, []);
1916
+ }
1917
+ const fileEdits = editsByFile.get(file);
1918
+ for (const edit of diag2.fix.edits) {
1919
+ fileEdits.push(edit);
1100
1920
  }
1101
1921
  }
1102
- const results = [];
1922
+ const result = /* @__PURE__ */ new Map();
1103
1923
  for (const [file, edits] of editsByFile) {
1104
- const sorted = [...edits].sort((a, b) => {
1105
- if (b.range.startLine !== a.range.startLine) {
1106
- return b.range.startLine - a.range.startLine;
1107
- }
1108
- return b.range.startCol - a.range.startCol;
1924
+ edits.sort((a, b) => {
1925
+ if (a.startLine !== b.startLine) return b.startLine - a.startLine;
1926
+ return b.startCol - a.startCol;
1109
1927
  });
1110
- let content;
1111
- try {
1112
- content = _fs2.default.readFileSync(file, "utf-8");
1113
- } catch (e3) {
1114
- continue;
1115
- }
1928
+ const content = readFile2(file);
1116
1929
  const lines = content.split("\n");
1117
- for (const edit of sorted) {
1118
- const { startLine, startCol, endLine, endCol } = edit.range;
1119
- if (startLine === endLine && startCol === endCol) {
1120
- const lineIdx = startLine - 1;
1121
- if (lineIdx >= 0 && lineIdx <= lines.length) {
1122
- if (lineIdx === lines.length) {
1123
- lines.push(edit.newText.replace(/\n$/, ""));
1124
- } else {
1125
- const line = _nullishCoalesce(lines[lineIdx], () => ( ""));
1126
- const colIdx = startCol - 1;
1127
- const before = line.slice(0, colIdx);
1128
- const after = line.slice(colIdx);
1129
- const insertLines = (before + edit.newText + after).split("\n");
1130
- lines.splice(lineIdx, 1, ...insertLines);
1131
- }
1132
- }
1133
- } else {
1134
- const startLineIdx = startLine - 1;
1135
- const endLineIdx = endLine - 1;
1136
- if (startLineIdx >= 0 && endLineIdx < lines.length) {
1137
- const beforeText = (_nullishCoalesce(lines[startLineIdx], () => ( ""))).slice(0, startCol - 1);
1138
- const afterText = (_nullishCoalesce(lines[endLineIdx], () => ( ""))).slice(endCol - 1);
1139
- const replacementLines = (beforeText + edit.newText + afterText).split("\n");
1140
- lines.splice(startLineIdx, endLineIdx - startLineIdx + 1, ...replacementLines);
1141
- }
1142
- }
1930
+ for (const edit of edits) {
1931
+ applyEdit(lines, edit);
1143
1932
  }
1144
- results.push({
1145
- file,
1146
- editsApplied: edits.length,
1147
- newContent: lines.join("\n")
1148
- });
1933
+ result.set(file, lines.join("\n"));
1149
1934
  }
1150
- return results;
1935
+ return result;
1936
+ }
1937
+ function applyEdit(lines, edit) {
1938
+ const { startLine, startCol, endLine, endCol, newText } = edit;
1939
+ const sl = startLine - 1;
1940
+ const el = endLine - 1;
1941
+ const prefix = lines[sl].slice(0, startCol - 1);
1942
+ const suffix = lines[el].slice(endCol - 1);
1943
+ const replacement = prefix + newText + suffix;
1944
+ const newLines = replacement.split("\n");
1945
+ lines.splice(sl, el - sl + 1, ...newLines);
1151
1946
  }
1152
1947
 
1153
1948
 
@@ -1188,5 +1983,25 @@ function applyFixes(diagnostics) {
1188
1983
 
1189
1984
 
1190
1985
 
1191
- exports.ALL_RULES = ALL_RULES; exports.DEFAULT_CONFIG = DEFAULT_CONFIG; exports.LintEngine = LintEngine; exports.applyFixes = applyFixes; exports.buildGraph = buildGraph; exports.compile = compile; exports.conceptFileSchema = conceptFileSchema; exports.conceptsCertifiedRequiresEvidence = conceptsCertifiedRequiresEvidence; exports.createEmptyGraph = createEmptyGraph; exports.deprecationRequireSunset = deprecationRequireSunset; exports.descriptionsRequired = descriptionsRequired; exports.discoverFiles = discoverFiles; exports.docsExamplesRequired = docsExamplesRequired; exports.emitManifest = emitManifest; exports.entityFieldSchema = entityFieldSchema; exports.entityFileSchema = entityFileSchema; exports.evidenceSchema = evidenceSchema; exports.exampleSchema = exampleSchema; exports.glossaryNoDuplicateTerms = glossaryNoDuplicateTerms; exports.loadConfig = loadConfig; exports.namingIdKebabCase = namingIdKebabCase; exports.normalizeNode = normalizeNode; exports.ownerFileSchema = ownerFileSchema; exports.ownershipRequired = ownershipRequired; exports.packagingNoSecrets = packagingNoSecrets; exports.parseFile = parseFile; exports.policiesDenyOverridesAllow = policiesDenyOverridesAllow; exports.policiesUnknownSubject = policiesUnknownSubject; exports.policyFileSchema = policyFileSchema; exports.policyRuleSchema = policyRuleSchema; exports.policyThenSchema = policyThenSchema; exports.policyWhenSchema = policyWhenSchema; exports.productFileSchema = productFileSchema; exports.referencesResolvable = referencesResolvable; exports.resolveConfig = resolveConfig; exports.schemaValidYaml = schemaValidYaml; exports.termFileSchema = termFileSchema; exports.validateFile = validateFile;
1986
+
1987
+
1988
+
1989
+
1990
+
1991
+
1992
+
1993
+
1994
+
1995
+
1996
+
1997
+
1998
+
1999
+
2000
+
2001
+
2002
+
2003
+
2004
+
2005
+
2006
+ exports.ALL_RULES = ALL_RULES; exports.DEFAULT_CONFIG = DEFAULT_CONFIG; exports.LintEngine = LintEngine; exports.aiContextObjectSchema = aiContextObjectSchema; exports.aiContextSchema = aiContextSchema; exports.applyFixes = applyFixes; exports.buildGraph = buildGraph; exports.businessRuleSchema = businessRuleSchema; exports.checkBronze = checkBronze; exports.checkGold = checkGold; exports.checkSilver = checkSilver; exports.compile = compile; exports.computeAllTiers = computeAllTiers; exports.computeTier = computeTier; exports.contextKitConfigSchema = contextKitConfigSchema; exports.createEmptyGraph = createEmptyGraph; exports.customExtensionSchema = customExtensionSchema; exports.datasetGovernanceSchema = datasetGovernanceSchema; exports.defaultAggregationEnum = defaultAggregationEnum; exports.dialectEnum = dialectEnum; exports.dialectExpressionSchema = dialectExpressionSchema; exports.dimensionSchema = dimensionSchema; exports.discoverFiles = discoverFiles; exports.downstreamEntrySchema = downstreamEntrySchema; exports.emitManifest = emitManifest; exports.expressionSchema = expressionSchema; exports.fieldGovernanceSchema = fieldGovernanceSchema; exports.goldenQuerySchema = goldenQuerySchema; exports.governanceFileSchema = governanceFileSchema; exports.guardrailFilterSchema = guardrailFilterSchema; exports.hierarchySchema = hierarchySchema; exports.lineageFileSchema = lineageFileSchema; exports.lineageTypeEnum = lineageTypeEnum; exports.lintConfigSchema = lintConfigSchema; exports.loadConfig = loadConfig; exports.mcpConfigSchema = mcpConfigSchema; exports.metadataTierEnum = metadataTierEnum; exports.osiDatasetSchema = osiDatasetSchema; exports.osiDocumentSchema = osiDocumentSchema; exports.osiFieldSchema = osiFieldSchema; exports.osiMetricSchema = osiMetricSchema; exports.osiRelationshipSchema = osiRelationshipSchema; exports.osiSemanticModelSchema = osiSemanticModelSchema; exports.ownerFileSchema = ownerFileSchema; exports.parseFile = parseFile; exports.resolveReferences = resolveReferences; exports.rulesFileSchema = rulesFileSchema; exports.securityClassificationEnum = securityClassificationEnum; exports.semanticRoleEnum = semanticRoleEnum; exports.severityEnum = severityEnum; exports.severityOrOffEnum = severityOrOffEnum; exports.siteConfigSchema = siteConfigSchema; exports.tableTypeEnum = tableTypeEnum; exports.termFileSchema = termFileSchema; exports.trustStatusEnum = trustStatusEnum; exports.upstreamEntrySchema = upstreamEntrySchema; exports.validate = validate; exports.vendorEnum = vendorEnum;
1192
2007
  //# sourceMappingURL=index.cjs.map