@sdd-method/author-cli 0.0.0-stage → 0.1.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/LICENSE +201 -0
- package/NOTICE +17 -0
- package/README.md +6 -2
- package/dist/author-bin.js +8 -0
- package/dist/author-program.js +99 -0
- package/dist/lib/adr-origin.js +59 -0
- package/dist/lib/agent-harness.js +37 -0
- package/dist/lib/api-surface/__fixtures__/serverless-repo/serverless/serverless.yml +18 -0
- package/dist/lib/api-surface/analyzers/library.js +95 -0
- package/dist/lib/api-surface/analyzers/serverless.js +336 -0
- package/dist/lib/api-surface/analyzers/spring.js +237 -0
- package/dist/lib/api-surface/analyzers/terraform.js +109 -0
- package/dist/lib/api-surface/detect.js +25 -0
- package/dist/lib/api-surface/emit.js +109 -0
- package/dist/lib/api-surface/file-set.js +43 -0
- package/dist/lib/api-surface/index.js +134 -0
- package/dist/lib/api-surface/types.js +17 -0
- package/dist/lib/briefing/index.js +13 -0
- package/dist/lib/briefing/render.js +132 -0
- package/dist/lib/briefing/variables.js +92 -0
- package/dist/lib/catalogue/application-archetype/vocabulary.js +177 -0
- package/dist/lib/catalogue/archetype/maps/android.yaml +66 -0
- package/dist/lib/catalogue/archetype/maps/swiftui.yaml +58 -0
- package/dist/lib/catalogue/archetype/maps/uikit.yaml +48 -0
- package/dist/lib/catalogue/archetype/maps/web-generic.yaml +86 -0
- package/dist/lib/catalogue/archetype/openui-archetype-vocabulary.yaml +46 -0
- package/dist/lib/catalogue/archetype/resolver.js +103 -0
- package/dist/lib/catalogue/archetype/wireframe.js +418 -0
- package/dist/lib/catalogue/baseline-check.js +151 -0
- package/dist/lib/catalogue/build.js +1502 -0
- package/dist/lib/catalogue/builders/adr-supersedes.js +103 -0
- package/dist/lib/catalogue/builders/adrs.js +222 -0
- package/dist/lib/catalogue/builders/application-conformance-coverage.js +47 -0
- package/dist/lib/catalogue/builders/application-data-plane-bindings.js +115 -0
- package/dist/lib/catalogue/builders/c4-components.js +229 -0
- package/dist/lib/catalogue/builders/c4-containers.js +240 -0
- package/dist/lib/catalogue/builders/c4-entity-rollups.js +87 -0
- package/dist/lib/catalogue/builders/c4-external-systems.js +194 -0
- package/dist/lib/catalogue/builders/c4-l1-candidates.js +107 -0
- package/dist/lib/catalogue/builders/c4-systems.js +98 -0
- package/dist/lib/catalogue/builders/capabilities.js +488 -0
- package/dist/lib/catalogue/builders/capability-domain-model.js +74 -0
- package/dist/lib/catalogue/builders/capability-edges.js +256 -0
- package/dist/lib/catalogue/builders/capability-spec-walker.js +99 -0
- package/dist/lib/catalogue/builders/capability-spec.js +348 -0
- package/dist/lib/catalogue/builders/container-dependency.js +183 -0
- package/dist/lib/catalogue/builders/contracts.js +402 -0
- package/dist/lib/catalogue/builders/data-items.js +344 -0
- package/dist/lib/catalogue/builders/data-store-mapping.js +113 -0
- package/dist/lib/catalogue/builders/domain-models.js +175 -0
- package/dist/lib/catalogue/builders/domains.js +119 -0
- package/dist/lib/catalogue/builders/edge-label-merge.js +19 -0
- package/dist/lib/catalogue/builders/entities-manifest.js +160 -0
- package/dist/lib/catalogue/builders/entities-mermaid.js +189 -0
- package/dist/lib/catalogue/builders/entity-persistence.js +102 -0
- package/dist/lib/catalogue/builders/entity-slug.js +47 -0
- package/dist/lib/catalogue/builders/event-catalog-walker.js +186 -0
- package/dist/lib/catalogue/builders/event-edges.js +327 -0
- package/dist/lib/catalogue/builders/events.js +168 -0
- package/dist/lib/catalogue/builders/evidence-class.js +43 -0
- package/dist/lib/catalogue/builders/evidence-target-repo.js +67 -0
- package/dist/lib/catalogue/builders/external-system-exemption.js +120 -0
- package/dist/lib/catalogue/builders/feature-contract.js +77 -0
- package/dist/lib/catalogue/builders/feature-domain-model.js +50 -0
- package/dist/lib/catalogue/builders/feature-grounding-walker.js +254 -0
- package/dist/lib/catalogue/builders/feature-grounding.js +391 -0
- package/dist/lib/catalogue/builders/feature-groups.js +63 -0
- package/dist/lib/catalogue/builders/feature-subscription-channels.js +228 -0
- package/dist/lib/catalogue/builders/feature-tree-walker.js +295 -0
- package/dist/lib/catalogue/builders/features.js +300 -0
- package/dist/lib/catalogue/builders/glossary-xref.js +378 -0
- package/dist/lib/catalogue/builders/glossary.js +204 -0
- package/dist/lib/catalogue/builders/implementations.js +214 -0
- package/dist/lib/catalogue/builders/index.js +64 -0
- package/dist/lib/catalogue/builders/integration-components.js +344 -0
- package/dist/lib/catalogue/builders/integrations.js +181 -0
- package/dist/lib/catalogue/builders/persona-c4system.js +149 -0
- package/dist/lib/catalogue/builders/persona-container.js +133 -0
- package/dist/lib/catalogue/builders/persona-journey-edges.js +133 -0
- package/dist/lib/catalogue/builders/personas.js +152 -0
- package/dist/lib/catalogue/builders/product-edges.js +106 -0
- package/dist/lib/catalogue/builders/product-platform-dependencies.js +405 -0
- package/dist/lib/catalogue/builders/products.js +162 -0
- package/dist/lib/catalogue/builders/quality-scores.js +635 -0
- package/dist/lib/catalogue/builders/repo-capability-exemption.js +61 -0
- package/dist/lib/catalogue/builders/repo-dependency.js +127 -0
- package/dist/lib/catalogue/builders/repo-edges.js +340 -0
- package/dist/lib/catalogue/builders/repositories.js +458 -0
- package/dist/lib/catalogue/builders/scaffolds.js +197 -0
- package/dist/lib/catalogue/builders/screen-tree.js +222 -0
- package/dist/lib/catalogue/builders/screens.js +396 -0
- package/dist/lib/catalogue/builders/service-capability-exemption.js +63 -0
- package/dist/lib/catalogue/builders/service-contract-consumes.js +186 -0
- package/dist/lib/catalogue/builders/service-contract.js +74 -0
- package/dist/lib/catalogue/builders/service-entity-tables.js +187 -0
- package/dist/lib/catalogue/builders/services.js +232 -0
- package/dist/lib/catalogue/builders/system-external-dependency.js +115 -0
- package/dist/lib/catalogue/builders/user-journeys.js +253 -0
- package/dist/lib/catalogue/canonical-schema.yaml +4117 -0
- package/dist/lib/catalogue/cli-version.js +10 -0
- package/dist/lib/catalogue/contract-id.js +49 -0
- package/dist/lib/catalogue/coverage-predicates.js +603 -0
- package/dist/lib/catalogue/coverage-render.js +552 -0
- package/dist/lib/catalogue/coverage-run.js +115 -0
- package/dist/lib/catalogue/csv-reader.js +97 -0
- package/dist/lib/catalogue/csv-writer.js +96 -0
- package/dist/lib/catalogue/data-plane-coherence.js +77 -0
- package/dist/lib/catalogue/ddl-generator.js +233 -0
- package/dist/lib/catalogue/dedupe-keyed-tables.js +0 -0
- package/dist/lib/catalogue/engine-validator.js +239 -0
- package/dist/lib/catalogue/ext-id.js +76 -0
- package/dist/lib/catalogue/feature-grounding-aliases-loader.js +65 -0
- package/dist/lib/catalogue/feature-repo-zero-resolution-summary.js +31 -0
- package/dist/lib/catalogue/finding-identity.js +183 -0
- package/dist/lib/catalogue/findings.js +46 -0
- package/dist/lib/catalogue/fk-validator.js +136 -0
- package/dist/lib/catalogue/freshness.js +70 -0
- package/dist/lib/catalogue/gate-checks.js +338 -0
- package/dist/lib/catalogue/graph-projection.js +346 -0
- package/dist/lib/catalogue/impact-run.js +228 -0
- package/dist/lib/catalogue/manifest-loader.js +435 -0
- package/dist/lib/catalogue/platform-scope.js +7 -0
- package/dist/lib/catalogue/population-checks.js +87 -0
- package/dist/lib/catalogue/relationship-semantics.js +66 -0
- package/dist/lib/catalogue/reverse-product-quality.js +99 -0
- package/dist/lib/catalogue/rubric-coverage-map.yaml +871 -0
- package/dist/lib/catalogue/runway-vocabulary-loader.js +56 -0
- package/dist/lib/catalogue/scaffold-delta.js +261 -0
- package/dist/lib/catalogue/schema-compat.js +52 -0
- package/dist/lib/catalogue/schema-loader.js +87 -0
- package/dist/lib/catalogue/scores-coverage.js +573 -0
- package/dist/lib/catalogue/screen-validation-loader.js +89 -0
- package/dist/lib/catalogue/service-architecture-parser.js +193 -0
- package/dist/lib/catalogue/service-contract-aliases-loader.js +109 -0
- package/dist/lib/catalogue/staleness-verdicts.js +345 -0
- package/dist/lib/catalogue/traceability-loader.js +350 -0
- package/dist/lib/catalogue/validate.js +200 -0
- package/dist/lib/catalogue/version.js +18 -0
- package/dist/lib/conformance/binding.js +70 -0
- package/dist/lib/conformance/finding.js +69 -0
- package/dist/lib/conformance/index.js +578 -0
- package/dist/lib/context-pull/index.js +191 -0
- package/dist/lib/context-pull/resolve.js +70 -0
- package/dist/lib/context-pull/retired-mcp-setup.js +7 -0
- package/dist/lib/contracts-availability/index.js +267 -0
- package/dist/lib/generate-briefing/index.js +77 -0
- package/dist/lib/list/discover.js +100 -0
- package/dist/lib/list/format.js +84 -0
- package/dist/lib/list/index.js +16 -0
- package/dist/lib/lock-file.js +437 -0
- package/dist/lib/pack/log.js +18 -0
- package/dist/lib/pack/schema.js +121 -0
- package/dist/lib/pack/seed.js +182 -0
- package/dist/lib/pack/validate.js +49 -0
- package/dist/lib/package-version.js +92 -0
- package/dist/lib/plan/index.js +69 -0
- package/dist/lib/plan/read-sdd.js +102 -0
- package/dist/lib/plan/render-template.js +84 -0
- package/dist/lib/repo-kind.js +129 -0
- package/dist/lib/scaffold/domain.js +234 -0
- package/dist/lib/scaffold/registry.js +136 -0
- package/dist/lib/scripts.js +7 -0
- package/dist/lib/sdd-index/repo-config.js +245 -0
- package/dist/lib/sdd-reader.js +91 -0
- package/dist/lib/snapshot/advance.js +454 -0
- package/dist/lib/snapshot/diff.js +190 -0
- package/dist/lib/snapshot/fold.js +208 -0
- package/dist/lib/snapshot/land.js +178 -0
- package/dist/lib/snapshot/manifest.js +206 -0
- package/dist/lib/snapshot/targets.js +169 -0
- package/dist/lib/snapshot/verify.js +91 -0
- package/dist/lib/sync/briefing-merge.js +106 -0
- package/dist/lib/sync/bundle-cache.js +109 -0
- package/dist/lib/sync/yaml-block-merge.js +120 -0
- package/dist/lib/validate/aggregate.js +51 -0
- package/dist/lib/validate/bundle-coherence.js +74 -0
- package/dist/lib/validate/dispatch.js +243 -0
- package/dist/lib/validate/index.js +110 -0
- package/dist/lib/validate/plan.js +54 -0
- package/dist/lib/validate/runner.js +85 -0
- package/dist/lib/validate/yaml-parse.js +112 -0
- package/dist/maintain-version.json +4 -0
- package/dist/verbs/api-surface.js +71 -0
- package/dist/verbs/catalogue-check.js +28 -0
- package/dist/verbs/catalogue-verify.js +114 -0
- package/dist/verbs/conformance.js +34 -0
- package/dist/verbs/context.js +39 -0
- package/dist/verbs/contracts.js +34 -0
- package/dist/verbs/generate-briefing.js +29 -0
- package/dist/verbs/list.js +45 -0
- package/dist/verbs/pack-validate.js +17 -0
- package/dist/verbs/path-option.js +25 -0
- package/dist/verbs/plan.js +42 -0
- package/dist/verbs/render.js +87 -0
- package/dist/verbs/scaffold.js +83 -0
- package/dist/verbs/snapshot.js +134 -0
- package/dist/verbs/validate.js +48 -0
- package/dist/verify-program.js +122 -0
- package/dist/verify-version.json +4 -0
- package/package.json +32 -4
- package/scripts/validate-app-dependencies.sh +664 -0
- package/scripts/validate-app-docs.sh +199 -0
- package/scripts/validate-integration-docs.sh +505 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loader for the optional `feature-runway-vocabulary.yaml` config that
|
|
3
|
+
* adopters can ship at the SDD root (schema 2.2.0+).
|
|
4
|
+
*
|
|
5
|
+
* The file declares a controlled vocabulary for the `runway` column on
|
|
6
|
+
* `features.csv`. When present, the canonical builder validates
|
|
7
|
+
* captured runway values against the declared `values[*].id` set;
|
|
8
|
+
* unknown values emit a `convention` finding. When absent, no
|
|
9
|
+
* validation runs and any runway value is accepted verbatim.
|
|
10
|
+
*
|
|
11
|
+
* The method does NOT bless any vocabulary — there is no method-level
|
|
12
|
+
* enum, no canonical mapping, no normalisation. See
|
|
13
|
+
* catalogue-metamodel.md §"Runway vocabulary (informative recipe)" for
|
|
14
|
+
* the rationale.
|
|
15
|
+
*/
|
|
16
|
+
import { readFile } from "node:fs/promises";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { parse as parseYaml } from "yaml";
|
|
19
|
+
const VOCABULARY_FILENAME = "feature-runway-vocabulary.yaml";
|
|
20
|
+
/**
|
|
21
|
+
* Load the optional vocabulary from `<sddPath>/feature-runway-vocabulary.yaml`.
|
|
22
|
+
* Returns `null` when the file is absent — callers treat that as "no
|
|
23
|
+
* validation runs". Throws on malformed YAML or a missing/non-array
|
|
24
|
+
* `values` key (the file exists, so the adopter intends validation;
|
|
25
|
+
* fail loud rather than silently disable it).
|
|
26
|
+
*/
|
|
27
|
+
export async function loadRunwayVocabulary(sddPath) {
|
|
28
|
+
const full = join(sddPath, VOCABULARY_FILENAME);
|
|
29
|
+
let text;
|
|
30
|
+
try {
|
|
31
|
+
text = await readFile(full, "utf-8");
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
const code = err.code;
|
|
35
|
+
if (code === "ENOENT")
|
|
36
|
+
return null;
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
const raw = parseYaml(text);
|
|
40
|
+
if (raw === null || typeof raw !== "object") {
|
|
41
|
+
throw new Error(`${VOCABULARY_FILENAME}: expected a mapping at the document root`);
|
|
42
|
+
}
|
|
43
|
+
if (!Array.isArray(raw.values)) {
|
|
44
|
+
throw new Error(`${VOCABULARY_FILENAME}: expected a 'values:' array at the document root`);
|
|
45
|
+
}
|
|
46
|
+
const values = new Set();
|
|
47
|
+
for (const entry of raw.values) {
|
|
48
|
+
if (entry === null || typeof entry !== "object")
|
|
49
|
+
continue;
|
|
50
|
+
const id = entry.id;
|
|
51
|
+
if (typeof id === "string" && id !== "")
|
|
52
|
+
values.add(id);
|
|
53
|
+
}
|
|
54
|
+
return { values, relPath: VOCABULARY_FILENAME };
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=runway-vocabulary-loader.js.map
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// Scaffold delta — M-ADR 0207 §4.8: the declared-vs-derived delta computed on
|
|
2
|
+
// the engine, plus the scaffold-coverage predicate (every non-closed scaffold
|
|
3
|
+
// carries a current anchor and a computable delta; a closed scaffold sits at
|
|
4
|
+
// delta zero). REPORTING-ONLY at both tiers per §4.9 — no gating tier ships
|
|
5
|
+
// until the pilot's stated-in-advance figures land, and promotion is a
|
|
6
|
+
// deliberate later act (change the class in one place, say so in the
|
|
7
|
+
// change-set — the scores-coverage precedent, word for word).
|
|
8
|
+
//
|
|
9
|
+
// Finding classes:
|
|
10
|
+
// declared-unrealised — a `declared` item under a CLOSED scaffold. §4.3's
|
|
11
|
+
// first presumption: the scaffold is right, the work is incomplete.
|
|
12
|
+
// Under an OPEN scaffold the same state is the computable backlog and
|
|
13
|
+
// lands in the per-scaffold metric, never as a finding (§4.2).
|
|
14
|
+
// realised-undeclared — an edge row whose BOTH endpoints are this
|
|
15
|
+
// scaffold's own realised entity targets, with no declaring item. The
|
|
16
|
+
// unit's scope is the entity set it declares (§4.2/§4.4): work fully
|
|
17
|
+
// inside that set that was never declared is scope drift made loud;
|
|
18
|
+
// edges to the rest of the estate are integration, not drift.
|
|
19
|
+
// resolution-disagreement — the builder's derived `state` disagrees with
|
|
20
|
+
// the engine's own re-derivation. Conforming builds cannot produce it
|
|
21
|
+
// (resolution is what computes state), so any instance is an
|
|
22
|
+
// instrument defect, adjudicated per M-ADR 0205 §4.4 — never smoothed.
|
|
23
|
+
// scaffold-stale — the §4.6 verdict. MEMBER TIER ONLY, stated not silent
|
|
24
|
+
// (the staleness-verdicts precedent): a per-target verdict needs the
|
|
25
|
+
// target's git history, which only contract items carry a path for.
|
|
26
|
+
// Entity/edge items are counted as pathless rather than guessed at;
|
|
27
|
+
// an anchor the repo does not know is UNKNOWN, never presumed stale.
|
|
28
|
+
import { execFile as execFileCb } from "node:child_process";
|
|
29
|
+
import { promisify } from "node:util";
|
|
30
|
+
const execFile = promisify(execFileCb);
|
|
31
|
+
const EMPTY = {
|
|
32
|
+
findings: [],
|
|
33
|
+
metrics: [],
|
|
34
|
+
staleness: { assessedPaths: 0, unknownAnchor: 0, pathlessItems: 0 },
|
|
35
|
+
};
|
|
36
|
+
const sqlStr = (s) => `'${s.replace(/'/g, "''")}'`;
|
|
37
|
+
async function rows(session, sql) {
|
|
38
|
+
const reader = await session.conn.runAndReadAll(sql);
|
|
39
|
+
return reader.getRows();
|
|
40
|
+
}
|
|
41
|
+
function refCatalogue(reference) {
|
|
42
|
+
return reference.split(".")[0] ?? "";
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Re-derive one item's realisation on the engine — the §4.8 self-check. A
|
|
46
|
+
* target whose catalogue is absent from the session cannot resolve.
|
|
47
|
+
*/
|
|
48
|
+
async function engineResolves(session, available, catalogue, item) {
|
|
49
|
+
if (!available.has(item.targetKind))
|
|
50
|
+
return false;
|
|
51
|
+
if (item.itemKind === "edge") {
|
|
52
|
+
const [srcFk, dstFk] = [catalogue.foreignKeys[0], catalogue.foreignKeys[1]];
|
|
53
|
+
if (!srcFk || !dstFk)
|
|
54
|
+
return false;
|
|
55
|
+
const n = await rows(session, `SELECT COUNT(*) FROM "${item.targetKind}" WHERE "${srcFk.column}" = ${sqlStr(item.edgeSourceRef)} AND "${dstFk.column}" = ${sqlStr(item.edgeTargetRef)};`);
|
|
56
|
+
return Number(n[0][0]) > 0;
|
|
57
|
+
}
|
|
58
|
+
const matchCol = item.itemKind === "contract" ? "spec_path" : catalogue.primaryKey;
|
|
59
|
+
const n = await rows(session, `SELECT COUNT(*) FROM "${item.targetKind}" WHERE "${matchCol}" = ${sqlStr(item.targetRef)};`);
|
|
60
|
+
return Number(n[0][0]) > 0;
|
|
61
|
+
}
|
|
62
|
+
async function gitKnowsCommit(sddRoot, ref) {
|
|
63
|
+
try {
|
|
64
|
+
await execFile("git", ["-C", sddRoot, "cat-file", "-e", `${ref}^{commit}`]);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function pathChangedSince(sddRoot, anchor, path) {
|
|
72
|
+
try {
|
|
73
|
+
const { stdout } = await execFile("git", [
|
|
74
|
+
"-C",
|
|
75
|
+
sddRoot,
|
|
76
|
+
"log",
|
|
77
|
+
"-1",
|
|
78
|
+
"--format=%h %ad",
|
|
79
|
+
"--date=short",
|
|
80
|
+
`${anchor}..HEAD`,
|
|
81
|
+
"--",
|
|
82
|
+
path,
|
|
83
|
+
]);
|
|
84
|
+
const line = stdout.trim();
|
|
85
|
+
return line === "" ? null : line;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export async function runScaffoldDelta(session, options) {
|
|
92
|
+
const { tier, available, schema, sddRoot } = options;
|
|
93
|
+
if (!available.has("scaffolds") || !available.has("scaffold_items"))
|
|
94
|
+
return EMPTY;
|
|
95
|
+
const scaffoldRows = await rows(session, `SELECT id, status, corpus_ref FROM "scaffolds" ORDER BY id;`);
|
|
96
|
+
if (scaffoldRows.length === 0)
|
|
97
|
+
return EMPTY;
|
|
98
|
+
const itemRows = (await rows(session, `SELECT scaffold_id, item_kind, target_kind, target_ref, edge_source_ref, edge_target_ref, state
|
|
99
|
+
FROM "scaffold_items" ORDER BY id;`)).map((r) => ({
|
|
100
|
+
scaffoldId: String(r[0]),
|
|
101
|
+
itemKind: String(r[1]),
|
|
102
|
+
targetKind: String(r[2]),
|
|
103
|
+
targetRef: String(r[3]),
|
|
104
|
+
edgeSourceRef: String(r[4] ?? ""),
|
|
105
|
+
edgeTargetRef: String(r[5] ?? ""),
|
|
106
|
+
state: String(r[6]),
|
|
107
|
+
}));
|
|
108
|
+
const catalogueByName = new Map(schema.catalogues.map((c) => [c.name, c]));
|
|
109
|
+
const findings = [];
|
|
110
|
+
const metrics = [];
|
|
111
|
+
const staleness = { assessedPaths: 0, unknownAnchor: 0, pathlessItems: 0 };
|
|
112
|
+
if (tier === "aggregate") {
|
|
113
|
+
staleness.skipped =
|
|
114
|
+
"aggregate tier — sibling repos' git histories are not local, and a verdict there would be a guess";
|
|
115
|
+
}
|
|
116
|
+
for (const s of scaffoldRows) {
|
|
117
|
+
const scaffoldId = String(s[0]);
|
|
118
|
+
const status = String(s[1]);
|
|
119
|
+
const corpusRef = String(s[2]);
|
|
120
|
+
const items = itemRows.filter((i) => i.scaffoldId === scaffoldId);
|
|
121
|
+
const declared = items.filter((i) => i.state === "declared");
|
|
122
|
+
const realised = items.filter((i) => i.state === "realised");
|
|
123
|
+
metrics.push({ scaffoldId, status, declared: declared.length, realised: realised.length });
|
|
124
|
+
// declared-unrealised — only a finding once the scaffold is closed (§4.2/§4.3).
|
|
125
|
+
if (status === "closed") {
|
|
126
|
+
for (const item of declared) {
|
|
127
|
+
findings.push({
|
|
128
|
+
kind: "declared-unrealised",
|
|
129
|
+
scaffoldId,
|
|
130
|
+
detail: `${item.itemKind} ${item.targetKind}/${item.targetRef} is declared but unrealised under a closed scaffold — the scaffold presumes right: the work is incomplete, or the scaffold needs a recorded amendment (M-ADR 0207 §4.3)`,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// resolution self-check (§4.8) — the engine re-derives every item's state.
|
|
135
|
+
for (const item of items) {
|
|
136
|
+
const cat = catalogueByName.get(item.targetKind);
|
|
137
|
+
if (!cat)
|
|
138
|
+
continue; // builder already reported unknown target_kind
|
|
139
|
+
const resolves = await engineResolves(session, available, cat, item);
|
|
140
|
+
const expected = resolves ? "realised" : "declared";
|
|
141
|
+
if (item.state !== expected) {
|
|
142
|
+
findings.push({
|
|
143
|
+
kind: "resolution-disagreement",
|
|
144
|
+
scaffoldId,
|
|
145
|
+
detail: `${item.itemKind} ${item.targetKind}/${item.targetRef} carries state '${item.state}' but the engine derives '${expected}' — an instrument defect in one side, adjudicated, never smoothed (M-ADR 0205 §4.4)`,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// realised-undeclared — edges wholly inside the scaffold's own realised
|
|
150
|
+
// entity set that no item declares (§4.2).
|
|
151
|
+
const realisedEntityRefs = new Map();
|
|
152
|
+
for (const item of realised) {
|
|
153
|
+
if (item.itemKind !== "entity")
|
|
154
|
+
continue;
|
|
155
|
+
const set = realisedEntityRefs.get(item.targetKind) ?? new Set();
|
|
156
|
+
set.add(item.targetRef);
|
|
157
|
+
realisedEntityRefs.set(item.targetKind, set);
|
|
158
|
+
}
|
|
159
|
+
const declaredEdgePairs = new Set(items
|
|
160
|
+
.filter((i) => i.itemKind === "edge")
|
|
161
|
+
.map((i) => `${i.targetKind}|${i.edgeSourceRef}|${i.edgeTargetRef}`));
|
|
162
|
+
if (realisedEntityRefs.size > 0) {
|
|
163
|
+
for (const cat of schema.catalogues) {
|
|
164
|
+
const [srcFk, dstFk] = [cat.foreignKeys[0], cat.foreignKeys[1]];
|
|
165
|
+
if (!srcFk || !dstFk || !available.has(cat.name))
|
|
166
|
+
continue;
|
|
167
|
+
const srcRefs = realisedEntityRefs.get(refCatalogue(srcFk.references));
|
|
168
|
+
const dstRefs = realisedEntityRefs.get(refCatalogue(dstFk.references));
|
|
169
|
+
if (!srcRefs?.size || !dstRefs?.size)
|
|
170
|
+
continue;
|
|
171
|
+
const inList = (vals) => [...vals].sort().map(sqlStr).join(", ");
|
|
172
|
+
const pairs = await rows(session, `SELECT "${srcFk.column}", "${dstFk.column}" FROM "${cat.name}"
|
|
173
|
+
WHERE "${srcFk.column}" IN (${inList(srcRefs)})
|
|
174
|
+
AND "${dstFk.column}" IN (${inList(dstRefs)})
|
|
175
|
+
ORDER BY 1, 2;`);
|
|
176
|
+
for (const [a, b] of pairs) {
|
|
177
|
+
const key = `${cat.name}|${String(a)}|${String(b)}`;
|
|
178
|
+
if (declaredEdgePairs.has(key))
|
|
179
|
+
continue;
|
|
180
|
+
findings.push({
|
|
181
|
+
kind: "realised-undeclared",
|
|
182
|
+
scaffoldId,
|
|
183
|
+
detail: `edge ${cat.name} ${String(a)} -> ${String(b)} joins two of this scaffold's declared entities but is not itself declared — scope drift made loud; amend the scaffold in the same change-set (M-ADR 0207 §4.3)`,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// scaffold-stale (§4.6) — member tier, per-path verdicts only.
|
|
189
|
+
if (tier === "member") {
|
|
190
|
+
if (!(await gitKnowsCommit(sddRoot, corpusRef))) {
|
|
191
|
+
staleness.unknownAnchor += 1;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
for (const item of items) {
|
|
195
|
+
if (item.itemKind !== "contract") {
|
|
196
|
+
staleness.pathlessItems += 1;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
staleness.assessedPaths += 1;
|
|
200
|
+
const moved = await pathChangedSince(sddRoot, corpusRef, item.targetRef);
|
|
201
|
+
if (moved !== null) {
|
|
202
|
+
findings.push({
|
|
203
|
+
kind: "scaffold-stale",
|
|
204
|
+
scaffoldId,
|
|
205
|
+
detail: `anchor ${corpusRef} predates a change to ${item.targetRef} (${moved}) — re-declare against the current corpus before acting on this scaffold's delta (M-ADR 0207 §4.6/§4.3)`,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
findings.sort((a, b) => a.scaffoldId.localeCompare(b.scaffoldId) ||
|
|
213
|
+
a.kind.localeCompare(b.kind) ||
|
|
214
|
+
a.detail.localeCompare(b.detail));
|
|
215
|
+
return { findings, metrics, staleness };
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* The delta's DEFECT classes are gate-acting (`divergence`), PROMOTED by a
|
|
219
|
+
* recorded governance act after the M-ADR 0207 §4.9 pilot completed with no
|
|
220
|
+
* surprise in any gated figure (2026-08-08) — the deliberate later act the
|
|
221
|
+
* scores-coverage precedent reserves, performed. `scaffold-stale` stays
|
|
222
|
+
* `coverage` with its staleness family: a stale anchor is a re-declare
|
|
223
|
+
* prompt, and gating on git-history movement would alarm, not measure.
|
|
224
|
+
*/
|
|
225
|
+
const PROMOTED = new Set([
|
|
226
|
+
"declared-unrealised",
|
|
227
|
+
"realised-undeclared",
|
|
228
|
+
"resolution-disagreement",
|
|
229
|
+
]);
|
|
230
|
+
export function renderScaffoldDelta(res, tier) {
|
|
231
|
+
// Silent only when there is genuinely nothing measured: no scaffolds is the
|
|
232
|
+
// designed zero-adoption state (M-ADR 0207 §4.4), not a suppressed report.
|
|
233
|
+
if (res.metrics.length === 0)
|
|
234
|
+
return [];
|
|
235
|
+
const out = [];
|
|
236
|
+
for (const f of res.findings) {
|
|
237
|
+
const cls = PROMOTED.has(f.kind) ? "divergence" : "coverage";
|
|
238
|
+
out.push(`[finding] ${cls}: scaffold ${f.scaffoldId}: [${f.kind}] ${f.detail}`);
|
|
239
|
+
}
|
|
240
|
+
for (const m of res.metrics) {
|
|
241
|
+
// The line carries the TOTAL, not just the outstanding count: the first
|
|
242
|
+
// pilot's checkpoint log read like a shrinking denominator because
|
|
243
|
+
// "N declared" meant outstanding. "M outstanding of T declared" cannot
|
|
244
|
+
// be misread that way.
|
|
245
|
+
const delta = m.declared;
|
|
246
|
+
const total = m.declared + m.realised;
|
|
247
|
+
out.push(`[metric] scaffold_delta (${tier}): ${m.scaffoldId} (${m.status}) — ` +
|
|
248
|
+
`${m.realised} realised, ${m.declared} outstanding of ${total} declared; delta ${delta}` +
|
|
249
|
+
(m.status === "closed" && delta > 0 ? " (closed scaffold, delta not zero)" : ""));
|
|
250
|
+
}
|
|
251
|
+
const s = res.staleness;
|
|
252
|
+
if (s.skipped) {
|
|
253
|
+
out.push(`[metric] scaffold_staleness: skipped — ${s.skipped}`);
|
|
254
|
+
}
|
|
255
|
+
else if (s.assessedPaths + s.unknownAnchor + s.pathlessItems > 0) {
|
|
256
|
+
out.push(`[metric] scaffold_staleness: ${s.assessedPaths} path verdict(s) attempted, ` +
|
|
257
|
+
`${s.unknownAnchor} unknown anchor(s), ${s.pathlessItems} pathless item(s) (no per-target verdict possible — stated, not guessed)`);
|
|
258
|
+
}
|
|
259
|
+
return out;
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=scaffold-delta.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-schema compatibility ranges declared by this sdd-cli build.
|
|
3
|
+
*
|
|
4
|
+
* Per resolution of adopt-canonical-catalogue-builder.md §7.5, the three
|
|
5
|
+
* sdd-method-distributed schemas version independently. The CLI declares
|
|
6
|
+
* the version range it accepts for each and fails fast at read time on
|
|
7
|
+
* out-of-range manifests.
|
|
8
|
+
*
|
|
9
|
+
* Range semantics: caret-prefixed semver (`^X.Y.Z`) — accept any version
|
|
10
|
+
* in the same major. Bump these in lockstep with sdd-method releases that
|
|
11
|
+
* change a schema's major version. Minor/patch bumps in sdd-method should
|
|
12
|
+
* NOT require updating the entries here.
|
|
13
|
+
*
|
|
14
|
+
* Schemas:
|
|
15
|
+
* - catalogue_schema: docs/method/foundations/schemas/catalogue-schema.yaml
|
|
16
|
+
* - sdd_manifest: docs/method/foundations/schemas/sdd-manifest-schema.yaml
|
|
17
|
+
* - aggregation_manifest: docs/method/foundations/schemas/aggregation-manifest-schema.yaml
|
|
18
|
+
*
|
|
19
|
+
* Bump rules per schema (in their respective YAML headers):
|
|
20
|
+
* - Patch: typo / description / non-structural fix.
|
|
21
|
+
* - Minor: additive change. Backward compatible — older readers ignore
|
|
22
|
+
* new fields. SCHEMA_COMPAT does NOT need updating.
|
|
23
|
+
* - Major: breaking change. SCHEMA_COMPAT MUST be updated in this
|
|
24
|
+
* release; migration tooling (`sdd-cli migrate <schema> --to <v>`,
|
|
25
|
+
* planned) covers downgrade paths.
|
|
26
|
+
*
|
|
27
|
+
* See ADR 0132 (catalogue-integrity gate's schema-drift digest hook) +
|
|
28
|
+
* ADR 0134 §4.6 (canonical builder + aggregator).
|
|
29
|
+
*/
|
|
30
|
+
export const SCHEMA_COMPAT = {
|
|
31
|
+
catalogue_schema: "^3.0.0",
|
|
32
|
+
sdd_manifest: "^1.0.0",
|
|
33
|
+
aggregation_manifest: "^1.0.0",
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Format a schema-version mismatch error consistently across all three
|
|
37
|
+
* schemas. Used by the per-SDD builder, the aggregator, and the
|
|
38
|
+
* `sdd-cli catalogue validate` subcommand.
|
|
39
|
+
*/
|
|
40
|
+
export function formatSchemaVersionMismatch(args) {
|
|
41
|
+
const supports = SCHEMA_COMPAT[args.schema];
|
|
42
|
+
return [
|
|
43
|
+
`schema version mismatch for ${args.schema}`,
|
|
44
|
+
` read: ${args.sourcePath} @ schema_version ${args.readVersion}`,
|
|
45
|
+
` cli: sdd-cli v${args.cliVersion}`,
|
|
46
|
+
` supports: ${supports}`,
|
|
47
|
+
"",
|
|
48
|
+
"Upgrade sdd-cli or downgrade the manifest. Migration tooling:",
|
|
49
|
+
`sdd-cli migrate ${args.schema} --to <version> (planned, not yet shipped).`,
|
|
50
|
+
].join("\n");
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=schema-compat.js.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loader for the canonical catalogue-schema.yaml vendored from sdd-method.
|
|
3
|
+
*
|
|
4
|
+
* Per ADR 0134 §4.6 the canonical builder reads the schema rather than
|
|
5
|
+
* hardcoding column shapes or FK rules. This loader is the only place in
|
|
6
|
+
* the CLI that knows the schema file's location.
|
|
7
|
+
*/
|
|
8
|
+
import { readFile } from "node:fs/promises";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
import { parse as parseYaml } from "yaml";
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = dirname(__filename);
|
|
14
|
+
/**
|
|
15
|
+
* Default location of the vendored canonical schema, relative to this
|
|
16
|
+
* compiled module. The build copies the YAML alongside the JS via
|
|
17
|
+
* scripts/copy-yaml-assets.mjs, so at runtime it sits next to the
|
|
18
|
+
* compiled schema-loader.js.
|
|
19
|
+
*/
|
|
20
|
+
export const VENDORED_SCHEMA_PATH = join(__dirname, "canonical-schema.yaml");
|
|
21
|
+
export async function loadCanonicalSchema(schemaPath = VENDORED_SCHEMA_PATH) {
|
|
22
|
+
const text = await readFile(schemaPath, "utf-8");
|
|
23
|
+
// `merge: true` resolves YAML merge keys (`<<: *anchor`) — used by
|
|
24
|
+
// `applications: <<: *products_def` in the canonical schema to express
|
|
25
|
+
// the filtered-view catalogue without duplicating the products def.
|
|
26
|
+
const raw = parseYaml(text, { merge: true });
|
|
27
|
+
return normalise(raw);
|
|
28
|
+
}
|
|
29
|
+
function normalise(raw) {
|
|
30
|
+
const catalogues = Object.entries(raw.catalogues).map(([name, def]) => ({
|
|
31
|
+
name,
|
|
32
|
+
primaryKey: def.primary_key,
|
|
33
|
+
...(def.description !== undefined ? { description: def.description } : {}),
|
|
34
|
+
columns: def.columns.map((c) => ({
|
|
35
|
+
name: c.name,
|
|
36
|
+
type: c.type,
|
|
37
|
+
nullable: c.nullable ?? false,
|
|
38
|
+
...(c.description !== undefined ? { description: c.description } : {}),
|
|
39
|
+
})),
|
|
40
|
+
foreignKeys: (def.foreign_keys ?? []).map((fk) => ({
|
|
41
|
+
column: fk.column,
|
|
42
|
+
references: fk.references,
|
|
43
|
+
nullable: fk.nullable ?? false,
|
|
44
|
+
external: fk.external ?? false,
|
|
45
|
+
externalForProfiles: fk.external_for_profiles ?? [],
|
|
46
|
+
})),
|
|
47
|
+
columnEnums: def.column_enums ?? {},
|
|
48
|
+
}));
|
|
49
|
+
const enums = {};
|
|
50
|
+
for (const [name, def] of Object.entries(raw.enums ?? {})) {
|
|
51
|
+
enums[name] = {
|
|
52
|
+
name,
|
|
53
|
+
...(def.description !== undefined ? { description: def.description } : {}),
|
|
54
|
+
values: def.values.map((v) => ({
|
|
55
|
+
value: v.value,
|
|
56
|
+
...(v.runtime !== undefined ? { runtime: v.runtime } : {}),
|
|
57
|
+
...(v.emits_service_row !== undefined
|
|
58
|
+
? { emitsServiceRow: v.emits_service_row }
|
|
59
|
+
: {}),
|
|
60
|
+
...(v.description !== undefined ? { description: v.description } : {}),
|
|
61
|
+
})),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
schemaVersion: raw.schema_version,
|
|
66
|
+
schemaOwner: raw.schema_owner,
|
|
67
|
+
schemaStatus: raw.schema_status,
|
|
68
|
+
catalogues,
|
|
69
|
+
enums,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** Look up a catalogue by name; throws if absent. */
|
|
73
|
+
export function findCatalogue(schema, name) {
|
|
74
|
+
const found = schema.catalogues.find((c) => c.name === name);
|
|
75
|
+
if (!found) {
|
|
76
|
+
throw new Error(`catalogue "${name}" not declared in canonical schema`);
|
|
77
|
+
}
|
|
78
|
+
return found;
|
|
79
|
+
}
|
|
80
|
+
/** Returns the set of `service_type` values that are runtime services (emit_service_row=true). */
|
|
81
|
+
export function runtimeServiceTypes(schema) {
|
|
82
|
+
const enumDef = schema.enums["service_type"];
|
|
83
|
+
if (!enumDef)
|
|
84
|
+
return new Set();
|
|
85
|
+
return new Set(enumDef.values.filter((v) => v.emitsServiceRow === true).map((v) => v.value));
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=schema-loader.js.map
|