@sdd-method/sdd-cli 1.23.1 → 1.24.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.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/catalogue/canonical-schema.yaml +260 -312
- package/dist/lib/catalogue/rubric-coverage-map.yaml +60 -60
- package/dist/lib/metamodel/roundtrip-gate.d.ts +52 -0
- package/dist/lib/metamodel/roundtrip-gate.d.ts.map +1 -0
- package/dist/lib/metamodel/roundtrip-gate.js +297 -0
- package/dist/lib/metamodel/roundtrip-gate.js.map +1 -0
- package/dist/lib/metamodel/schema-projection.d.ts +60 -0
- package/dist/lib/metamodel/schema-projection.d.ts.map +1 -0
- package/dist/lib/metamodel/schema-projection.js +254 -0
- package/dist/lib/metamodel/schema-projection.js.map +1 -0
- package/dist/lib/metamodel/tql-subset-parser.d.ts +38 -0
- package/dist/lib/metamodel/tql-subset-parser.d.ts.map +1 -0
- package/dist/lib/metamodel/tql-subset-parser.js +275 -0
- package/dist/lib/metamodel/tql-subset-parser.js.map +1 -0
- package/dist/verbs/metamodel.d.ts +3 -0
- package/dist/verbs/metamodel.d.ts.map +1 -0
- package/dist/verbs/metamodel.js +28 -0
- package/dist/verbs/metamodel.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
// The catalogue-metamodel round-trip GATE (M-ADR 0210 §4.2; §4.6 flips 1 and 2) — the CLI verb that
|
|
2
|
+
// replaces the orchestration-script prototype under M-ADR 0127 §4.3.
|
|
3
|
+
//
|
|
4
|
+
// The authored form is the TypeQL pair; catalogue-schema.yaml and rubric-coverage-map.yaml are GENERATED
|
|
5
|
+
// projections of it. This gate is what makes that derivation checkable: it parses the documented subset
|
|
6
|
+
// fail-closed (tql-subset-parser), rebuilds both projections (schema-projection), and compares.
|
|
7
|
+
//
|
|
8
|
+
// Modes
|
|
9
|
+
// steady (default) each regenerated projection must be BYTE-IDENTICAL to the committed file
|
|
10
|
+
// migrate semantic comparison against OLD files, run once at a flip or an emitter change;
|
|
11
|
+
// `schema_status` is the only named difference, and only on the schema
|
|
12
|
+
// write regenerate and overwrite both committed projections
|
|
13
|
+
// self-test run the mutation controls alone and report
|
|
14
|
+
//
|
|
15
|
+
// Controls run FIRST in every verdict mode (M-ADR 0205 §4.3): seven mutations of the authored form must
|
|
16
|
+
// each be REFUSED before a passing comparison means anything. They are a property of the GENERATOR, not
|
|
17
|
+
// of the committed file: a mutation is refused when the parser rejects it or when it changes the
|
|
18
|
+
// regeneration relative to the unmutated authored form.
|
|
19
|
+
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
20
|
+
import { tmpdir } from "node:os";
|
|
21
|
+
import { join, relative, resolve } from "node:path";
|
|
22
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
23
|
+
import { ParseError, parseMeta, parseTypes } from "./tql-subset-parser.js";
|
|
24
|
+
import { projectMap, projectSchema } from "./schema-projection.js";
|
|
25
|
+
export const CANONICAL_PATHS = {
|
|
26
|
+
tql: "docs/method/foundations/schemas/catalogue-schema.tql",
|
|
27
|
+
meta: "docs/method/foundations/schemas/catalogue-schema-meta.tql",
|
|
28
|
+
schema: "docs/method/foundations/schemas/catalogue-schema.yaml",
|
|
29
|
+
map: "orchestration/rubric-coverage-map.yaml",
|
|
30
|
+
};
|
|
31
|
+
const VERB = "sdd-cli metamodel roundtrip";
|
|
32
|
+
const GENERATED_HEADER = "# GENERATED — DO NOT EDIT. This file is a projection of the authored model (M-ADR 0210 §4.6, flip 1):\n" +
|
|
33
|
+
"# docs/method/foundations/schemas/catalogue-schema.tql (the type system; header carries the bump log)\n" +
|
|
34
|
+
"# docs/method/foundations/schemas/catalogue-schema-meta.tql (what a type system cannot say)\n" +
|
|
35
|
+
`# Regenerate: ${VERB} --write\n` +
|
|
36
|
+
`# Guard: ${VERB} (any difference fails the build)\n` +
|
|
37
|
+
"# Direction of truth: authored .tql -> this projection (round-trip gate) -> vendored copies (digest hook).\n" +
|
|
38
|
+
"# Emitted by sdd-cli's YAML emitter with no line folding; the three header scalars are quoted for the\n" +
|
|
39
|
+
"# textual readers downstream. The one deliberate difference from the authored form is schema_status:\n" +
|
|
40
|
+
"# the authored form says canonical; a projection never does.\n";
|
|
41
|
+
const MAP_HEADER = "# GENERATED — DO NOT EDIT. This file is a projection of the authored model (M-ADR 0210 §4.6, flip 2):\n" +
|
|
42
|
+
"# docs/method/foundations/schemas/catalogue-schema-meta.tql — coverage-meta + relationship-meta rows carry\n" +
|
|
43
|
+
"# every entry here; the relations themselves (roles, players) are in catalogue-schema.tql.\n" +
|
|
44
|
+
`# Regenerate: ${VERB} --write\n` +
|
|
45
|
+
`# Guard: ${VERB} (any difference fails the build)\n` +
|
|
46
|
+
"# Vocabularies, rulings and the reading of each token live in the authored .tql header, as comments.\n";
|
|
47
|
+
function dump(obj) {
|
|
48
|
+
return stringifyYaml(obj, { lineWidth: 0, indentSeq: false, aliasDuplicateObjects: false });
|
|
49
|
+
}
|
|
50
|
+
/** Emit projection 1. The three header scalars are QUOTED: two downstream readers take the version textually. */
|
|
51
|
+
export function emitSchema(out) {
|
|
52
|
+
let text = dump(out);
|
|
53
|
+
for (const k of ["schema_version", "schema_owner", "schema_status"]) {
|
|
54
|
+
text = text.replace(new RegExp(`^${k}: (?!")([^\\n]+)$`, "m"), (_m, v) => `${k}: "${v}"`);
|
|
55
|
+
}
|
|
56
|
+
return GENERATED_HEADER + text;
|
|
57
|
+
}
|
|
58
|
+
export function emitMap(cov) {
|
|
59
|
+
return MAP_HEADER + dump(cov);
|
|
60
|
+
}
|
|
61
|
+
/** Parse + project + emit from the two authored texts. Throws ParseError (fail-closed). */
|
|
62
|
+
export function regenerateFromText(tqlText, metaText) {
|
|
63
|
+
const types = parseTypes(tqlText);
|
|
64
|
+
const meta = parseMeta(metaText);
|
|
65
|
+
const schema = projectSchema(types, meta);
|
|
66
|
+
const map = projectMap(meta);
|
|
67
|
+
return { schemaText: emitSchema(schema), mapText: emitMap(map), schema, map };
|
|
68
|
+
}
|
|
69
|
+
function retargetOnePlayer(tql) {
|
|
70
|
+
const m = /^(entity ([a-z0-9-]+), [^\n]*?)(, plays ([a-z0-9-]+:[a-z0-9-]+))/m.exec(tql);
|
|
71
|
+
if (!m)
|
|
72
|
+
return tql;
|
|
73
|
+
const start = m.index + m[1].length;
|
|
74
|
+
const stripped = tql.slice(0, start) + tql.slice(start + m[3].length);
|
|
75
|
+
const other = new RegExp(`^(entity (?!${m[2].replace(/[-]/g, "\\-")}\\b)([a-z0-9-]+), [^\\n]*?)(;)$`, "m").exec(stripped);
|
|
76
|
+
if (!other)
|
|
77
|
+
return tql;
|
|
78
|
+
const at = other.index + other[1].length;
|
|
79
|
+
return stripped.slice(0, at) + `, plays ${m[4]}` + stripped.slice(at);
|
|
80
|
+
}
|
|
81
|
+
export function mutations(tql, meta) {
|
|
82
|
+
return [
|
|
83
|
+
{ label: "@values on an owns clause", tql: tql.replace(/(entity [a-z0-9-]+, owns [a-z0-9-]+ @key)/, '$1 @values("x")'), meta },
|
|
84
|
+
{ label: "@card(2..3) in place of @card(1..1)", tql: tql.replace(/^(entity [^\n#]*?)@card\(1\.\.1\)/m, "$1@card(2..3)"), meta },
|
|
85
|
+
{
|
|
86
|
+
label: "a role with two players",
|
|
87
|
+
tql: tql + "relation ctl-rel, relates ctl-role;\nentity ctl-a, plays ctl-rel:ctl-role;\nentity ctl-b, plays ctl-rel:ctl-role;\n",
|
|
88
|
+
meta,
|
|
89
|
+
},
|
|
90
|
+
{ label: "attribute value type contradicting the column's type family", tql: tql.replace(/^attribute id, value string;$/m, "attribute id, value integer;"), meta },
|
|
91
|
+
{ label: "a changed description must make the projection differ", tql, meta: meta.replace('has m-description "', 'has m-description "CONTROL-MUTATION ') },
|
|
92
|
+
{ label: "a role's cardinality change must change the projection (nullable)", tql: tql.replace(/^(relation [^\n#]*?relates [a-z0-9-]+ )@card\(1\.\.1\)/m, "$1@card(0..1)"), meta },
|
|
93
|
+
{ label: "a role player disagreeing with the declared permitted player is refused", tql: retargetOnePlayer(tql), meta },
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
export function runControls(tql, meta) {
|
|
97
|
+
let clean;
|
|
98
|
+
try {
|
|
99
|
+
clean = regenerateFromText(tql, meta);
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
return { ok: false, fired: 0, total: 7, results: [], cleanError: e.message };
|
|
103
|
+
}
|
|
104
|
+
const results = [];
|
|
105
|
+
for (const m of mutations(tql, meta)) {
|
|
106
|
+
if (m.tql === tql && m.meta === meta) {
|
|
107
|
+
results.push({ label: m.label, refused: false, reason: "mutation did not apply — the anchor text is missing" });
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const r = regenerateFromText(m.tql, m.meta);
|
|
112
|
+
const refused = r.schemaText !== clean.schemaText || r.mapText !== clean.mapText;
|
|
113
|
+
results.push({ label: m.label, refused, reason: refused ? "regeneration differs from the unmutated form" : "NOT FIRED — mutation left the regeneration unchanged" });
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
if (!(e instanceof ParseError))
|
|
117
|
+
throw e;
|
|
118
|
+
results.push({ label: m.label, refused: true, reason: `refused: ${e.message}` });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const fired = results.filter((r) => r.refused).length;
|
|
122
|
+
return { ok: fired === results.length, fired, total: results.length, results };
|
|
123
|
+
}
|
|
124
|
+
// ---- semantic diff for migration mode ----
|
|
125
|
+
export function* semanticDiff(a, b, path = "$") {
|
|
126
|
+
if (a !== null && b !== null && typeof a === "object" && typeof b === "object" && !Array.isArray(a) && !Array.isArray(b)) {
|
|
127
|
+
const ao = a;
|
|
128
|
+
const bo = b;
|
|
129
|
+
for (const k of [...new Set([...Object.keys(ao), ...Object.keys(bo)])].sort()) {
|
|
130
|
+
if (!(k in ao))
|
|
131
|
+
yield `${path}.${k}: only in regenerated`;
|
|
132
|
+
else if (!(k in bo))
|
|
133
|
+
yield `${path}.${k}: only in old`;
|
|
134
|
+
else
|
|
135
|
+
yield* semanticDiff(ao[k], bo[k], `${path}.${k}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else if (Array.isArray(a) && Array.isArray(b)) {
|
|
139
|
+
if (a.length !== b.length)
|
|
140
|
+
yield `${path}: list length ${a.length} vs ${b.length}`;
|
|
141
|
+
for (let i = 0; i < Math.min(a.length, b.length); i++)
|
|
142
|
+
yield* semanticDiff(a[i], b[i], `${path}[${i}]`);
|
|
143
|
+
}
|
|
144
|
+
else if (JSON.stringify(a) !== JSON.stringify(b)) {
|
|
145
|
+
yield `${path}: ${JSON.stringify(a)} vs ${JSON.stringify(b)}`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function unifiedDiffLines(committed, regen, max = 30) {
|
|
149
|
+
const a = committed.split("\n");
|
|
150
|
+
const b = regen.split("\n");
|
|
151
|
+
const out = [];
|
|
152
|
+
const n = Math.max(a.length, b.length);
|
|
153
|
+
for (let i = 0; i < n && out.length < max; i++) {
|
|
154
|
+
if (a[i] !== b[i]) {
|
|
155
|
+
if (a[i] !== undefined)
|
|
156
|
+
out.push(`-${i + 1}: ${a[i].slice(0, 180)}`);
|
|
157
|
+
if (b[i] !== undefined)
|
|
158
|
+
out.push(`+${i + 1}: ${b[i].slice(0, 180)}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
/** Run the gate. Exit codes: 0 pass; 1 projection differs; 2 authored form does not parse; 3 controls did not all fire. */
|
|
164
|
+
export async function runMetamodelRoundtrip(inputs, write) {
|
|
165
|
+
const root = resolve(inputs.root ?? process.cwd());
|
|
166
|
+
const mode = inputs.mode ?? "steady";
|
|
167
|
+
const p = {
|
|
168
|
+
tql: join(root, CANONICAL_PATHS.tql),
|
|
169
|
+
meta: join(root, CANONICAL_PATHS.meta),
|
|
170
|
+
schema: join(root, CANONICAL_PATHS.schema),
|
|
171
|
+
map: join(root, CANONICAL_PATHS.map),
|
|
172
|
+
};
|
|
173
|
+
let tql;
|
|
174
|
+
let meta;
|
|
175
|
+
try {
|
|
176
|
+
tql = await readFile(p.tql, "utf-8");
|
|
177
|
+
meta = await readFile(p.meta, "utf-8");
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
write(`[FAIL] authored form not found under ${root} (${e.message}) — run from the canonical method repo or pass --root\n`);
|
|
181
|
+
return { exitCode: 2 };
|
|
182
|
+
}
|
|
183
|
+
if (mode !== "write") {
|
|
184
|
+
const c = runControls(tql, meta);
|
|
185
|
+
if (c.cleanError !== undefined) {
|
|
186
|
+
write(` [FAIL] control of the controls: the unmutated authored form does not parse — ${c.cleanError}\n`);
|
|
187
|
+
write(`[FAIL] 0/${c.total} controls fired; refusing to report a verdict (M-ADR 0205 §4.3: controls run first)\n`);
|
|
188
|
+
return { exitCode: 3 };
|
|
189
|
+
}
|
|
190
|
+
for (const r of c.results)
|
|
191
|
+
write(` [${r.refused ? "ok" : "NOT FIRED"}] control: ${r.label} — ${r.reason.slice(0, 150)}\n`);
|
|
192
|
+
write(` [ok] control of the controls: the unmutated authored form parses and projects\n`);
|
|
193
|
+
if (mode === "self-test") {
|
|
194
|
+
write(`[${c.ok ? "PASS" : "FAIL"}] round-trip gate self-test: ${c.fired}/${c.total} controls fired${c.ok ? "" : " — a control that does not fire is a gate that cannot"}\n`);
|
|
195
|
+
return { exitCode: c.ok ? 0 : 3 };
|
|
196
|
+
}
|
|
197
|
+
if (!c.ok) {
|
|
198
|
+
write(`[FAIL] ${c.fired}/${c.total} controls fired; refusing to report a verdict (M-ADR 0205 §4.3: controls run first)\n`);
|
|
199
|
+
return { exitCode: 3 };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
let regen;
|
|
203
|
+
try {
|
|
204
|
+
regen = regenerateFromText(tql, meta);
|
|
205
|
+
}
|
|
206
|
+
catch (e) {
|
|
207
|
+
if (!(e instanceof ParseError))
|
|
208
|
+
throw e;
|
|
209
|
+
write(`[FAIL] the authored form does not parse (fail-closed): ${e.message}\n`);
|
|
210
|
+
return { exitCode: 2 };
|
|
211
|
+
}
|
|
212
|
+
const rel = (x) => relative(root, x);
|
|
213
|
+
if (mode === "write") {
|
|
214
|
+
await writeFile(p.schema, regen.schemaText, "utf-8");
|
|
215
|
+
await writeFile(p.map, regen.mapText, "utf-8");
|
|
216
|
+
const cats = Object.values(regen.schema.catalogues);
|
|
217
|
+
write(`[PASS] wrote ${rel(p.schema)}: ${cats.length} catalogues, ${Object.keys(regen.schema.enums).length} enums, ` +
|
|
218
|
+
`${cats.reduce((n, c) => n + c.columns.length, 0)} columns, ${cats.reduce((n, c) => n + (c.foreign_keys?.length ?? 0), 0)} foreign keys, schema_status generated\n`);
|
|
219
|
+
const cov = Object.values(regen.map.coverage);
|
|
220
|
+
write(`[PASS] wrote ${rel(p.map)}: ${cov.length} coverage entries, ${cov.filter((v) => v.class !== undefined).length} edge declarations, ` +
|
|
221
|
+
`${cov.reduce((n, v) => n + Object.keys(v.relationships ?? {}).length, 0)} entity-FK declarations\n`);
|
|
222
|
+
return { exitCode: 0 };
|
|
223
|
+
}
|
|
224
|
+
if (mode === "migrate") {
|
|
225
|
+
if (inputs.oldSchema === undefined) {
|
|
226
|
+
write(`[FAIL] --migrate needs the OLD schema projection path\n`);
|
|
227
|
+
return { exitCode: 1 };
|
|
228
|
+
}
|
|
229
|
+
let rc = 0;
|
|
230
|
+
const prev = parseYaml(await readFile(resolve(inputs.oldSchema), "utf-8"));
|
|
231
|
+
const next = parseYaml(regen.schemaText);
|
|
232
|
+
if (next["schema_status"] !== "generated" || !["canonical", "generated"].includes(String(prev["schema_status"]))) {
|
|
233
|
+
write(`[FAIL] migration comparison expects new=generated; got ${String(prev["schema_status"])}/${String(next["schema_status"])}\n`);
|
|
234
|
+
return { exitCode: 1 };
|
|
235
|
+
}
|
|
236
|
+
const diffs = [...semanticDiff(prev, next)].filter((x) => !x.startsWith("$.schema_status"));
|
|
237
|
+
write(`migration comparison (schema): ${Object.keys(regen.schema.catalogues).length} catalogues, ${Object.keys(regen.schema.enums).length} enums; semantic differences excluding schema_status: ${diffs.length}\n`);
|
|
238
|
+
for (const x of diffs.slice(0, 20))
|
|
239
|
+
write(` ${x.slice(0, 200)}\n`);
|
|
240
|
+
write(`[${diffs.length === 0 ? "PASS" : "FAIL"}] ROUND TRIP (migration, schema): ${diffs.length === 0 ? "EXACT" : "DIFFERS"}\n`);
|
|
241
|
+
if (diffs.length > 0)
|
|
242
|
+
rc = 1;
|
|
243
|
+
if (inputs.oldMap !== undefined) {
|
|
244
|
+
const prevm = parseYaml(await readFile(resolve(inputs.oldMap), "utf-8"));
|
|
245
|
+
const nextm = parseYaml(regen.mapText);
|
|
246
|
+
const dm = [...semanticDiff(prevm, nextm)];
|
|
247
|
+
write(`migration comparison (coverage map): ${Object.keys(regen.map.coverage).length} entries; semantic differences: ${dm.length}\n`);
|
|
248
|
+
for (const x of dm.slice(0, 20))
|
|
249
|
+
write(` ${x.slice(0, 200)}\n`);
|
|
250
|
+
write(`[${dm.length === 0 ? "PASS" : "FAIL"}] ROUND TRIP (migration, coverage map): ${dm.length === 0 ? "EXACT" : "DIFFERS"}\n`);
|
|
251
|
+
if (dm.length > 0)
|
|
252
|
+
rc = 1;
|
|
253
|
+
}
|
|
254
|
+
return { exitCode: rc };
|
|
255
|
+
}
|
|
256
|
+
let rc = 0;
|
|
257
|
+
for (const [label, path, text] of [
|
|
258
|
+
["schema", p.schema, regen.schemaText],
|
|
259
|
+
["coverage map", p.map, regen.mapText],
|
|
260
|
+
]) {
|
|
261
|
+
let committed;
|
|
262
|
+
try {
|
|
263
|
+
committed = await readFile(path, "utf-8");
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
write(`[FAIL] ROUND TRIP (steady state, ${label}): ${rel(path)} is missing — regenerate with --write\n`);
|
|
267
|
+
rc = 1;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (text === committed) {
|
|
271
|
+
write(`[PASS] ROUND TRIP (steady state, ${label}): EXACT — ${rel(path)} is byte-identical to its regeneration\n`);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
rc = 1;
|
|
275
|
+
write(`[FAIL] ROUND TRIP (steady state, ${label}): DIFFERS — the committed projection is not what the authored form generates\n`);
|
|
276
|
+
for (const l of unifiedDiffLines(committed, text))
|
|
277
|
+
write(` ${l}\n`);
|
|
278
|
+
write(` regenerate with --write, or fix the authored form; never edit a projection\n`);
|
|
279
|
+
}
|
|
280
|
+
return { exitCode: rc };
|
|
281
|
+
}
|
|
282
|
+
/** Test helper: write two authored texts to a temp dir laid out like the canonical repo. */
|
|
283
|
+
export async function withAuthoredTemp(tql, meta, fn) {
|
|
284
|
+
const root = await mkdtemp(join(tmpdir(), "sdd-metamodel-"));
|
|
285
|
+
try {
|
|
286
|
+
const { mkdir } = await import("node:fs/promises");
|
|
287
|
+
await mkdir(join(root, "docs/method/foundations/schemas"), { recursive: true });
|
|
288
|
+
await mkdir(join(root, "orchestration"), { recursive: true });
|
|
289
|
+
await writeFile(join(root, CANONICAL_PATHS.tql), tql, "utf-8");
|
|
290
|
+
await writeFile(join(root, CANONICAL_PATHS.meta), meta, "utf-8");
|
|
291
|
+
return await fn(root);
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
294
|
+
await rm(root, { recursive: true, force: true });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
//# sourceMappingURL=roundtrip-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roundtrip-gate.js","sourceRoot":"","sources":["../../../src/lib/metamodel/roundtrip-gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ParsedMeta, ParsedTypes } from "./tql-subset-parser.js";
|
|
2
|
+
export interface ColumnSpec {
|
|
3
|
+
name: string;
|
|
4
|
+
type: string;
|
|
5
|
+
nullable: boolean;
|
|
6
|
+
description?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ForeignKeySpec {
|
|
9
|
+
column: string;
|
|
10
|
+
references: string;
|
|
11
|
+
nullable: boolean;
|
|
12
|
+
external?: boolean;
|
|
13
|
+
external_for_profiles?: string[];
|
|
14
|
+
}
|
|
15
|
+
export interface CatalogueSpec {
|
|
16
|
+
primary_key: string | null;
|
|
17
|
+
description?: string;
|
|
18
|
+
columns: ColumnSpec[];
|
|
19
|
+
foreign_keys?: ForeignKeySpec[];
|
|
20
|
+
column_enums?: Record<string, string>;
|
|
21
|
+
}
|
|
22
|
+
export interface EnumSpec {
|
|
23
|
+
description?: string;
|
|
24
|
+
values: Record<string, unknown>[];
|
|
25
|
+
}
|
|
26
|
+
export interface SchemaProjection {
|
|
27
|
+
schema_version: string;
|
|
28
|
+
schema_owner: string;
|
|
29
|
+
schema_status: "generated";
|
|
30
|
+
enums: Record<string, EnumSpec>;
|
|
31
|
+
catalogues: Record<string, CatalogueSpec>;
|
|
32
|
+
}
|
|
33
|
+
export interface CoverageEntry {
|
|
34
|
+
kind: string;
|
|
35
|
+
class?: string;
|
|
36
|
+
impact_flow?: string;
|
|
37
|
+
impact_flow_by?: {
|
|
38
|
+
column: string;
|
|
39
|
+
values: unknown;
|
|
40
|
+
};
|
|
41
|
+
scoring: string;
|
|
42
|
+
instrument?: string;
|
|
43
|
+
reason?: string;
|
|
44
|
+
relationships?: Record<string, {
|
|
45
|
+
class: string;
|
|
46
|
+
impact_flow?: string;
|
|
47
|
+
impact_flow_by?: {
|
|
48
|
+
column: string;
|
|
49
|
+
values: unknown;
|
|
50
|
+
};
|
|
51
|
+
}>;
|
|
52
|
+
}
|
|
53
|
+
export interface MapProjection {
|
|
54
|
+
coverage: Record<string, CoverageEntry>;
|
|
55
|
+
}
|
|
56
|
+
/** Projection 1: the relational catalogue schema. */
|
|
57
|
+
export declare function projectSchema(types: ParsedTypes, meta: ParsedMeta): SchemaProjection;
|
|
58
|
+
/** Projection 2: the rubric-coverage-map from coverage-meta + relationship-meta rows. */
|
|
59
|
+
export declare function projectMap(meta: ParsedMeta): MapProjection;
|
|
60
|
+
//# sourceMappingURL=schema-projection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-projection.d.ts","sourceRoot":"","sources":["../../../src/lib/metamodel/schema-projection.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAmC,UAAU,EAAE,WAAW,EAAY,MAAM,wBAAwB,CAAC;AAWjH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AACD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;CAClC;AACD,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,cAAc,EAAE,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AACD,MAAM,WAAW,QAAQ;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;CACnC;AACD,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,WAAW,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CAC3C;AACD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,OAAO,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;CAC/H;AACD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CACzC;AAwBD,qDAAqD;AACrD,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,GAAG,gBAAgB,CAgJpF;AAED,yFAAyF;AACzF,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,aAAa,CAoC1D"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// The catalogue metamodel's two PROJECTIONS of the parsed authored form (M-ADR 0210 §4.6, flips 1 and 2):
|
|
2
|
+
// 1. catalogue-schema.yaml — the relational schema every catalogue reader consumes
|
|
3
|
+
// 2. rubric-coverage-map.yaml — the coverage registry (class, impact flow, scoring) M-ADR 0206 declares
|
|
4
|
+
//
|
|
5
|
+
// This module consumes `ParsedTypes` + `ParsedMeta` and produces plain objects; it neither reads files
|
|
6
|
+
// nor emits YAML (that is `roundtrip-gate.ts`). Every structural check that is not the grammar's lives
|
|
7
|
+
// here: one @key per catalogue, every owned attribute has a column, a role-backed column has an fk row,
|
|
8
|
+
// and — M-ADR 0210 §4.3, the permitted-players check — the type system's player of a role IS the fk
|
|
9
|
+
// row's declared target. A disagreement is a ParseError: fail-closed, never a skip.
|
|
10
|
+
import { META_TYPES, ParseError } from "./tql-subset-parser.js";
|
|
11
|
+
const FAMILY = {
|
|
12
|
+
string: "string",
|
|
13
|
+
int: "integer",
|
|
14
|
+
integer: "integer",
|
|
15
|
+
number: "double",
|
|
16
|
+
date: "date",
|
|
17
|
+
boolean: "boolean",
|
|
18
|
+
};
|
|
19
|
+
function str(row, k) {
|
|
20
|
+
const v = row[k];
|
|
21
|
+
if (typeof v !== "string")
|
|
22
|
+
throw new ParseError(`meta row missing string ${k}: ${JSON.stringify(row)}`);
|
|
23
|
+
return v;
|
|
24
|
+
}
|
|
25
|
+
function num(row, k) {
|
|
26
|
+
const v = row[k];
|
|
27
|
+
if (typeof v !== "number")
|
|
28
|
+
throw new ParseError(`meta row missing integer ${k}: ${JSON.stringify(row)}`);
|
|
29
|
+
return v;
|
|
30
|
+
}
|
|
31
|
+
function has(row, k) {
|
|
32
|
+
return Object.prototype.hasOwnProperty.call(row, k);
|
|
33
|
+
}
|
|
34
|
+
function byPosition(rows) {
|
|
35
|
+
return [...rows].sort((a, b) => num(a, "m-position") - num(b, "m-position"));
|
|
36
|
+
}
|
|
37
|
+
function one(meta, typ, where) {
|
|
38
|
+
const rows = meta.get(typ) ?? [];
|
|
39
|
+
if (rows.length !== 1)
|
|
40
|
+
throw new ParseError(`${where}: exactly one ${typ} row required, found ${rows.length}`);
|
|
41
|
+
return rows[0];
|
|
42
|
+
}
|
|
43
|
+
/** Projection 1: the relational catalogue schema. */
|
|
44
|
+
export function projectSchema(types, meta) {
|
|
45
|
+
const { attrs, entities, relations, players } = types;
|
|
46
|
+
const sm = one(meta, "schema-meta", "schema");
|
|
47
|
+
if (sm["m-schema-status"] !== "canonical") {
|
|
48
|
+
throw new ParseError('the authored form must carry m-schema-status "canonical"; the projection alone says generated');
|
|
49
|
+
}
|
|
50
|
+
const out = {
|
|
51
|
+
schema_version: str(sm, "m-schema-version"),
|
|
52
|
+
schema_owner: str(sm, "m-schema-owner"),
|
|
53
|
+
schema_status: "generated",
|
|
54
|
+
enums: {},
|
|
55
|
+
catalogues: {},
|
|
56
|
+
};
|
|
57
|
+
const enumValues = meta.get("enum-value-meta") ?? [];
|
|
58
|
+
const enumProps = meta.get("enum-value-property") ?? [];
|
|
59
|
+
for (const e of byPosition(meta.get("enum-meta") ?? [])) {
|
|
60
|
+
const name = str(e, "m-enum");
|
|
61
|
+
const spec = { values: [] };
|
|
62
|
+
if (has(e, "m-description"))
|
|
63
|
+
spec.description = str(e, "m-description");
|
|
64
|
+
for (const v of byPosition(enumValues.filter((x) => x["m-enum"] === name))) {
|
|
65
|
+
const row = { value: str(v, "m-value") };
|
|
66
|
+
if (has(v, "m-description"))
|
|
67
|
+
row["description"] = str(v, "m-description");
|
|
68
|
+
for (const pr of byPosition(enumProps.filter((x) => x["m-enum"] === name && x["m-value"] === v["m-value"]))) {
|
|
69
|
+
row[str(pr, "m-property")] = JSON.parse(str(pr, "m-json"));
|
|
70
|
+
}
|
|
71
|
+
spec.values.push(row);
|
|
72
|
+
}
|
|
73
|
+
if (name in out.enums)
|
|
74
|
+
throw new ParseError(`enum ${name} declared twice`);
|
|
75
|
+
out.enums[name] = spec;
|
|
76
|
+
}
|
|
77
|
+
const typesByLabel = new Map();
|
|
78
|
+
for (const [k, v] of entities)
|
|
79
|
+
typesByLabel.set(k, { kind: "entity", decl: v });
|
|
80
|
+
for (const [k, v] of relations)
|
|
81
|
+
typesByLabel.set(k, { kind: "relation", decl: v });
|
|
82
|
+
const catRows = byPosition(meta.get("catalogue-meta") ?? []);
|
|
83
|
+
const labels = new Set(catRows.map((c) => str(c, "m-type-label")));
|
|
84
|
+
for (const lab of typesByLabel.keys()) {
|
|
85
|
+
if (!META_TYPES.has(lab) && !labels.has(lab))
|
|
86
|
+
throw new ParseError(`type ${JSON.stringify(lab)} has no catalogue-meta row`);
|
|
87
|
+
}
|
|
88
|
+
for (const c of catRows) {
|
|
89
|
+
if (!typesByLabel.has(str(c, "m-type-label"))) {
|
|
90
|
+
throw new ParseError(`catalogue-meta ${str(c, "m-catalogue")} names undeclared type ${JSON.stringify(str(c, "m-type-label"))}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const catOfLabel = new Map(catRows.map((c) => [str(c, "m-type-label"), str(c, "m-catalogue")]));
|
|
94
|
+
const columnMeta = meta.get("column-meta") ?? [];
|
|
95
|
+
const fkMeta = meta.get("fk-meta") ?? [];
|
|
96
|
+
const colEnumMeta = meta.get("column-enum-meta") ?? [];
|
|
97
|
+
const seen = new Set();
|
|
98
|
+
for (const c of catRows) {
|
|
99
|
+
const name = str(c, "m-catalogue");
|
|
100
|
+
const t = str(c, "m-type-label");
|
|
101
|
+
if (seen.has(name))
|
|
102
|
+
throw new ParseError(`catalogue ${name} declared twice`);
|
|
103
|
+
seen.add(name);
|
|
104
|
+
const { kind, decl } = typesByLabel.get(t);
|
|
105
|
+
const owns = decl.owns;
|
|
106
|
+
const relates = decl.relates;
|
|
107
|
+
const pk = [...owns].filter(([, a]) => a.key).map(([n]) => n);
|
|
108
|
+
if (pk.length !== 1)
|
|
109
|
+
throw new ParseError(`${name}: expected exactly one @key attribute, found ${pk.length}`);
|
|
110
|
+
const colsMeta = byPosition(columnMeta.filter((x) => x["m-catalogue"] === name));
|
|
111
|
+
const fkRows = byPosition(fkMeta.filter((x) => x["m-catalogue"] === name));
|
|
112
|
+
const fkByCol = new Map(fkRows.map((f) => [str(f, "m-column"), f]));
|
|
113
|
+
const cols = [];
|
|
114
|
+
let pkName = null;
|
|
115
|
+
const attrSeen = new Set();
|
|
116
|
+
const roleSeen = new Set();
|
|
117
|
+
for (const cm of colsMeta) {
|
|
118
|
+
const column = str(cm, "m-column");
|
|
119
|
+
const yamlType = str(cm, "m-yaml-type");
|
|
120
|
+
const fam = FAMILY[yamlType];
|
|
121
|
+
if (fam === undefined)
|
|
122
|
+
throw new ParseError(`${name}.${column}: unknown yaml type ${JSON.stringify(yamlType)}`);
|
|
123
|
+
let nullable;
|
|
124
|
+
if (has(cm, "m-role")) {
|
|
125
|
+
const r = str(cm, "m-role");
|
|
126
|
+
if (kind !== "relation")
|
|
127
|
+
throw new ParseError(`${name}.${column}: column meta names a role but ${t} is an entity`);
|
|
128
|
+
const ann = relates.get(r);
|
|
129
|
+
if (!ann)
|
|
130
|
+
throw new ParseError(`${name}.${column}: relation ${t} does not relate ${r}`);
|
|
131
|
+
if (roleSeen.has(r))
|
|
132
|
+
throw new ParseError(`${name}: role ${r} appears on two columns`);
|
|
133
|
+
roleSeen.add(r);
|
|
134
|
+
if (!(ann.required || ann.optional))
|
|
135
|
+
throw new ParseError(`${name}.${column}: role ${r} must carry @card(1..1) or @card(0..1)`);
|
|
136
|
+
const fk = fkByCol.get(column);
|
|
137
|
+
if (!fk)
|
|
138
|
+
throw new ParseError(`${name}.${column}: a role-backed column must have an fk-meta row (the declared permitted player)`);
|
|
139
|
+
// M-ADR 0210 §4.3 — the permitted-players check
|
|
140
|
+
const player = players.get(`${t}:${r}`);
|
|
141
|
+
if (player === undefined)
|
|
142
|
+
throw new ParseError(`${name}.${column}: role ${t}:${r} has no player`);
|
|
143
|
+
const declared = str(fk, "m-references").split(".")[0];
|
|
144
|
+
if (catOfLabel.get(player) !== declared) {
|
|
145
|
+
throw new ParseError(`${name}.${column}: role ${t}:${r} is played by ${JSON.stringify(catOfLabel.get(player) ?? player)} but the declared permitted player (fk-meta m-references) is ${JSON.stringify(declared)}`);
|
|
146
|
+
}
|
|
147
|
+
nullable = !ann.required;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
const a = str(cm, "m-attr");
|
|
151
|
+
if (attrSeen.has(a))
|
|
152
|
+
throw new ParseError(`${name}: column meta for attribute ${a} appears twice`);
|
|
153
|
+
attrSeen.add(a);
|
|
154
|
+
const ann = owns.get(a);
|
|
155
|
+
if (!ann)
|
|
156
|
+
throw new ParseError(`${name}.${column}: ${kind} ${t} does not own ${a}`);
|
|
157
|
+
if (attrs.get(a) !== fam) {
|
|
158
|
+
throw new ParseError(`${name}.${column}: attribute ${a} has value type ${attrs.get(a)} but the column's declared type ${JSON.stringify(yamlType)} is of family ${fam}`);
|
|
159
|
+
}
|
|
160
|
+
nullable = !(ann.key || ann.required);
|
|
161
|
+
if (ann.key)
|
|
162
|
+
pkName = column;
|
|
163
|
+
}
|
|
164
|
+
const col = { name: column, type: yamlType, nullable };
|
|
165
|
+
if (has(cm, "m-description"))
|
|
166
|
+
col.description = str(cm, "m-description");
|
|
167
|
+
cols.push(col);
|
|
168
|
+
}
|
|
169
|
+
const missingOwns = [...owns.keys()].filter((a) => !attrSeen.has(a)).sort();
|
|
170
|
+
if (missingOwns.length > 0)
|
|
171
|
+
throw new ParseError(`${name}: ${kind} ${t} owns ${JSON.stringify(missingOwns)} with no column meta`);
|
|
172
|
+
if (kind === "relation") {
|
|
173
|
+
const missingRoles = [...relates.keys()].filter((r) => !roleSeen.has(r)).sort();
|
|
174
|
+
if (missingRoles.length > 0)
|
|
175
|
+
throw new ParseError(`${name}: relation ${t} relates ${JSON.stringify(missingRoles)} with no column meta`);
|
|
176
|
+
}
|
|
177
|
+
const spec = { primary_key: pkName, columns: [] };
|
|
178
|
+
if (has(c, "m-description"))
|
|
179
|
+
spec.description = str(c, "m-description");
|
|
180
|
+
spec.columns = cols;
|
|
181
|
+
if (fkRows.length > 0) {
|
|
182
|
+
const colNull = new Map(cols.map((col) => [col.name, col.nullable]));
|
|
183
|
+
spec.foreign_keys = [];
|
|
184
|
+
for (const f of fkRows) {
|
|
185
|
+
const column = str(f, "m-column");
|
|
186
|
+
if (!colNull.has(column))
|
|
187
|
+
throw new ParseError(`${name}: fk on unknown column ${column}`);
|
|
188
|
+
const entry = { column, references: str(f, "m-references"), nullable: colNull.get(column) };
|
|
189
|
+
if (f["m-external"] === true)
|
|
190
|
+
entry.external = true;
|
|
191
|
+
const efp = f["m-external-for-profile"];
|
|
192
|
+
if (Array.isArray(efp) && efp.length > 0)
|
|
193
|
+
entry.external_for_profiles = efp.map(String);
|
|
194
|
+
spec.foreign_keys.push(entry);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const ces = colEnumMeta.filter((x) => x["m-catalogue"] === name);
|
|
198
|
+
if (ces.length > 0) {
|
|
199
|
+
spec.column_enums = {};
|
|
200
|
+
for (const x of ces) {
|
|
201
|
+
const en = str(x, "m-enum");
|
|
202
|
+
if (!(en in out.enums))
|
|
203
|
+
throw new ParseError(`${name}.${str(x, "m-column")} binds unknown enum ${en}`);
|
|
204
|
+
spec.column_enums[str(x, "m-column")] = `enums.${en}`;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
out.catalogues[name] = spec;
|
|
208
|
+
}
|
|
209
|
+
return out;
|
|
210
|
+
}
|
|
211
|
+
/** Projection 2: the rubric-coverage-map from coverage-meta + relationship-meta rows. */
|
|
212
|
+
export function projectMap(meta) {
|
|
213
|
+
const cov = {};
|
|
214
|
+
const rels = meta.get("relationship-meta") ?? [];
|
|
215
|
+
const flow = (r) => has(r, "m-flow-by-column")
|
|
216
|
+
? { impact_flow_by: { column: str(r, "m-flow-by-column"), values: JSON.parse(str(r, "m-flow-by-json")) } }
|
|
217
|
+
: { impact_flow: str(r, "m-impact-flow") };
|
|
218
|
+
for (const c of byPosition(meta.get("coverage-meta") ?? [])) {
|
|
219
|
+
const n = str(c, "m-name");
|
|
220
|
+
if (n in cov)
|
|
221
|
+
throw new ParseError(`coverage-meta ${n} declared twice`);
|
|
222
|
+
const e = { kind: str(c, "m-kind"), scoring: "" };
|
|
223
|
+
const mine = rels.filter((r) => r["m-name"] === n);
|
|
224
|
+
const top = mine.filter((r) => !has(r, "m-column"));
|
|
225
|
+
if (top.length > 0) {
|
|
226
|
+
if (top.length > 1)
|
|
227
|
+
throw new ParseError(`${n}: two top-level relationship declarations`);
|
|
228
|
+
const r = top[0];
|
|
229
|
+
e.class = str(r, "m-class");
|
|
230
|
+
Object.assign(e, flow(r));
|
|
231
|
+
}
|
|
232
|
+
e.scoring = str(c, "m-scoring");
|
|
233
|
+
if (has(c, "m-instrument"))
|
|
234
|
+
e.instrument = str(c, "m-instrument");
|
|
235
|
+
if (has(c, "m-reason"))
|
|
236
|
+
e.reason = str(c, "m-reason");
|
|
237
|
+
const cols = byPosition(mine.filter((r) => has(r, "m-column")));
|
|
238
|
+
if (cols.length > 0) {
|
|
239
|
+
e.relationships = {};
|
|
240
|
+
for (const r of cols) {
|
|
241
|
+
const column = str(r, "m-column");
|
|
242
|
+
if (column in e.relationships)
|
|
243
|
+
throw new ParseError(`${n}.${column}: relationship declared twice`);
|
|
244
|
+
e.relationships[column] = { class: str(r, "m-class"), ...flow(r) };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
cov[n] = e;
|
|
248
|
+
}
|
|
249
|
+
const orphan = [...new Set(rels.map((r) => str(r, "m-name")).filter((n) => !(n in cov)))].sort();
|
|
250
|
+
if (orphan.length > 0)
|
|
251
|
+
throw new ParseError(`relationship-meta rows for names with no coverage entry: ${JSON.stringify(orphan.slice(0, 5))}`);
|
|
252
|
+
return { coverage: cov };
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=schema-projection.js.map
|