@duckcodeailabs/dql-core 1.6.21 → 1.6.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/contracts/index.d.ts +1 -1
  2. package/dist/contracts/index.d.ts.map +1 -1
  3. package/dist/contracts/index.js.map +1 -1
  4. package/dist/contracts/registry.d.ts +25 -1
  5. package/dist/contracts/registry.d.ts.map +1 -1
  6. package/dist/contracts/registry.js +104 -12
  7. package/dist/contracts/registry.js.map +1 -1
  8. package/dist/contracts/types.d.ts +55 -0
  9. package/dist/contracts/types.d.ts.map +1 -1
  10. package/dist/contracts/types.js.map +1 -1
  11. package/dist/index.d.ts +1 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/lineage/builder.d.ts.map +1 -1
  16. package/dist/lineage/builder.js +12 -0
  17. package/dist/lineage/builder.js.map +1 -1
  18. package/dist/lineage/column-lineage.d.ts +27 -0
  19. package/dist/lineage/column-lineage.d.ts.map +1 -1
  20. package/dist/lineage/column-lineage.js +147 -0
  21. package/dist/lineage/column-lineage.js.map +1 -1
  22. package/dist/lineage/derivation.d.ts +111 -0
  23. package/dist/lineage/derivation.d.ts.map +1 -0
  24. package/dist/lineage/derivation.js +177 -0
  25. package/dist/lineage/derivation.js.map +1 -0
  26. package/dist/lineage/impact.d.ts +127 -0
  27. package/dist/lineage/impact.d.ts.map +1 -0
  28. package/dist/lineage/impact.js +305 -0
  29. package/dist/lineage/impact.js.map +1 -0
  30. package/dist/lineage/index.d.ts +3 -1
  31. package/dist/lineage/index.d.ts.map +1 -1
  32. package/dist/lineage/index.js +3 -1
  33. package/dist/lineage/index.js.map +1 -1
  34. package/dist/manifest/builder.d.ts +16 -0
  35. package/dist/manifest/builder.d.ts.map +1 -1
  36. package/dist/manifest/builder.js +117 -10
  37. package/dist/manifest/builder.js.map +1 -1
  38. package/dist/manifest/dbt-freshness.d.ts +65 -0
  39. package/dist/manifest/dbt-freshness.d.ts.map +1 -0
  40. package/dist/manifest/dbt-freshness.js +264 -0
  41. package/dist/manifest/dbt-freshness.js.map +1 -0
  42. package/dist/manifest/domain-writer.d.ts +66 -0
  43. package/dist/manifest/domain-writer.d.ts.map +1 -0
  44. package/dist/manifest/domain-writer.js +114 -0
  45. package/dist/manifest/domain-writer.js.map +1 -0
  46. package/dist/manifest/index.d.ts +4 -1
  47. package/dist/manifest/index.d.ts.map +1 -1
  48. package/dist/manifest/index.js +3 -0
  49. package/dist/manifest/index.js.map +1 -1
  50. package/dist/manifest/output-drift.d.ts +31 -0
  51. package/dist/manifest/output-drift.d.ts.map +1 -0
  52. package/dist/manifest/output-drift.js +97 -0
  53. package/dist/manifest/output-drift.js.map +1 -0
  54. package/dist/manifest/types.d.ts +156 -1
  55. package/dist/manifest/types.d.ts.map +1 -1
  56. package/dist/parser/parser.d.ts.map +1 -1
  57. package/dist/parser/parser.js +8 -0
  58. package/dist/parser/parser.js.map +1 -1
  59. package/dist/semantic/analyzer.d.ts +8 -0
  60. package/dist/semantic/analyzer.d.ts.map +1 -1
  61. package/dist/semantic/analyzer.js +8 -0
  62. package/dist/semantic/analyzer.js.map +1 -1
  63. package/dist/semantic/conflicts.d.ts +33 -0
  64. package/dist/semantic/conflicts.d.ts.map +1 -0
  65. package/dist/semantic/conflicts.js +270 -0
  66. package/dist/semantic/conflicts.js.map +1 -0
  67. package/dist/semantic/index.d.ts +1 -1
  68. package/dist/semantic/index.d.ts.map +1 -1
  69. package/dist/semantic/index.js +1 -1
  70. package/dist/semantic/index.js.map +1 -1
  71. package/dist/trust/index.d.ts +3 -0
  72. package/dist/trust/index.d.ts.map +1 -0
  73. package/dist/trust/index.js +2 -0
  74. package/dist/trust/index.js.map +1 -0
  75. package/dist/trust/labels.d.ts +122 -0
  76. package/dist/trust/labels.d.ts.map +1 -0
  77. package/dist/trust/labels.js +185 -0
  78. package/dist/trust/labels.js.map +1 -0
  79. package/package.json +5 -1
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Freshness-aware trust — read dbt run artifacts (READ-ONLY) and fold data
3
+ * health into the effective trust of certified blocks.
4
+ *
5
+ * "Certified" means the *logic* was reviewed. It says nothing about whether the
6
+ * *data* behind a block is fresh: an upstream dbt model may have failed its last
7
+ * run, or be past its freshness window. This module reads dbt's own artifacts —
8
+ * `run_results.json` (last-run status/time per model) and, when present, the
9
+ * source-freshness output — and derives a {@link DbtDataState} per dbt node. It
10
+ * then rolls those up to each certified block's transitive dbt upstreams so a
11
+ * surface can render "Certified · stale data" / "Certified · upstream failed".
12
+ *
13
+ * We never run dbt or query the warehouse — we only parse files dbt produced.
14
+ * Everything degrades to `unknown` (which surfaces as the plain "Certified"
15
+ * label) when an artifact is missing or unparseable, so this is fully additive.
16
+ *
17
+ * Kept in a dedicated module so `manifest/builder.ts` only needs a couple of
18
+ * call sites, keeping its diff minimal and merge-clean with the sibling
19
+ * `outputContract`/`drift` change.
20
+ */
21
+ import { readFileSync, existsSync } from 'node:fs';
22
+ import { dirname, join } from 'node:path';
23
+ /** Worst-first ordering used when rolling many upstream states into one. */
24
+ const STATE_SEVERITY = {
25
+ failed: 3,
26
+ stale: 2,
27
+ unknown: 1,
28
+ fresh: 0,
29
+ };
30
+ /** Return the worse (higher-severity) of two data states. */
31
+ export function worseDataState(a, b) {
32
+ return STATE_SEVERITY[a] >= STATE_SEVERITY[b] ? a : b;
33
+ }
34
+ /**
35
+ * Resolve and read dbt's `run_results.json` (and source-freshness output, when
36
+ * a separate `sources.json` sits beside it) from the directory holding the dbt
37
+ * `manifest.json`. Returns an empty index — never throws — when nothing is
38
+ * found or the file cannot be parsed.
39
+ *
40
+ * dbt writes both `manifest.json` and `run_results.json` into the same
41
+ * `target/` directory, so we look beside `manifestPath`.
42
+ */
43
+ export function loadDbtRunState(manifestPath) {
44
+ const targetDir = dirname(manifestPath);
45
+ const byUniqueId = new Map();
46
+ const runResultsPath = join(targetDir, 'run_results.json');
47
+ let foundPath;
48
+ if (existsSync(runResultsPath)) {
49
+ try {
50
+ const raw = JSON.parse(readFileSync(runResultsPath, 'utf-8'));
51
+ const generatedAt = raw?.metadata?.generated_at;
52
+ const results = Array.isArray(raw?.results) ? raw.results : [];
53
+ for (const result of results) {
54
+ const uniqueId = result?.unique_id;
55
+ if (!uniqueId)
56
+ continue;
57
+ byUniqueId.set(uniqueId, runStateFromRunResult(result, generatedAt));
58
+ }
59
+ if (results.length > 0)
60
+ foundPath = runResultsPath;
61
+ }
62
+ catch {
63
+ // Unparseable artifact — degrade silently to "unknown" everywhere.
64
+ }
65
+ }
66
+ // Optional source-freshness output. dbt's `source freshness` command writes a
67
+ // `sources.json` beside the manifest; fold any stale/error verdicts in. This
68
+ // overrides/augments run-result state for source nodes only.
69
+ const sourcesPath = join(targetDir, 'sources.json');
70
+ if (existsSync(sourcesPath)) {
71
+ try {
72
+ const raw = JSON.parse(readFileSync(sourcesPath, 'utf-8'));
73
+ const results = Array.isArray(raw?.results) ? raw.results : [];
74
+ for (const result of results) {
75
+ const uniqueId = result?.unique_id;
76
+ if (!uniqueId)
77
+ continue;
78
+ const freshness = freshnessFromSourceResult(result);
79
+ if (!freshness)
80
+ continue;
81
+ const existing = byUniqueId.get(uniqueId);
82
+ byUniqueId.set(uniqueId, {
83
+ dataState: existing
84
+ ? worseDataState(existing.dataState, freshness.dataState)
85
+ : freshness.dataState,
86
+ lastRunStatus: existing?.lastRunStatus,
87
+ lastRunCompletedAt: existing?.lastRunCompletedAt,
88
+ freshnessStatus: freshness.freshnessStatus,
89
+ maxLoadedAt: freshness.maxLoadedAt,
90
+ });
91
+ }
92
+ if (results.length > 0)
93
+ foundPath = foundPath ?? sourcesPath;
94
+ }
95
+ catch {
96
+ // Ignore — freshness is best-effort.
97
+ }
98
+ }
99
+ return { byUniqueId, runResultsPath: foundPath };
100
+ }
101
+ /** Map one `run_results.json` entry to a {@link DbtRunState}. */
102
+ function runStateFromRunResult(result, generatedAt) {
103
+ const status = result?.status;
104
+ // Prefer the precise per-node execute completion time from `timing`; fall back
105
+ // to the artifact's `generated_at` so we always carry *some* "as of" time.
106
+ const timing = Array.isArray(result?.timing) ? result.timing : [];
107
+ const executeCompletedAt = timing.find((t) => t?.name === 'execute')?.completed_at;
108
+ return {
109
+ dataState: dataStateFromRunStatus(status),
110
+ lastRunStatus: status,
111
+ lastRunCompletedAt: executeCompletedAt ?? generatedAt,
112
+ };
113
+ }
114
+ /**
115
+ * dbt run statuses: model runs report `success` | `error` | `skipped`; tests
116
+ * report `pass` | `fail` | `warn` | `error`. A failed or skipped upstream means
117
+ * the data may be missing or wrong → `failed`. Success → `fresh`. Anything
118
+ * unrecognized → `unknown` (no opinion).
119
+ */
120
+ function dataStateFromRunStatus(status) {
121
+ switch (status) {
122
+ case 'success':
123
+ case 'pass':
124
+ return 'fresh';
125
+ case 'error':
126
+ case 'fail':
127
+ case 'skipped':
128
+ case 'runtime error':
129
+ return 'failed';
130
+ default:
131
+ return 'unknown';
132
+ }
133
+ }
134
+ /** Map one source-freshness result to a freshness state. */
135
+ function freshnessFromSourceResult(result) {
136
+ const status = result?.status ?? result?.criteria?.status;
137
+ const maxLoadedAt = result?.max_loaded_at ?? result?.criteria?.max_loaded_at;
138
+ switch (status) {
139
+ case 'pass':
140
+ return { dataState: 'fresh', freshnessStatus: status, maxLoadedAt };
141
+ case 'warn':
142
+ case 'error':
143
+ // A freshness warn/error means the data is past its window: stale, not a
144
+ // hard run failure.
145
+ return { dataState: 'stale', freshnessStatus: status, maxLoadedAt };
146
+ case 'runtime error':
147
+ return { dataState: 'failed', freshnessStatus: status, maxLoadedAt };
148
+ default:
149
+ return status ? { dataState: 'unknown', freshnessStatus: status, maxLoadedAt } : undefined;
150
+ }
151
+ }
152
+ /**
153
+ * Compute each certified block's effective `dataState` from the health of its
154
+ * transitive dbt upstreams, mutating blocks additively in place.
155
+ *
156
+ * Resolution: a block's `tableDependencies` name dbt models/sources; we resolve
157
+ * each to a `sources[...]` entry that carries a `dbtModel.uniqueId`, then walk
158
+ * upstream through the dbt DAG (`edges`: source → target uniqueId) collecting
159
+ * every reachable node's run state. The block's `dataState` is the worst state
160
+ * among them. Blocks with no resolvable dbt upstream are left untouched.
161
+ *
162
+ * Only acts when a run-results artifact was actually read — without it every
163
+ * node is `unknown` and we leave blocks alone so nothing regresses to a noisy
164
+ * "· data freshness unknown" qualifier.
165
+ */
166
+ export function applyBlockDataState(blocks, sources, dbtDag, runState) {
167
+ if (runState.byUniqueId.size === 0)
168
+ return;
169
+ // uniqueId -> upstream uniqueIds (reverse of edges' source→target direction;
170
+ // an edge {source, target} means target depends on source).
171
+ const upstream = new Map();
172
+ for (const edge of dbtDag?.edges ?? []) {
173
+ const list = upstream.get(edge.target) ?? [];
174
+ list.push(edge.source);
175
+ upstream.set(edge.target, list);
176
+ }
177
+ // table name -> dbt uniqueId, via the sources map. Block table refs may be
178
+ // schema-qualified (`dev.customers`) while the dbt-model source is named bare
179
+ // (`customers`), so index every alias: the source name plus the model's
180
+ // bare / schema-qualified / db-qualified names derived from its dbt metadata.
181
+ const tableToUniqueId = new Map();
182
+ const addTableKey = (key, uid) => {
183
+ if (!key)
184
+ return;
185
+ const k = key.toLowerCase();
186
+ if (!tableToUniqueId.has(k))
187
+ tableToUniqueId.set(k, uid);
188
+ };
189
+ for (const source of Object.values(sources)) {
190
+ const dm = source.dbtModel;
191
+ if (!dm?.uniqueId)
192
+ continue;
193
+ addTableKey(source.name, dm.uniqueId);
194
+ const bare = dm.uniqueId.split('.').pop();
195
+ addTableKey(bare, dm.uniqueId);
196
+ if (dm.schema && bare)
197
+ addTableKey(`${dm.schema}.${bare}`, dm.uniqueId);
198
+ if (dm.database && dm.schema && bare)
199
+ addTableKey(`${dm.database}.${dm.schema}.${bare}`, dm.uniqueId);
200
+ }
201
+ // A node's own state, or `undefined` when dbt produced no run/freshness result
202
+ // for it (e.g. raw sources never "run"). Unstated nodes are NEUTRAL — being in
203
+ // a block's lineage must not drag it to "unknown" just because a source has no
204
+ // run record.
205
+ const stateFor = (uid) => runState.byUniqueId.get(uid)?.dataState;
206
+ // Memoised worst KNOWN state over the transitive upstream closure of a node.
207
+ const closureCache = new Map();
208
+ const worstUpstream = (uid, seen = new Set()) => {
209
+ if (closureCache.has(uid))
210
+ return closureCache.get(uid);
211
+ if (seen.has(uid))
212
+ return stateFor(uid);
213
+ seen.add(uid);
214
+ let worst = stateFor(uid);
215
+ for (const up of upstream.get(uid) ?? []) {
216
+ const s = worstUpstream(up, seen);
217
+ if (s !== undefined)
218
+ worst = worst === undefined ? s : worseDataState(worst, s);
219
+ }
220
+ closureCache.set(uid, worst);
221
+ return worst;
222
+ };
223
+ for (const block of Object.values(blocks)) {
224
+ const upstreamUids = [];
225
+ for (const table of block.tableDependencies ?? []) {
226
+ const lower = table.toLowerCase();
227
+ const uid = tableToUniqueId.get(lower) ?? tableToUniqueId.get(lower.split('.').pop() ?? lower);
228
+ if (uid)
229
+ upstreamUids.push(uid);
230
+ }
231
+ if (upstreamUids.length === 0)
232
+ continue;
233
+ let worst;
234
+ let worstUid;
235
+ for (const uid of upstreamUids) {
236
+ const state = worstUpstream(uid);
237
+ if (state === undefined)
238
+ continue;
239
+ if (worst === undefined || STATE_SEVERITY[state] > STATE_SEVERITY[worst]) {
240
+ worst = state;
241
+ worstUid = uid;
242
+ }
243
+ }
244
+ if (worst === undefined)
245
+ continue;
246
+ block.dataState = worst;
247
+ block.dataStateDetail = describeBlockDataState(worst, worstUid, runState);
248
+ }
249
+ }
250
+ function describeBlockDataState(state, worstUid, runState) {
251
+ if (state === 'fresh')
252
+ return 'All upstream dbt models are fresh (last run succeeded).';
253
+ const node = worstUid ? runState.byUniqueId.get(worstUid) : undefined;
254
+ const shortName = worstUid?.split('.').pop();
255
+ switch (state) {
256
+ case 'failed':
257
+ return `Upstream dbt model${shortName ? ` "${shortName}"` : ''} last run failed${node?.lastRunStatus ? ` (status: ${node.lastRunStatus})` : ''}.`;
258
+ case 'stale':
259
+ return `Upstream data${shortName ? ` from "${shortName}"` : ''} is past its freshness window${node?.freshnessStatus ? ` (freshness: ${node.freshnessStatus})` : ''}.`;
260
+ default:
261
+ return undefined;
262
+ }
263
+ }
264
+ //# sourceMappingURL=dbt-freshness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dbt-freshness.js","sourceRoot":"","sources":["../../src/manifest/dbt-freshness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Domain declaration writer (spec 17, part B).
3
+ *
4
+ * `scanDomains` reads first-class `domain "<Name>" { ... }` declarations and
5
+ * `dql doctor` warns when a used domain has no such declaration — but until now
6
+ * there was no way to AUTHOR one. This module emits a `domain` declaration in
7
+ * the EXACT format the parser (`parseDomainDecl`) + `scanDomains` accept, and
8
+ * resolves the canonical on-disk location for it.
9
+ *
10
+ * Convention (matches `scanDomains` + the "domain-first folders" test):
11
+ * domains/<slug>/domain.dql ← when a domain folder exists or is chosen
12
+ * domains/<slug>/domain.dql ← created if absent (the writer makes the dir)
13
+ *
14
+ * A domain authored here is `name`, `owner`, `boundedContext`, `sourceSystems`,
15
+ * `description`. `reviewCadence` is included with a sensible default so the
16
+ * doctor's "missing reviewCadence" warning is also satisfied.
17
+ */
18
+ /** A domain authored through the writer (spec 17, part B). */
19
+ export interface DomainInput {
20
+ name: string;
21
+ owner?: string;
22
+ boundedContext?: string;
23
+ sourceSystems?: string[];
24
+ description?: string;
25
+ /** Optional review cadence; defaults to "quarterly" so doctor stays quiet. */
26
+ reviewCadence?: string;
27
+ }
28
+ export interface WrittenDomain {
29
+ /** Project-relative path of the written `domain.dql`. */
30
+ path: string;
31
+ /** Absolute path on disk. */
32
+ absPath: string;
33
+ /** The folder slug under `domains/`. */
34
+ slug: string;
35
+ }
36
+ /** Normalize a domain name/id into a folder slug under `domains/`. */
37
+ export declare function domainFolderSlug(value: string): string;
38
+ /**
39
+ * Render a first-class `domain` declaration that `scanDomains` reads back. The
40
+ * field order + syntax mirror the parser's accepted set so a round-trip is
41
+ * lossless for the authored fields.
42
+ */
43
+ export declare function renderDomainDeclaration(domain: DomainInput): string;
44
+ /**
45
+ * Resolve the canonical `domains/<slug>/domain.dql` path for a domain. Honors an
46
+ * EXISTING `domain.dql` anywhere under `domains/<slug>/` (so a PUT/DELETE finds
47
+ * the file `scanDomains` actually read), else falls back to the canonical path.
48
+ */
49
+ export declare function resolveDomainDeclPath(projectRoot: string, nameOrSlug: string): {
50
+ absPath: string;
51
+ relativePath: string;
52
+ slug: string;
53
+ };
54
+ /**
55
+ * Write (create or overwrite) a first-class domain declaration. Returns the path
56
+ * written. The folder is created so subsequent block authoring can place blocks
57
+ * under `domains/<slug>/blocks/`.
58
+ */
59
+ export declare function writeDomainDeclaration(projectRoot: string, domain: DomainInput): WrittenDomain;
60
+ /**
61
+ * Delete a domain declaration file. Returns true when a file was removed. Only
62
+ * the declaration `.dql` is removed — authored blocks/terms under the folder are
63
+ * left untouched.
64
+ */
65
+ export declare function deleteDomainDeclaration(projectRoot: string, nameOrSlug: string): boolean;
66
+ //# sourceMappingURL=domain-writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-writer.d.ts","sourceRoot":"","sources":["../../src/manifest/domain-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,8DAA8D;AAC9D,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;CACd;AAMD,sEAAsE;AACtE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQtD;AAOD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAWnE;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,GACjB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAOzD;AAkBD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,aAAa,CAQ9F;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAKxF"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Domain declaration writer (spec 17, part B).
3
+ *
4
+ * `scanDomains` reads first-class `domain "<Name>" { ... }` declarations and
5
+ * `dql doctor` warns when a used domain has no such declaration — but until now
6
+ * there was no way to AUTHOR one. This module emits a `domain` declaration in
7
+ * the EXACT format the parser (`parseDomainDecl`) + `scanDomains` accept, and
8
+ * resolves the canonical on-disk location for it.
9
+ *
10
+ * Convention (matches `scanDomains` + the "domain-first folders" test):
11
+ * domains/<slug>/domain.dql ← when a domain folder exists or is chosen
12
+ * domains/<slug>/domain.dql ← created if absent (the writer makes the dir)
13
+ *
14
+ * A domain authored here is `name`, `owner`, `boundedContext`, `sourceSystems`,
15
+ * `description`. `reviewCadence` is included with a sensible default so the
16
+ * doctor's "missing reviewCadence" warning is also satisfied.
17
+ */
18
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
19
+ import { dirname, join, relative } from 'node:path';
20
+ function escapeString(value) {
21
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
22
+ }
23
+ /** Normalize a domain name/id into a folder slug under `domains/`. */
24
+ export function domainFolderSlug(value) {
25
+ return (value
26
+ .trim()
27
+ .toLowerCase()
28
+ .replace(/[^a-z0-9]+/g, '-')
29
+ .replace(/^-+|-+$/g, '') || 'domain');
30
+ }
31
+ function stringArrayField(name, values) {
32
+ if (!values || values.length === 0)
33
+ return '';
34
+ return `\n ${name} = [${values.map((v) => `"${escapeString(v)}"`).join(', ')}]`;
35
+ }
36
+ /**
37
+ * Render a first-class `domain` declaration that `scanDomains` reads back. The
38
+ * field order + syntax mirror the parser's accepted set so a round-trip is
39
+ * lossless for the authored fields.
40
+ */
41
+ export function renderDomainDeclaration(domain) {
42
+ const reviewCadence = domain.reviewCadence?.trim() || 'quarterly';
43
+ const lines = [`domain "${escapeString(domain.name)}" {`];
44
+ if (domain.owner)
45
+ lines.push(` owner = "${escapeString(domain.owner)}"`);
46
+ if (domain.boundedContext)
47
+ lines.push(` boundedContext = "${escapeString(domain.boundedContext)}"`);
48
+ lines.push(` reviewCadence = "${escapeString(reviewCadence)}"`);
49
+ let body = lines.join('\n');
50
+ body += stringArrayField('sourceSystems', domain.sourceSystems);
51
+ if (domain.description)
52
+ body += `\n description = "${escapeString(domain.description)}"`;
53
+ body += '\n}\n';
54
+ return body;
55
+ }
56
+ /**
57
+ * Resolve the canonical `domains/<slug>/domain.dql` path for a domain. Honors an
58
+ * EXISTING `domain.dql` anywhere under `domains/<slug>/` (so a PUT/DELETE finds
59
+ * the file `scanDomains` actually read), else falls back to the canonical path.
60
+ */
61
+ export function resolveDomainDeclPath(projectRoot, nameOrSlug) {
62
+ const slug = domainFolderSlug(nameOrSlug);
63
+ const domainDir = join(projectRoot, 'domains', slug);
64
+ // Prefer an existing declaration file (any `*.dql` that declares this domain).
65
+ const existing = findExistingDomainFile(domainDir);
66
+ const absPath = existing ?? join(domainDir, 'domain.dql');
67
+ return { absPath, relativePath: relative(projectRoot, absPath), slug };
68
+ }
69
+ /** Find an existing `.dql` file under a domain folder that declares a domain. */
70
+ function findExistingDomainFile(domainDir) {
71
+ if (!existsSync(domainDir))
72
+ return undefined;
73
+ try {
74
+ for (const entry of readdirSync(domainDir, { withFileTypes: true })) {
75
+ if (!entry.isFile() || !entry.name.endsWith('.dql'))
76
+ continue;
77
+ const file = join(domainDir, entry.name);
78
+ const source = readFileSync(file, 'utf-8');
79
+ if (/(^|\n)\s*domain\s+"/.test(source))
80
+ return file;
81
+ }
82
+ }
83
+ catch {
84
+ // Best-effort.
85
+ }
86
+ return undefined;
87
+ }
88
+ /**
89
+ * Write (create or overwrite) a first-class domain declaration. Returns the path
90
+ * written. The folder is created so subsequent block authoring can place blocks
91
+ * under `domains/<slug>/blocks/`.
92
+ */
93
+ export function writeDomainDeclaration(projectRoot, domain) {
94
+ if (!domain.name || !domain.name.trim()) {
95
+ throw new Error('writeDomainDeclaration requires a non-empty domain name.');
96
+ }
97
+ const { absPath, relativePath, slug } = resolveDomainDeclPath(projectRoot, domain.name);
98
+ mkdirSync(dirname(absPath), { recursive: true });
99
+ writeFileSync(absPath, renderDomainDeclaration(domain), 'utf-8');
100
+ return { path: relativePath, absPath, slug };
101
+ }
102
+ /**
103
+ * Delete a domain declaration file. Returns true when a file was removed. Only
104
+ * the declaration `.dql` is removed — authored blocks/terms under the folder are
105
+ * left untouched.
106
+ */
107
+ export function deleteDomainDeclaration(projectRoot, nameOrSlug) {
108
+ const { absPath } = resolveDomainDeclPath(projectRoot, nameOrSlug);
109
+ if (!existsSync(absPath))
110
+ return false;
111
+ rmSync(absPath, { force: true });
112
+ return true;
113
+ }
114
+ //# sourceMappingURL=domain-writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-writer.js","sourceRoot":"","sources":["../../src/manifest/domain-writer.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,6 @@
1
1
  export { buildManifest, collectInputFiles, loadProjectConfig, resolveDataLexManifestPath, resolveDbtManifestPath, type ManifestBuildOptions, type DbtImportFilters, } from './builder.js';
2
- export type { DQLManifest, ManifestBlock, ManifestTerm, ManifestBusinessView, ManifestNotebook, ManifestNotebookCell, ManifestMetric, ManifestDimension, ManifestSource, ManifestLineage, ManifestLineageNode, ManifestLineageEdge, ManifestDbtImport, ManifestDiagnostic, ManifestApp, ManifestDashboard, } from './types.js';
2
+ export { detectOutputDrift } from './output-drift.js';
3
+ export { writeDomainDeclaration, deleteDomainDeclaration, renderDomainDeclaration, resolveDomainDeclPath, domainFolderSlug, type DomainInput, type WrittenDomain, } from './domain-writer.js';
4
+ export { loadDbtRunState, applyBlockDataState, worseDataState, type DbtRunStateIndex, } from './dbt-freshness.js';
5
+ export type { DQLManifest, ManifestBlock, ManifestDomain, ManifestTerm, ManifestBusinessView, ManifestNotebook, ManifestNotebookCell, ManifestMetric, ManifestDimension, ManifestSource, ManifestLineage, ManifestLineageNode, ManifestLineageEdge, ManifestDbtImport, ManifestDiagnostic, ManifestConflictDetail, ManifestConflictSide, ManifestDriftDetail, ManifestApp, ManifestDashboard, DbtDataState, DbtRunState, } from './types.js';
3
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/manifest/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,0BAA0B,EAC1B,sBAAsB,EACtB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAC;AAEtB,YAAY,EACV,WAAW,EACX,aAAa,EACb,YAAY,EACZ,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,EACpB,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,iBAAiB,GAClB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/manifest/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,0BAA0B,EAC1B,sBAAsB,EACtB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,EACrB,gBAAgB,EAChB,KAAK,WAAW,EAChB,KAAK,aAAa,GACnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EACV,WAAW,EACX,aAAa,EACb,cAAc,EACd,YAAY,EACZ,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,EACpB,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,WAAW,EACX,iBAAiB,EACjB,YAAY,EACZ,WAAW,GACZ,MAAM,YAAY,CAAC"}
@@ -1,3 +1,6 @@
1
1
  // Manifest — DQL project compilation artifact
2
2
  export { buildManifest, collectInputFiles, loadProjectConfig, resolveDataLexManifestPath, resolveDbtManifestPath, } from './builder.js';
3
+ export { detectOutputDrift } from './output-drift.js';
4
+ export { writeDomainDeclaration, deleteDomainDeclaration, renderDomainDeclaration, resolveDomainDeclPath, domainFolderSlug, } from './domain-writer.js';
5
+ export { loadDbtRunState, applyBlockDataState, worseDataState, } from './dbt-freshness.js';
3
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/manifest/index.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAE9C,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,0BAA0B,EAC1B,sBAAsB,GAGvB,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/manifest/index.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAE9C,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,0BAA0B,EAC1B,sBAAsB,GAGvB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,EACrB,gBAAgB,GAGjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,cAAc,GAEf,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Output-contract drift detection.
3
+ *
4
+ * DQL composition is intentionally unbounded freeform `ref()` between blocks.
5
+ * The cost of that freedom: when a child block changes its output columns, a
6
+ * parent that `ref()`s it can break *silently*. We do not restrict composition
7
+ * — instead we make drift *visible*.
8
+ *
9
+ * This pass compares the columns a parent block references on a `ref()`'d child
10
+ * (extracted from the parent's SQL) against the child's current `outputContract`
11
+ * (its actual output schema). A referenced column that is missing/renamed
12
+ * produces a `kind: 'drift'`, `severity: 'warning'` diagnostic naming the
13
+ * parent, the child, and the drifted column. It never fails the build, and
14
+ * additive changes (the child gaining new columns) produce no warning.
15
+ *
16
+ * Non-goals (explicitly rejected — see the feature spec):
17
+ * - Hard compile-time parent→child binding / failing the build.
18
+ * - Runtime enforcement.
19
+ */
20
+ import type { ManifestBlock, ManifestDiagnostic } from './types.js';
21
+ /**
22
+ * Detect output-contract drift across all blocks. For each parent block that
23
+ * `ref()`s a child block and references one of its columns, warn when that
24
+ * column is no longer in the child's `outputContract`.
25
+ *
26
+ * Conservative on every axis: unknown child schemas, unparseable parent SQL,
27
+ * and ambiguous (multi-source, unqualified) column references all yield no
28
+ * warning rather than a false positive — see `extractRefColumnUsage`.
29
+ */
30
+ export declare function detectOutputDrift(blocks: Record<string, ManifestBlock>): ManifestDiagnostic[];
31
+ //# sourceMappingURL=output-drift.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-drift.d.ts","sourceRoot":"","sources":["../../src/manifest/output-drift.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AA4BpE;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,kBAAkB,EAAE,CA0C7F"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Output-contract drift detection.
3
+ *
4
+ * DQL composition is intentionally unbounded freeform `ref()` between blocks.
5
+ * The cost of that freedom: when a child block changes its output columns, a
6
+ * parent that `ref()`s it can break *silently*. We do not restrict composition
7
+ * — instead we make drift *visible*.
8
+ *
9
+ * This pass compares the columns a parent block references on a `ref()`'d child
10
+ * (extracted from the parent's SQL) against the child's current `outputContract`
11
+ * (its actual output schema). A referenced column that is missing/renamed
12
+ * produces a `kind: 'drift'`, `severity: 'warning'` diagnostic naming the
13
+ * parent, the child, and the drifted column. It never fails the build, and
14
+ * additive changes (the child gaining new columns) produce no warning.
15
+ *
16
+ * Non-goals (explicitly rejected — see the feature spec):
17
+ * - Hard compile-time parent→child binding / failing the build.
18
+ * - Runtime enforcement.
19
+ */
20
+ import { extractRefColumnUsage } from '../lineage/column-lineage.js';
21
+ /** Case-insensitive column-name key for comparing referenced vs. declared columns. */
22
+ function columnKey(name) {
23
+ return name.trim().toLowerCase();
24
+ }
25
+ /**
26
+ * The set of columns a child block guarantees, derived from its
27
+ * `outputContract`. Returns `null` when the child's output schema is unknown
28
+ * (no contract, or a contract that includes a star/unresolved entry) — in that
29
+ * case we cannot tell a real rename from an unparsed projection, so we stay
30
+ * silent rather than emit a false positive.
31
+ */
32
+ function knownChildColumns(child) {
33
+ const contract = child.outputContract;
34
+ if (!contract || contract.length === 0)
35
+ return null;
36
+ const names = new Set();
37
+ for (const entry of contract) {
38
+ const name = entry.name?.trim();
39
+ // A `*` / wildcard contract entry means the schema is open — we can't
40
+ // distinguish a renamed column from a still-present one.
41
+ if (!name || name === '*' || name.endsWith('.*'))
42
+ return null;
43
+ names.add(columnKey(name));
44
+ }
45
+ return names;
46
+ }
47
+ /**
48
+ * Detect output-contract drift across all blocks. For each parent block that
49
+ * `ref()`s a child block and references one of its columns, warn when that
50
+ * column is no longer in the child's `outputContract`.
51
+ *
52
+ * Conservative on every axis: unknown child schemas, unparseable parent SQL,
53
+ * and ambiguous (multi-source, unqualified) column references all yield no
54
+ * warning rather than a false positive — see `extractRefColumnUsage`.
55
+ */
56
+ export function detectOutputDrift(blocks) {
57
+ const diagnostics = [];
58
+ for (const parent of Object.values(blocks)) {
59
+ if (!parent.sql || !(parent.refDependencies?.length))
60
+ continue;
61
+ const usages = extractRefColumnUsage(parent.sql);
62
+ if (usages.length === 0)
63
+ continue;
64
+ for (const usage of usages) {
65
+ const child = blocks[usage.block];
66
+ if (!child)
67
+ continue; // unresolved ref — handled elsewhere as a resolve diagnostic
68
+ if (child.name === parent.name)
69
+ continue; // self-reference, ignore
70
+ const childColumns = knownChildColumns(child);
71
+ if (!childColumns)
72
+ continue; // child schema unknown — cannot judge drift
73
+ const available = (child.outputContract ?? []).map((c) => c.name);
74
+ for (const referenced of usage.columns) {
75
+ if (childColumns.has(columnKey(referenced)))
76
+ continue;
77
+ diagnostics.push({
78
+ kind: 'drift',
79
+ filePath: parent.filePath,
80
+ severity: 'warning',
81
+ message: `block "${parent.name}" references column "${referenced}" on ref("${child.name}"), ` +
82
+ `but "${child.name}" no longer outputs it` +
83
+ (available.length > 0 ? ` (current outputs: ${available.join(', ')})` : '') +
84
+ `. The child's output contract drifted — update the reference or restore the column.`,
85
+ drift: {
86
+ parentBlock: parent.name,
87
+ childBlock: child.name,
88
+ column: referenced,
89
+ availableColumns: available,
90
+ },
91
+ });
92
+ }
93
+ }
94
+ }
95
+ return diagnostics;
96
+ }
97
+ //# sourceMappingURL=output-drift.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-drift.js","sourceRoot":"","sources":["../../src/manifest/output-drift.ts"],"names":[],"mappings":"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"}