@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.
Files changed (203) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +17 -0
  3. package/README.md +6 -2
  4. package/dist/author-bin.js +8 -0
  5. package/dist/author-program.js +99 -0
  6. package/dist/lib/adr-origin.js +59 -0
  7. package/dist/lib/agent-harness.js +37 -0
  8. package/dist/lib/api-surface/__fixtures__/serverless-repo/serverless/serverless.yml +18 -0
  9. package/dist/lib/api-surface/analyzers/library.js +95 -0
  10. package/dist/lib/api-surface/analyzers/serverless.js +336 -0
  11. package/dist/lib/api-surface/analyzers/spring.js +237 -0
  12. package/dist/lib/api-surface/analyzers/terraform.js +109 -0
  13. package/dist/lib/api-surface/detect.js +25 -0
  14. package/dist/lib/api-surface/emit.js +109 -0
  15. package/dist/lib/api-surface/file-set.js +43 -0
  16. package/dist/lib/api-surface/index.js +134 -0
  17. package/dist/lib/api-surface/types.js +17 -0
  18. package/dist/lib/briefing/index.js +13 -0
  19. package/dist/lib/briefing/render.js +132 -0
  20. package/dist/lib/briefing/variables.js +92 -0
  21. package/dist/lib/catalogue/application-archetype/vocabulary.js +177 -0
  22. package/dist/lib/catalogue/archetype/maps/android.yaml +66 -0
  23. package/dist/lib/catalogue/archetype/maps/swiftui.yaml +58 -0
  24. package/dist/lib/catalogue/archetype/maps/uikit.yaml +48 -0
  25. package/dist/lib/catalogue/archetype/maps/web-generic.yaml +86 -0
  26. package/dist/lib/catalogue/archetype/openui-archetype-vocabulary.yaml +46 -0
  27. package/dist/lib/catalogue/archetype/resolver.js +103 -0
  28. package/dist/lib/catalogue/archetype/wireframe.js +418 -0
  29. package/dist/lib/catalogue/baseline-check.js +151 -0
  30. package/dist/lib/catalogue/build.js +1502 -0
  31. package/dist/lib/catalogue/builders/adr-supersedes.js +103 -0
  32. package/dist/lib/catalogue/builders/adrs.js +222 -0
  33. package/dist/lib/catalogue/builders/application-conformance-coverage.js +47 -0
  34. package/dist/lib/catalogue/builders/application-data-plane-bindings.js +115 -0
  35. package/dist/lib/catalogue/builders/c4-components.js +229 -0
  36. package/dist/lib/catalogue/builders/c4-containers.js +240 -0
  37. package/dist/lib/catalogue/builders/c4-entity-rollups.js +87 -0
  38. package/dist/lib/catalogue/builders/c4-external-systems.js +194 -0
  39. package/dist/lib/catalogue/builders/c4-l1-candidates.js +107 -0
  40. package/dist/lib/catalogue/builders/c4-systems.js +98 -0
  41. package/dist/lib/catalogue/builders/capabilities.js +488 -0
  42. package/dist/lib/catalogue/builders/capability-domain-model.js +74 -0
  43. package/dist/lib/catalogue/builders/capability-edges.js +256 -0
  44. package/dist/lib/catalogue/builders/capability-spec-walker.js +99 -0
  45. package/dist/lib/catalogue/builders/capability-spec.js +348 -0
  46. package/dist/lib/catalogue/builders/container-dependency.js +183 -0
  47. package/dist/lib/catalogue/builders/contracts.js +402 -0
  48. package/dist/lib/catalogue/builders/data-items.js +344 -0
  49. package/dist/lib/catalogue/builders/data-store-mapping.js +113 -0
  50. package/dist/lib/catalogue/builders/domain-models.js +175 -0
  51. package/dist/lib/catalogue/builders/domains.js +119 -0
  52. package/dist/lib/catalogue/builders/edge-label-merge.js +19 -0
  53. package/dist/lib/catalogue/builders/entities-manifest.js +160 -0
  54. package/dist/lib/catalogue/builders/entities-mermaid.js +189 -0
  55. package/dist/lib/catalogue/builders/entity-persistence.js +102 -0
  56. package/dist/lib/catalogue/builders/entity-slug.js +47 -0
  57. package/dist/lib/catalogue/builders/event-catalog-walker.js +186 -0
  58. package/dist/lib/catalogue/builders/event-edges.js +327 -0
  59. package/dist/lib/catalogue/builders/events.js +168 -0
  60. package/dist/lib/catalogue/builders/evidence-class.js +43 -0
  61. package/dist/lib/catalogue/builders/evidence-target-repo.js +67 -0
  62. package/dist/lib/catalogue/builders/external-system-exemption.js +120 -0
  63. package/dist/lib/catalogue/builders/feature-contract.js +77 -0
  64. package/dist/lib/catalogue/builders/feature-domain-model.js +50 -0
  65. package/dist/lib/catalogue/builders/feature-grounding-walker.js +254 -0
  66. package/dist/lib/catalogue/builders/feature-grounding.js +391 -0
  67. package/dist/lib/catalogue/builders/feature-groups.js +63 -0
  68. package/dist/lib/catalogue/builders/feature-subscription-channels.js +228 -0
  69. package/dist/lib/catalogue/builders/feature-tree-walker.js +295 -0
  70. package/dist/lib/catalogue/builders/features.js +300 -0
  71. package/dist/lib/catalogue/builders/glossary-xref.js +378 -0
  72. package/dist/lib/catalogue/builders/glossary.js +204 -0
  73. package/dist/lib/catalogue/builders/implementations.js +214 -0
  74. package/dist/lib/catalogue/builders/index.js +64 -0
  75. package/dist/lib/catalogue/builders/integration-components.js +344 -0
  76. package/dist/lib/catalogue/builders/integrations.js +181 -0
  77. package/dist/lib/catalogue/builders/persona-c4system.js +149 -0
  78. package/dist/lib/catalogue/builders/persona-container.js +133 -0
  79. package/dist/lib/catalogue/builders/persona-journey-edges.js +133 -0
  80. package/dist/lib/catalogue/builders/personas.js +152 -0
  81. package/dist/lib/catalogue/builders/product-edges.js +106 -0
  82. package/dist/lib/catalogue/builders/product-platform-dependencies.js +405 -0
  83. package/dist/lib/catalogue/builders/products.js +162 -0
  84. package/dist/lib/catalogue/builders/quality-scores.js +635 -0
  85. package/dist/lib/catalogue/builders/repo-capability-exemption.js +61 -0
  86. package/dist/lib/catalogue/builders/repo-dependency.js +127 -0
  87. package/dist/lib/catalogue/builders/repo-edges.js +340 -0
  88. package/dist/lib/catalogue/builders/repositories.js +458 -0
  89. package/dist/lib/catalogue/builders/scaffolds.js +197 -0
  90. package/dist/lib/catalogue/builders/screen-tree.js +222 -0
  91. package/dist/lib/catalogue/builders/screens.js +396 -0
  92. package/dist/lib/catalogue/builders/service-capability-exemption.js +63 -0
  93. package/dist/lib/catalogue/builders/service-contract-consumes.js +186 -0
  94. package/dist/lib/catalogue/builders/service-contract.js +74 -0
  95. package/dist/lib/catalogue/builders/service-entity-tables.js +187 -0
  96. package/dist/lib/catalogue/builders/services.js +232 -0
  97. package/dist/lib/catalogue/builders/system-external-dependency.js +115 -0
  98. package/dist/lib/catalogue/builders/user-journeys.js +253 -0
  99. package/dist/lib/catalogue/canonical-schema.yaml +4117 -0
  100. package/dist/lib/catalogue/cli-version.js +10 -0
  101. package/dist/lib/catalogue/contract-id.js +49 -0
  102. package/dist/lib/catalogue/coverage-predicates.js +603 -0
  103. package/dist/lib/catalogue/coverage-render.js +552 -0
  104. package/dist/lib/catalogue/coverage-run.js +115 -0
  105. package/dist/lib/catalogue/csv-reader.js +97 -0
  106. package/dist/lib/catalogue/csv-writer.js +96 -0
  107. package/dist/lib/catalogue/data-plane-coherence.js +77 -0
  108. package/dist/lib/catalogue/ddl-generator.js +233 -0
  109. package/dist/lib/catalogue/dedupe-keyed-tables.js +0 -0
  110. package/dist/lib/catalogue/engine-validator.js +239 -0
  111. package/dist/lib/catalogue/ext-id.js +76 -0
  112. package/dist/lib/catalogue/feature-grounding-aliases-loader.js +65 -0
  113. package/dist/lib/catalogue/feature-repo-zero-resolution-summary.js +31 -0
  114. package/dist/lib/catalogue/finding-identity.js +183 -0
  115. package/dist/lib/catalogue/findings.js +46 -0
  116. package/dist/lib/catalogue/fk-validator.js +136 -0
  117. package/dist/lib/catalogue/freshness.js +70 -0
  118. package/dist/lib/catalogue/gate-checks.js +338 -0
  119. package/dist/lib/catalogue/graph-projection.js +346 -0
  120. package/dist/lib/catalogue/impact-run.js +228 -0
  121. package/dist/lib/catalogue/manifest-loader.js +435 -0
  122. package/dist/lib/catalogue/platform-scope.js +7 -0
  123. package/dist/lib/catalogue/population-checks.js +87 -0
  124. package/dist/lib/catalogue/relationship-semantics.js +66 -0
  125. package/dist/lib/catalogue/reverse-product-quality.js +99 -0
  126. package/dist/lib/catalogue/rubric-coverage-map.yaml +871 -0
  127. package/dist/lib/catalogue/runway-vocabulary-loader.js +56 -0
  128. package/dist/lib/catalogue/scaffold-delta.js +261 -0
  129. package/dist/lib/catalogue/schema-compat.js +52 -0
  130. package/dist/lib/catalogue/schema-loader.js +87 -0
  131. package/dist/lib/catalogue/scores-coverage.js +573 -0
  132. package/dist/lib/catalogue/screen-validation-loader.js +89 -0
  133. package/dist/lib/catalogue/service-architecture-parser.js +193 -0
  134. package/dist/lib/catalogue/service-contract-aliases-loader.js +109 -0
  135. package/dist/lib/catalogue/staleness-verdicts.js +345 -0
  136. package/dist/lib/catalogue/traceability-loader.js +350 -0
  137. package/dist/lib/catalogue/validate.js +200 -0
  138. package/dist/lib/catalogue/version.js +18 -0
  139. package/dist/lib/conformance/binding.js +70 -0
  140. package/dist/lib/conformance/finding.js +69 -0
  141. package/dist/lib/conformance/index.js +578 -0
  142. package/dist/lib/context-pull/index.js +191 -0
  143. package/dist/lib/context-pull/resolve.js +70 -0
  144. package/dist/lib/context-pull/retired-mcp-setup.js +7 -0
  145. package/dist/lib/contracts-availability/index.js +267 -0
  146. package/dist/lib/generate-briefing/index.js +77 -0
  147. package/dist/lib/list/discover.js +100 -0
  148. package/dist/lib/list/format.js +84 -0
  149. package/dist/lib/list/index.js +16 -0
  150. package/dist/lib/lock-file.js +437 -0
  151. package/dist/lib/pack/log.js +18 -0
  152. package/dist/lib/pack/schema.js +121 -0
  153. package/dist/lib/pack/seed.js +182 -0
  154. package/dist/lib/pack/validate.js +49 -0
  155. package/dist/lib/package-version.js +92 -0
  156. package/dist/lib/plan/index.js +69 -0
  157. package/dist/lib/plan/read-sdd.js +102 -0
  158. package/dist/lib/plan/render-template.js +84 -0
  159. package/dist/lib/repo-kind.js +129 -0
  160. package/dist/lib/scaffold/domain.js +234 -0
  161. package/dist/lib/scaffold/registry.js +136 -0
  162. package/dist/lib/scripts.js +7 -0
  163. package/dist/lib/sdd-index/repo-config.js +245 -0
  164. package/dist/lib/sdd-reader.js +91 -0
  165. package/dist/lib/snapshot/advance.js +454 -0
  166. package/dist/lib/snapshot/diff.js +190 -0
  167. package/dist/lib/snapshot/fold.js +208 -0
  168. package/dist/lib/snapshot/land.js +178 -0
  169. package/dist/lib/snapshot/manifest.js +206 -0
  170. package/dist/lib/snapshot/targets.js +169 -0
  171. package/dist/lib/snapshot/verify.js +91 -0
  172. package/dist/lib/sync/briefing-merge.js +106 -0
  173. package/dist/lib/sync/bundle-cache.js +109 -0
  174. package/dist/lib/sync/yaml-block-merge.js +120 -0
  175. package/dist/lib/validate/aggregate.js +51 -0
  176. package/dist/lib/validate/bundle-coherence.js +74 -0
  177. package/dist/lib/validate/dispatch.js +243 -0
  178. package/dist/lib/validate/index.js +110 -0
  179. package/dist/lib/validate/plan.js +54 -0
  180. package/dist/lib/validate/runner.js +85 -0
  181. package/dist/lib/validate/yaml-parse.js +112 -0
  182. package/dist/maintain-version.json +4 -0
  183. package/dist/verbs/api-surface.js +71 -0
  184. package/dist/verbs/catalogue-check.js +28 -0
  185. package/dist/verbs/catalogue-verify.js +114 -0
  186. package/dist/verbs/conformance.js +34 -0
  187. package/dist/verbs/context.js +39 -0
  188. package/dist/verbs/contracts.js +34 -0
  189. package/dist/verbs/generate-briefing.js +29 -0
  190. package/dist/verbs/list.js +45 -0
  191. package/dist/verbs/pack-validate.js +17 -0
  192. package/dist/verbs/path-option.js +25 -0
  193. package/dist/verbs/plan.js +42 -0
  194. package/dist/verbs/render.js +87 -0
  195. package/dist/verbs/scaffold.js +83 -0
  196. package/dist/verbs/snapshot.js +134 -0
  197. package/dist/verbs/validate.js +48 -0
  198. package/dist/verify-program.js +122 -0
  199. package/dist/verify-version.json +4 -0
  200. package/package.json +32 -4
  201. package/scripts/validate-app-dependencies.sh +664 -0
  202. package/scripts/validate-app-docs.sh +199 -0
  203. 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