@sdd-method/author-cli 0.17.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -65,9 +65,9 @@ async function defaultBehaviourFindings(sddPath, archiveDir) {
|
|
|
65
65
|
const r = await checkBehaviourStore(sddPath);
|
|
66
66
|
return r.stdout.split("\n").filter((l) => l.startsWith("[FAIL]") && l.includes(archiveDir));
|
|
67
67
|
}
|
|
68
|
-
function viewProblems(schema, view) {
|
|
68
|
+
function viewProblems(schema, view, profile) {
|
|
69
69
|
const out = [];
|
|
70
|
-
validateView(schema, view, () => null, out, new Set());
|
|
70
|
+
validateView(schema, view, () => null, out, new Set(), () => undefined, profile);
|
|
71
71
|
return new Set(out);
|
|
72
72
|
}
|
|
73
73
|
/** Does the effect record hold at `commit`? Each added or modified element at its target, each removed one absent. */
|
|
@@ -98,6 +98,11 @@ async function verifyAt(root, relSdd, commit, effects) {
|
|
|
98
98
|
await rm(scratch, { recursive: true, force: true });
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
+
/** The SDD's profile from its manifest, "" when absent or unreadable. */
|
|
102
|
+
async function sddProfile(sddPath) {
|
|
103
|
+
const text = await readFile(join(sddPath, ".sdd", "sdd-manifest.yaml"), "utf8").catch(() => "");
|
|
104
|
+
return /^profile:\s*["']?([A-Za-z0-9-]+)["']?\s*$/m.exec(text)?.[1] ?? "";
|
|
105
|
+
}
|
|
101
106
|
export async function archiveChange(o) {
|
|
102
107
|
const say = (s) => o.write(`${s}\n`);
|
|
103
108
|
const refuse = (s) => {
|
|
@@ -241,6 +246,7 @@ async function publish(o, root, relSdd, schema, s, changeRel, mode) {
|
|
|
241
246
|
say(`[FAIL] change archive ${o.change}: ${x}`);
|
|
242
247
|
return 1;
|
|
243
248
|
};
|
|
249
|
+
const profile = await sddProfile(o.sddPath);
|
|
244
250
|
const storeRel = join(relSdd, "openspec");
|
|
245
251
|
const viewsRel = join(relSdd, "docs", "structure");
|
|
246
252
|
const dirty = await git(root, "status", "--porcelain", "--", storeRel, viewsRel);
|
|
@@ -250,9 +256,9 @@ async function publish(o, root, relSdd, schema, s, changeRel, mode) {
|
|
|
250
256
|
// §4.5 again, against the model as it now is.
|
|
251
257
|
const problems = [];
|
|
252
258
|
const current = viewOf((await loadModel(o.sddPath, problems)) ?? [], problems);
|
|
253
|
-
const before = viewProblems(schema, current);
|
|
259
|
+
const before = viewProblems(schema, current, profile);
|
|
254
260
|
const deltaProblems = [];
|
|
255
|
-
validateDeltas(schema, current, s.deltas, new Map(), deltaProblems);
|
|
261
|
+
validateDeltas(schema, current, s.deltas, new Map(), deltaProblems, profile);
|
|
256
262
|
if (deltaProblems.length > 0)
|
|
257
263
|
return refuse(`its structural deltas no longer apply to the model as it stands:\n ${deltaProblems.join("\n ")}`);
|
|
258
264
|
const effects = effectsOf(s, current);
|
|
@@ -280,7 +286,7 @@ async function publish(o, root, relSdd, schema, s, changeRel, mode) {
|
|
|
280
286
|
// 2. Validate the candidate as a whole.
|
|
281
287
|
const cp = [];
|
|
282
288
|
const candidate = viewOf((await loadModel(scratchSdd, cp)) ?? [], cp);
|
|
283
|
-
const after = viewProblems(schema, candidate);
|
|
289
|
+
const after = viewProblems(schema, candidate, profile);
|
|
284
290
|
const worse = [...cp, ...[...after].filter((p) => !before.has(p))];
|
|
285
291
|
if (worse.length > 0)
|
|
286
292
|
return refuse(`the model as it would stand fails where it did not:\n ${worse.join("\n ")}`);
|
|
@@ -90,13 +90,15 @@ function find(view, catalogue, column, value) {
|
|
|
90
90
|
return undefined;
|
|
91
91
|
}
|
|
92
92
|
/** The element's references: each non-external foreign key with a value. */
|
|
93
|
-
export function referencesOf(schema, e) {
|
|
93
|
+
export function referencesOf(schema, e, profile = "") {
|
|
94
94
|
const cat = catalogueOf(schema, e.catalogue);
|
|
95
95
|
if (!cat)
|
|
96
96
|
return [];
|
|
97
97
|
const out = [];
|
|
98
98
|
for (const fk of cat.foreignKeys) {
|
|
99
|
-
|
|
99
|
+
// An external key names another SDD's element; so does one external for this SDD's profile (an application or
|
|
100
|
+
// integration model naming a data item the platform model owns), as the FK validator and the graph projection read it.
|
|
101
|
+
if (fk.external || (profile !== "" && (fk.externalForProfiles ?? []).includes(profile)))
|
|
100
102
|
continue;
|
|
101
103
|
const v = e.columns[fk.column];
|
|
102
104
|
if (v === undefined || v === "")
|
|
@@ -121,7 +123,7 @@ function naturalKey(cat, e) {
|
|
|
121
123
|
* catalogue of another class, against its built rows), no cycle through a self-reference, natural keys unique, and
|
|
122
124
|
* domain invariants naming their entity's own attributes and relationships.
|
|
123
125
|
*/
|
|
124
|
-
export function validateView(schema, view, built, problems, unresolved, addedBy = () => undefined) {
|
|
126
|
+
export function validateView(schema, view, built, problems, unresolved, addedBy = () => undefined, profile = "") {
|
|
125
127
|
// A reference to an open change's addition, from outside that change and its dependants (§4.11 proof 3).
|
|
126
128
|
const outside = (catalogue, id) => {
|
|
127
129
|
const by = addedBy(catalogue, id);
|
|
@@ -135,7 +137,7 @@ export function validateView(schema, view, built, problems, unresolved, addedBy
|
|
|
135
137
|
problems.push(...shape);
|
|
136
138
|
if (!cat || cat.sourceOfTruth !== STRUCTURE)
|
|
137
139
|
continue;
|
|
138
|
-
for (const r of referencesOf(schema, e)) {
|
|
140
|
+
for (const r of referencesOf(schema, e, profile)) {
|
|
139
141
|
if (r.structural) {
|
|
140
142
|
if (!find(view, r.catalogue, r.targetColumn, r.value))
|
|
141
143
|
problems.push(`${e.file}: ${elementKey(catalogue, e.id)}: ${r.column} ${JSON.stringify(r.value)} names no ${r.catalogue} element in this view (${elementKey(r.catalogue, r.value)})${r.targetColumn === "id" ? outside(r.catalogue, r.value) : ""}`);
|
|
@@ -218,7 +220,7 @@ export function validateView(schema, view, built, problems, unresolved, addedBy
|
|
|
218
220
|
* A base revision that no longer matches the current element is reported as moved: the change is revalidated.
|
|
219
221
|
* `addedElsewhere` holds the elements other open changes add, for a reference into one of them.
|
|
220
222
|
*/
|
|
221
|
-
export function validateDeltas(schema, current, deltas, addedElsewhere, problems) {
|
|
223
|
+
export function validateDeltas(schema, current, deltas, addedElsewhere, problems, profile = "") {
|
|
222
224
|
const ownAdded = new Set();
|
|
223
225
|
const dependsOn = new Set();
|
|
224
226
|
for (const d of deltas) {
|
|
@@ -281,7 +283,7 @@ export function validateDeltas(schema, current, deltas, addedElsewhere, problems
|
|
|
281
283
|
}
|
|
282
284
|
// Reads: every element an added or modified element references.
|
|
283
285
|
for (const e of [...d.added, ...d.modified]) {
|
|
284
|
-
for (const r of referencesOf(schema, e)) {
|
|
286
|
+
for (const r of referencesOf(schema, e, profile)) {
|
|
285
287
|
if (!r.structural)
|
|
286
288
|
continue;
|
|
287
289
|
const target = r.targetColumn === "id" ? r.value : find(current, r.catalogue, r.targetColumn, r.value)?.id;
|
|
@@ -162,7 +162,7 @@ export async function checkStructuralModel(repo, options = {}) {
|
|
|
162
162
|
// The current view.
|
|
163
163
|
const unresolved = new Set();
|
|
164
164
|
const currentProblems = [];
|
|
165
|
-
validateView(schema, view, builtIn(built, allNew, new Set()), currentProblems, unresolved, addedBy);
|
|
165
|
+
validateView(schema, view, builtIn(built, allNew, new Set()), currentProblems, unresolved, addedBy, profile);
|
|
166
166
|
problems.push(...currentProblems);
|
|
167
167
|
const elementCount = [...view.values()].reduce((n, m) => n + m.size, 0);
|
|
168
168
|
lines.push(files === null ? `model: none (${STRUCTURE_DIR}/ absent)` : `model: ${files.length} partition file(s), ${elementCount} element(s) in ${view.size} catalogue(s)`);
|
|
@@ -211,7 +211,7 @@ export async function checkStructuralModel(repo, options = {}) {
|
|
|
211
211
|
if (s.noOp && s.deltas.length > 0)
|
|
212
212
|
problems.push(`${s.noOp.file}: a no-op claim beside ${s.deltas.length} partition delta(s); state one or the other`);
|
|
213
213
|
const elsewhere = new Map([...addedByChange].filter(([, c]) => c !== s.change));
|
|
214
|
-
validateDeltas(schema, view, s.deltas, elsewhere, problems);
|
|
214
|
+
validateDeltas(schema, view, s.deltas, elsewhere, problems, profile);
|
|
215
215
|
// A behaviour delta for a feature the model does not hold needs that feature added here.
|
|
216
216
|
const adds = new Set(s.deltas.flatMap((d) => d.added.filter((e) => e.catalogue === "features").map((e) => e.id)));
|
|
217
217
|
for (const feature of await subdirs(join(repo, dir, "specs"))) {
|
|
@@ -238,7 +238,7 @@ export async function checkStructuralModel(repo, options = {}) {
|
|
|
238
238
|
if (oc.structure.deltas.length === 0)
|
|
239
239
|
continue;
|
|
240
240
|
const at = [];
|
|
241
|
-
validateView(schema, viewAt(view, name, open), builtIn(built, allNew, additions.contracts(closure(name, open))), at, new Set(), addedBy);
|
|
241
|
+
validateView(schema, viewAt(view, name, open), builtIn(built, allNew, additions.contracts(closure(name, open))), at, new Set(), addedBy, profile);
|
|
242
242
|
for (const p of at) {
|
|
243
243
|
if (known.has(p))
|
|
244
244
|
continue;
|
|
@@ -250,7 +250,7 @@ export async function checkStructuralModel(repo, options = {}) {
|
|
|
250
250
|
const agreed = agreedOrder(open);
|
|
251
251
|
if (agreed.length > 0) {
|
|
252
252
|
const inIntended = [];
|
|
253
|
-
validateView(schema, intended(view, open), builtIn(built, allNew, additions.contracts(agreed)), inIntended, new Set(), addedBy);
|
|
253
|
+
validateView(schema, intended(view, open), builtIn(built, allNew, additions.contracts(agreed)), inIntended, new Set(), addedBy, profile);
|
|
254
254
|
for (const p of inIntended) {
|
|
255
255
|
if (known.has(p) || atAgreed.has(p))
|
|
256
256
|
continue;
|
package/dist/verify-version.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdd-method/author-cli",
|
|
3
|
-
"version": "0.17.
|
|
4
|
-
"sddCliVersion": "5.4.
|
|
3
|
+
"version": "0.17.1",
|
|
4
|
+
"sddCliVersion": "5.4.1",
|
|
5
5
|
"description": "Re-author one SDD's content — scaffold, plan, contracts, conformance, api-surface, snapshots, briefings, catalogue check — plus every read-only verify verb. Ships only the code those verbs reach; it cannot sync the baseline or rebuild the catalogues. Maintenance is in @sdd-method/maintain-cli, administration in @sdd-method/sdd-cli.",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"type": "module",
|