@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,214 @@
1
+ // implementations / implementation_members / snapshots / snapshot_members builder
2
+ // (M-ADR 0214, schema 3.15.0).
3
+ //
4
+ // Source: `.sdd/implementation-binding.yaml` (M-ADR 0181 §2.1) and, through its
5
+ // `snapshot_area:` and the snapshot store root, each dated `snapshot.yaml` in that
6
+ // area (M-ADR 0181 §2.5). The implementation id is the durable identity all four
7
+ // catalogues key on, so a binding that names no `implementation:` emits NOTHING and
8
+ // says so: there is no identity to key on, and inventing one from `spec:` would
9
+ // make a binding and its snapshots agree by construction rather than by record.
10
+ //
11
+ // A member's repository end is optional: a `planned` member names a repository
12
+ // that may not exist yet (M-ADR 0181 lifecycle). repo_key always carries the named
13
+ // repository; repo_id carries it only when it resolves in this SDD's repositories,
14
+ // and a non-planned member that does not resolve is a finding.
15
+ import { readFile } from "node:fs/promises";
16
+ import { basename, join } from "node:path";
17
+ import { parse as parseYaml } from "yaml";
18
+ import { BINDING_FILENAME, BINDINGS_DIRNAME, bindingFiles } from "../../conformance/binding.js";
19
+ import { baselineIds, exists, readManifest, SnapshotError } from "../../snapshot/manifest.js";
20
+ const str = (v) => (typeof v === "string" ? v.trim() : typeof v === "number" ? String(v) : "");
21
+ const empty = () => ({
22
+ implementations: [],
23
+ implementationMembers: [],
24
+ snapshots: [],
25
+ snapshotMembers: [],
26
+ });
27
+ /**
28
+ * Every binding file the SDD carries (M-ADR 0214 §4.9): the single-implementation file and one
29
+ * per implementation in the bindings directory. Each builds independently; an implementation id
30
+ * declared twice is refused rather than merged, and a directory file must be named for the
31
+ * implementation it declares.
32
+ */
33
+ export async function buildImplementations(inputs) {
34
+ const out = empty();
35
+ const declaredIn = new Map();
36
+ const files = bindingFiles(inputs.sddPath);
37
+ // Legal, and how drift starts: a forgotten single file keeps contributing rows beside the
38
+ // directory, and the duplicate-id refusal only catches it when the ids happen to coincide.
39
+ if (files.length > 1 && files[0] === BINDING_FILENAME) {
40
+ inputs.findings.add("convention", `implementations: the SDD carries both ${BINDING_FILENAME} and ${BINDINGS_DIRNAME}/ — both are built; move the single binding into the directory as <implementation>.yaml (M-ADR 0214 §4.9)`);
41
+ }
42
+ for (const file of files) {
43
+ const one = await buildOne(inputs, file, declaredIn);
44
+ out.implementations.push(...one.implementations);
45
+ out.implementationMembers.push(...one.implementationMembers);
46
+ out.snapshots.push(...one.snapshots);
47
+ out.snapshotMembers.push(...one.snapshotMembers);
48
+ }
49
+ return out;
50
+ }
51
+ async function buildOne(inputs, bindingFile, declaredIn) {
52
+ const { sddPath, knownRepoIds, snapshotStore, findings } = inputs;
53
+ let raw;
54
+ try {
55
+ raw = parseYaml(await readFile(join(sddPath, bindingFile), "utf8"));
56
+ }
57
+ catch (e) {
58
+ findings.add("divergence", `implementations: ${bindingFile} does not parse — ${e.message}`);
59
+ return empty();
60
+ }
61
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
62
+ findings.add("divergence", `implementations: ${bindingFile} is not a mapping — nothing built`);
63
+ return empty();
64
+ }
65
+ const impl = str(raw.implementation);
66
+ const spec = str(raw.spec);
67
+ if (impl === "") {
68
+ findings.add("coverage", `implementations: ${bindingFile} names no implementation — add a top-level \`implementation:\` carrying the id its snapshot manifests carry (M-ADR 0214 §4.1); no implementation, member or snapshot rows are built from it until it does`);
69
+ return empty();
70
+ }
71
+ if (bindingFile.startsWith(`${BINDINGS_DIRNAME}/`)) {
72
+ const named = basename(bindingFile).replace(/\.ya?ml$/, "");
73
+ if (named !== impl) {
74
+ findings.add("divergence", `implementations: ${bindingFile} declares implementation ${impl} — a bindings-directory file is named for its implementation (M-ADR 0214 §4.9); nothing built from it`);
75
+ return empty();
76
+ }
77
+ }
78
+ const prior = declaredIn.get(impl);
79
+ if (prior !== undefined) {
80
+ findings.add("divergence", `implementations: ${impl} is declared by both ${prior} and ${bindingFile} — refused, not merged (M-ADR 0214 §4.9); only ${prior} is built`);
81
+ return empty();
82
+ }
83
+ declaredIn.set(impl, bindingFile);
84
+ if (spec === "") {
85
+ findings.add("divergence", `implementations: ${bindingFile} (implementation ${impl}) names no spec — nothing built`);
86
+ return empty();
87
+ }
88
+ const area = str(raw.snapshot_area);
89
+ const out = empty();
90
+ out.implementations.push({
91
+ id: impl,
92
+ name: str(raw.name) || impl,
93
+ spec,
94
+ snapshot_area: area,
95
+ doc_path: bindingFile,
96
+ source_sdd_id: "",
97
+ platform_sdd_id: "",
98
+ });
99
+ const members = Array.isArray(raw.implementations) ? raw.implementations : [];
100
+ const seen = new Set();
101
+ for (const [i, m] of members.entries()) {
102
+ const repoKey = str(m?.repo);
103
+ if (repoKey === "") {
104
+ findings.add("divergence", `implementation_members: ${impl} member [${i}] names no repo — skipped`);
105
+ continue;
106
+ }
107
+ const id = `${impl}::${repoKey}`;
108
+ if (seen.has(id)) {
109
+ findings.add("divergence", `implementation_members: ${impl} lists ${repoKey} twice — second skipped`);
110
+ continue;
111
+ }
112
+ seen.add(id);
113
+ const lifecycle = str(m.lifecycle);
114
+ const resolved = knownRepoIds.has(repoKey);
115
+ if (!resolved && lifecycle !== "planned") {
116
+ findings.add("coverage", `implementation_members: ${impl} member ${repoKey} (lifecycle ${lifecycle || "unset"}) does not resolve in this SDD's repositories — name the repository by its id; only a planned member may name one that does not exist yet`);
117
+ }
118
+ out.implementationMembers.push({
119
+ id,
120
+ implementation_id: impl,
121
+ repo_id: resolved ? repoKey : "",
122
+ repo_key: repoKey,
123
+ role: str(m.role),
124
+ lifecycle,
125
+ evidence_direction: str(m["evidence-direction"]) || str(m.evidence_direction),
126
+ source_sdd_id: "",
127
+ platform_sdd_id: "",
128
+ });
129
+ }
130
+ if (area === "")
131
+ return out;
132
+ if (snapshotStore === undefined || snapshotStore === "") {
133
+ findings.add("coverage", `snapshots: ${impl} declares snapshot_area ${area} but no snapshot store was given (--snapshot-store or SDD_SNAPSHOT_STORE) — snapshots NOT built`);
134
+ return out;
135
+ }
136
+ const areaRoot = join(snapshotStore, area);
137
+ if (!(await exists(areaRoot))) {
138
+ findings.add("coverage", `snapshots: ${impl} snapshot_area ${area} does not exist under the snapshot store ${snapshotStore}`);
139
+ return out;
140
+ }
141
+ const priors = new Map();
142
+ for (const bid of await baselineIds(areaRoot)) {
143
+ let manifest;
144
+ try {
145
+ manifest = await readManifest(areaRoot, bid);
146
+ }
147
+ catch (e) {
148
+ if (!(e instanceof SnapshotError))
149
+ throw e;
150
+ findings.add("divergence", `snapshots: ${impl}@${bid} skipped — ${e.message}`);
151
+ continue;
152
+ }
153
+ if (manifest.implementation !== impl) {
154
+ findings.add("divergence", `snapshots: ${area}/${bid}/snapshot.yaml names implementation ${manifest.implementation}, the binding names ${impl} — skipped (M-ADR 0214 §4.1: one identity)`);
155
+ continue;
156
+ }
157
+ const sid = `${impl}@${bid}`;
158
+ if (manifest.prior_snapshot)
159
+ priors.set(sid, `${impl}@${manifest.prior_snapshot}`);
160
+ out.snapshots.push({
161
+ id: sid,
162
+ implementation_id: impl,
163
+ baseline_id: bid,
164
+ prior_snapshot_id: "",
165
+ credential_scan_result: manifest.credential_scan?.result ?? "",
166
+ source_sdd_id: "",
167
+ platform_sdd_id: "",
168
+ });
169
+ const inSnapshot = new Set();
170
+ const member = (repoKey, revision, observedAt, presence) => {
171
+ if (inSnapshot.has(repoKey)) {
172
+ findings.add("divergence", `snapshot_members: ${sid} lists ${repoKey} twice (as ${presence} the second time) — second skipped`);
173
+ return;
174
+ }
175
+ inSnapshot.add(repoKey);
176
+ const resolved = knownRepoIds.has(repoKey);
177
+ if (!resolved && presence !== "removed") {
178
+ findings.add("coverage", `snapshot_members: ${sid} member ${repoKey} does not resolve in this SDD's repositories`);
179
+ }
180
+ out.snapshotMembers.push({
181
+ id: `${sid}::${repoKey}`,
182
+ snapshot_id: sid,
183
+ repo_id: resolved ? repoKey : "",
184
+ repo_key: repoKey,
185
+ source_revision: revision,
186
+ observed_at: observedAt,
187
+ presence,
188
+ source_sdd_id: "",
189
+ platform_sdd_id: "",
190
+ });
191
+ };
192
+ for (const m of manifest.members) {
193
+ const carried = m.carried_from !== undefined;
194
+ member(m.canonical_repo_key, m.source_revision, m.observed_at ?? (carried ? m.carried_from : bid), carried ? "carried" : "observed");
195
+ }
196
+ for (const r of manifest.members_removed ?? []) {
197
+ member(r.canonical_repo_key, r.last_revision, r.last_observed ?? "", "removed");
198
+ }
199
+ }
200
+ // A prior that names a baseline not built here would dangle an internal FK; the
201
+ // link is kept only when its target is a row, and a missing one is said aloud.
202
+ const built = new Set(out.snapshots.map((s) => String(s["id"])));
203
+ for (const [i, s] of out.snapshots.entries()) {
204
+ const prior = priors.get(String(s["id"]));
205
+ if (prior === undefined)
206
+ continue;
207
+ if (built.has(prior))
208
+ out.snapshots[i] = { ...s, prior_snapshot_id: prior };
209
+ else
210
+ findings.add("divergence", `snapshots: ${String(s["id"])} names prior ${prior}, which is not a snapshot in ${area} — link dropped`);
211
+ }
212
+ return out;
213
+ }
214
+ //# sourceMappingURL=implementations.js.map
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Re-exports for the per-source row discoverers used by the canonical
3
+ * `sdd-cli catalogue build` orchestrator.
4
+ *
5
+ * Each builder is responsible for exactly one node catalogue (or a small
6
+ * tightly-coupled group when reading the same source). The orchestrator
7
+ * in build.ts composes them with a deterministic order.
8
+ */
9
+ export { buildDomains } from "./domains.js";
10
+ export { buildProducts } from "./products.js";
11
+ export { buildApplicationDataPlaneBindings, buildApplicationDataPlaneDeviations, } from "./application-data-plane-bindings.js";
12
+ export { buildApplicationConformanceCoverage } from "./application-conformance-coverage.js";
13
+ export { buildImplementations } from "./implementations.js";
14
+ export { buildRepositories } from "./repositories.js";
15
+ export { buildServices } from "./services.js";
16
+ export { buildAdrs } from "./adrs.js";
17
+ export { buildAdrSupersedes } from "./adr-supersedes.js";
18
+ export { buildFeatures } from "./features.js";
19
+ export { buildFeatureGroups } from "./feature-groups.js";
20
+ export { buildFeatureGrounding } from "./feature-grounding.js";
21
+ export { buildCapabilitySpec } from "./capability-spec.js";
22
+ export { buildDeclaredExternalContracts } from "./contracts.js";
23
+ export { buildContracts } from "./contracts.js";
24
+ export { buildRepoEdges, deriveFeatureRepoFromServices } from "./repo-edges.js";
25
+ export { buildRepoCapabilityExemption } from "./repo-capability-exemption.js";
26
+ export { buildServiceCapabilityExemption } from "./service-capability-exemption.js";
27
+ export { buildExternalSystemExemption } from "./external-system-exemption.js";
28
+ export { buildContainerDependency, reportEntryIdReferentCollisions } from "./container-dependency.js";
29
+ export { buildPersonaContainer } from "./persona-container.js";
30
+ export { buildEvents } from "./events.js";
31
+ export { buildServiceEvent, buildCapabilityEvent, buildFeatureEvent, } from "./event-edges.js";
32
+ export { buildServiceContract } from "./service-contract.js";
33
+ export { buildServiceContractConsumes } from "./service-contract-consumes.js";
34
+ export { buildGlossaryItems, backfillGlossaryRelations } from "./glossary.js";
35
+ export { buildGlossaryXref } from "./glossary-xref.js";
36
+ export { buildCapabilities } from "./capabilities.js";
37
+ export { buildC4Systems } from "./c4-systems.js";
38
+ export { buildC4Containers } from "./c4-containers.js";
39
+ export { buildC4Components } from "./c4-components.js";
40
+ export { buildPersonas } from "./personas.js";
41
+ export { buildScreens } from "./screens.js";
42
+ export { buildUserJourneys } from "./user-journeys.js";
43
+ export { buildPersonaJourney, buildPersonaFeature, buildJourneyFeature, } from "./persona-journey-edges.js";
44
+ export { buildProductEdges } from "./product-edges.js";
45
+ export { buildIntegrations } from "./integrations.js";
46
+ export { buildCapabilityService, buildCapabilityAdr, buildCapabilityFeature, buildCapabilityContract, } from "./capability-edges.js";
47
+ export { buildFeatureContract } from "./feature-contract.js";
48
+ export { buildDomainModels } from "./domain-models.js";
49
+ export { buildCapabilityDomainModel } from "./capability-domain-model.js";
50
+ export { buildFeatureDomainModel } from "./feature-domain-model.js";
51
+ export { buildFeatureSubscriptionChannels } from "./feature-subscription-channels.js";
52
+ export { buildProductPlatformDependencies } from "./product-platform-dependencies.js";
53
+ export { buildC4ExternalSystems } from "./c4-external-systems.js";
54
+ export { buildSystemExternalDependency } from "./system-external-dependency.js";
55
+ export { buildPersonaC4System } from "./persona-c4system.js";
56
+ export { entitySlug } from "./entity-slug.js";
57
+ export { buildEntitiesFromManifest } from "./entities-manifest.js";
58
+ export { buildEntitiesFromMermaid } from "./entities-mermaid.js";
59
+ export { buildServiceEntityFromTables } from "./service-entity-tables.js";
60
+ export { enrichEntityPersistenceKind } from "./entity-persistence.js";
61
+ export { buildC4ContainerEntity, buildC4SystemEntity, } from "./c4-entity-rollups.js";
62
+ export { buildQualityScores } from "./quality-scores.js";
63
+ export { buildScaffolds } from "./scaffolds.js";
64
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,344 @@
1
+ // integration_component builder — emits one row per component declared in an
2
+ // integration-product's `integration-manifest.yaml` `components:` array
3
+ // (M-ADR 0171 §9, catalogued per §10.1).
4
+ //
5
+ // An integration SDD is a one-to-many spec→code unit (M-ADR 0106): one SDD owns N
6
+ // component code repos, each with its own wire shape and interaction pattern. §9.3
7
+ // calls the tier the SOURCE OF TRUTH for the pattern, with the SDD-level
8
+ // `ingestionPattern`/`invocationPattern` as its aggregate. Until this builder the
9
+ // tier was consumed by NOTHING — the integrations builder read only
10
+ // integrationId/providerName/profileType/parentIntegrationProductId/
11
+ // sub_integrations, and no validator mentioned `components`.
12
+ //
13
+ // TWO RULES, both deliberately non-fatal.
14
+ //
15
+ // 1. OPTIONAL CODE LINKAGE (§10.2). A component names an INTERACTION SURFACE —
16
+ // commonly one provider feed — not necessarily a deployable. The tier sits at
17
+ // finer granularity than the code: one provider legitimately declares five feed
18
+ // components against two code repos, and those repos are already catalogued
19
+ // under their own correct names. So the id itself is NOT required to resolve.
20
+ //
21
+ // Requiring it would have coarsened the tier to the deployable set, collapsing
22
+ // five declared feeds into two repo names and destroying the per-feed
23
+ // distinction the tier exists to carry — the failure mode M-ADR 0193 §4.2
24
+ // refuses one axis over, where matching a data_plane binding's target against a
25
+ // platform-dep's would drive adopters to coarsen `target` to the gateway id.
26
+ //
27
+ // The code link is therefore the OPTIONAL `componentRepo`. Absent carries no
28
+ // finding (`resolution: undeclared`). DECLARED it must resolve, because a
29
+ // declared link is a positive assertion the method can check — and a
30
+ // declared-but-unresolvable one is a COVERAGE finding, not a load failure.
31
+ //
32
+ // 2. AGGREGATE CROSS-CHECK (§9.3/§10.3). Where both tiers are declared, the
33
+ // SDD-level pattern should appear among the matching-direction components'
34
+ // patterns. A disagreement is a convention finding, NOT a reconciliation: it
35
+ // means either the tier is incomplete or the SDD-level value is stale, and
36
+ // deriving one from the other would discard exactly that signal. Same reasoning
37
+ // M-ADR 0193 §4.2 reached for data_plane vs platform-dependencies.
38
+ //
39
+ // The method does not enumerate `componentType`, `transport`, `format` or `auth` —
40
+ // the adopter owns framework-role vocabulary (§9.5) and the wire details, while the
41
+ // portable semantic is the pattern.
42
+ import { readdir, readFile } from "node:fs/promises";
43
+ import { join } from "node:path";
44
+ import { parse as parseYaml } from "yaml";
45
+ import { z } from "zod";
46
+ const REL_PATH = "integration-manifest.yaml";
47
+ const ComponentSchema = z.object({
48
+ id: z.string().optional(),
49
+ componentRepo: z.string().optional(),
50
+ componentType: z.string().optional(),
51
+ protocol: z
52
+ .object({
53
+ transport: z.string().optional(),
54
+ format: z.string().optional(),
55
+ auth: z.string().optional(),
56
+ })
57
+ .optional(),
58
+ dataFlow: z
59
+ .object({
60
+ direction: z.string().optional(),
61
+ frequency: z.string().optional(),
62
+ primaryEntities: z.array(z.string()).optional(),
63
+ })
64
+ .optional(),
65
+ ingestionPattern: z.string().optional(),
66
+ invocationPattern: z.string().optional(),
67
+ patternQualifier: z.string().optional(),
68
+ });
69
+ /**
70
+ * A per-component manifest is a FULL integration-manifest shape, so it carries the
71
+ * component fields plus its own `integrationId` (its declared identity). Only the
72
+ * fields this catalogue models are read; the rest — `dependencies`, `field_mappings`,
73
+ * `evidence`, `openQuestions` — are deliberately out of scope here: they are new concepts,
74
+ * not component attributes. `dependencies.sharedLibraries` is read by its own builder,
75
+ * repo-dependency.ts, as `repo_dependency` rows (M-ADR 0217).
76
+ */
77
+ const ComponentManifestSchema = ComponentSchema.extend({
78
+ integrationId: z.string().optional(),
79
+ });
80
+ const ManifestSchema = z.object({
81
+ integrationId: z.string().optional(),
82
+ ingestionPattern: z.string().optional(),
83
+ invocationPattern: z.string().optional(),
84
+ components: z.array(ComponentSchema).optional(),
85
+ });
86
+ export async function buildIntegrationComponents(inputs) {
87
+ if (inputs.profile !== "integration-product")
88
+ return [];
89
+ let text;
90
+ try {
91
+ text = await readFile(join(inputs.sddPath, REL_PATH), "utf-8");
92
+ }
93
+ catch (err) {
94
+ // A missing manifest is already reported by the integrations builder; a second
95
+ // finding for the same cause would be noise.
96
+ if (err.code !== "ENOENT")
97
+ throw err;
98
+ return [];
99
+ }
100
+ let raw;
101
+ try {
102
+ raw = parseYaml(text);
103
+ }
104
+ catch {
105
+ return []; // likewise already reported
106
+ }
107
+ const parsed = ManifestSchema.safeParse(raw);
108
+ if (!parsed.success) {
109
+ inputs.findings.add("convention", `${REL_PATH}: components shape violation — ${parsed.error.issues
110
+ .map((i) => i.path.join(".") + ": " + i.message)
111
+ .join("; ")}`);
112
+ return [];
113
+ }
114
+ const components = parsed.data.components ?? [];
115
+ // NO early return on an empty array: §9.1's SECOND home is read below, and 21 SDDs
116
+ // on the reverse estate declare components ONLY there. Returning early here is what
117
+ // kept 86 of 101 components invisible — the array form was not just the first source
118
+ // read, it was the only one that could produce a row at all.
119
+ const integrationId = (parsed.data.integrationId ?? "").trim() || inputs.sddId;
120
+ const rows = [];
121
+ const seen = new Set();
122
+ for (const [i, c] of components.entries()) {
123
+ const componentId = (c.id ?? "").trim();
124
+ if (componentId === "") {
125
+ inputs.findings.add("convention", `${REL_PATH}: components[${i}] has no id — dropped; a component must name the interaction surface it represents (M-ADR 0171 §9.1)`);
126
+ continue;
127
+ }
128
+ const id = `${integrationId}__${componentId}`;
129
+ if (seen.has(id)) {
130
+ inputs.findings.add("divergence", `integration_component: duplicate component id="${id}" — two components of one integration cannot share an id; second dropped`);
131
+ continue;
132
+ }
133
+ seen.add(id);
134
+ // §10.2 linkage. Resolve the DECLARED `componentRepo`, never the component id
135
+ // itself. Repository first, service as the legitimate alternative where the
136
+ // deployable is catalogued as one.
137
+ const componentRepo = (c.componentRepo ?? "").trim();
138
+ let resolution;
139
+ if (componentRepo === "") {
140
+ // The normal case for a feed-level component. Not a finding: the method does
141
+ // not impose deployable granularity on the tier.
142
+ resolution = "undeclared";
143
+ }
144
+ else if (inputs.knownRepoIds.has(componentRepo)) {
145
+ resolution = "repository";
146
+ }
147
+ else if (inputs.knownServiceIds.has(componentRepo)) {
148
+ resolution = "service";
149
+ }
150
+ else {
151
+ resolution = "unresolved";
152
+ inputs.findings.add("coverage", `integration_component: component "${componentId}" (${integrationId}) declares componentRepo "${componentRepo}", which matches no repositories.id or services.id in this SDD — ` +
153
+ `a declared code linkage is an assertion the method checks (M-ADR 0171 §10.2). Either catalogue that deployable, correct the linkage, or drop it ` +
154
+ `(an undeclared linkage is legitimate — the tier is at interaction-surface granularity). Not a load failure.`);
155
+ }
156
+ const direction = (c.dataFlow?.direction ?? "").trim();
157
+ rows.push({
158
+ id,
159
+ integration_id: integrationId,
160
+ component_id: componentId,
161
+ component_repo: componentRepo,
162
+ resolution,
163
+ component_type: (c.componentType ?? "").trim(),
164
+ direction,
165
+ frequency: (c.dataFlow?.frequency ?? "").trim(),
166
+ transport: (c.protocol?.transport ?? "").trim(),
167
+ format: (c.protocol?.format ?? "").trim(),
168
+ auth: (c.protocol?.auth ?? "").trim(),
169
+ ingestion_pattern: (c.ingestionPattern ?? "").trim(),
170
+ invocation_pattern: (c.invocationPattern ?? "").trim(),
171
+ pattern_qualifier: (c.patternQualifier ?? "").trim(),
172
+ primary_entities: (c.dataFlow?.primaryEntities ?? []).join(";"),
173
+ declared_in: "components-array",
174
+ source_sdd_id: "",
175
+ });
176
+ }
177
+ // SECOND HOME (§9.1): a full `integration-manifest.yaml` per component repo under
178
+ // `repos/<component>/`. Read after the array form so `seen` already holds the
179
+ // array-form ids and a collision across the two homes is caught rather than
180
+ // silently duplicated — measured disjoint today, but the two forms are authored
181
+ // independently and nothing stops them meeting.
182
+ rows.push(...(await readComponentManifests({
183
+ sddPath: inputs.sddPath,
184
+ findings: inputs.findings,
185
+ integrationId,
186
+ knownRepoIds: inputs.knownRepoIds,
187
+ knownServiceIds: inputs.knownServiceIds,
188
+ seen,
189
+ })));
190
+ checkAggregate({
191
+ findings: inputs.findings,
192
+ integrationId,
193
+ sddIngestion: (parsed.data.ingestionPattern ?? "").trim(),
194
+ sddInvocation: (parsed.data.invocationPattern ?? "").trim(),
195
+ rows,
196
+ });
197
+ return rows;
198
+ }
199
+ /** `repos/` — where the per-component manifests live (M-ADR 0171 §9.1, second home). */
200
+ const REPOS_DIR = "repos";
201
+ /**
202
+ * Read the per-component manifests: one row per `repos/<component>/integration-manifest.yaml`.
203
+ *
204
+ * IDENTITY. Same `${integrationId}__${componentId}` scheme as the array form, so both
205
+ * homes are addressable the same way and the PK stays `id`. The component id is the
206
+ * manifest's own `integrationId` — its declared identity — NOT the directory name. They
207
+ * agree in 82 of 86 real cases; where they differ the declaration wins, because a
208
+ * directory is a location and the manifest is a statement.
209
+ *
210
+ * RESOLUTION differs from the array form by construction, and that is real rather than a
211
+ * quirk: the containing directory IS the component repo, so linkage is structural. It is
212
+ * resolved against the same known-id sets, so a component whose directory matches no
213
+ * catalogued repo or service still reports `unresolved` — the useful signal — instead of
214
+ * being assumed correct because it exists on disk.
215
+ */
216
+ async function readComponentManifests(args) {
217
+ let entries;
218
+ try {
219
+ entries = (await readdir(join(args.sddPath, REPOS_DIR), { withFileTypes: true }))
220
+ .filter((e) => e.isDirectory())
221
+ .map((e) => e.name)
222
+ .sort();
223
+ }
224
+ catch (err) {
225
+ // No `repos/` is the normal case for an SDD using the array form, or none.
226
+ if (err.code === "ENOENT")
227
+ return [];
228
+ throw err;
229
+ }
230
+ const rows = [];
231
+ for (const dir of entries) {
232
+ const rel = `${REPOS_DIR}/${dir}/${REL_PATH}`;
233
+ let text;
234
+ try {
235
+ text = await readFile(join(args.sddPath, rel), "utf-8");
236
+ }
237
+ catch (err) {
238
+ // A component repo directory without a manifest is not a defect: the
239
+ // per-component form is optional, and `repos/` holds reverse content besides.
240
+ if (err.code === "ENOENT")
241
+ continue;
242
+ throw err;
243
+ }
244
+ let raw;
245
+ try {
246
+ raw = parseYaml(text);
247
+ }
248
+ catch (err) {
249
+ args.findings.add("convention", `${rel}: failed to parse YAML — component dropped (${err.message})`);
250
+ continue;
251
+ }
252
+ const parsed = ComponentManifestSchema.safeParse(raw);
253
+ if (!parsed.success) {
254
+ args.findings.add("convention", `${rel}: shape violation — ${parsed.error.issues
255
+ .map((i) => i.path.join(".") + ": " + i.message)
256
+ .join("; ")}`);
257
+ continue;
258
+ }
259
+ // The manifest's declared identity, falling back to the directory only when it
260
+ // declares none at all.
261
+ const componentId = (parsed.data.integrationId ?? "").trim() || dir;
262
+ const id = `${args.integrationId}__${componentId}`;
263
+ if (args.seen.has(id)) {
264
+ args.findings.add("divergence", `integration_component: component id="${id}" declared in BOTH §9.1 homes (components array and ${rel}) — ` +
265
+ `one component cannot be declared twice; the per-component manifest is dropped. Remove whichever declaration is stale.`);
266
+ continue;
267
+ }
268
+ args.seen.add(id);
269
+ // Structural linkage: the directory IS the repo. Still resolved, so a component
270
+ // whose directory matches nothing catalogued reports `unresolved` rather than
271
+ // being assumed correct because it exists on disk.
272
+ let resolution;
273
+ if (args.knownRepoIds.has(dir))
274
+ resolution = "repository";
275
+ else if (args.knownServiceIds.has(dir))
276
+ resolution = "service";
277
+ else {
278
+ resolution = "unresolved";
279
+ args.findings.add("coverage", `integration_component: per-component manifest at ${rel} names a component repo "${dir}" that matches no repositories.id or services.id in this SDD (M-ADR 0171 §10.2). ` +
280
+ `Catalogue that deployable or correct the directory name. Not a load failure.`);
281
+ }
282
+ rows.push({
283
+ id,
284
+ integration_id: args.integrationId,
285
+ component_id: componentId,
286
+ component_repo: dir,
287
+ resolution,
288
+ component_type: (parsed.data.componentType ?? "").trim(),
289
+ direction: (parsed.data.dataFlow?.direction ?? "").trim(),
290
+ frequency: (parsed.data.dataFlow?.frequency ?? "").trim(),
291
+ transport: (parsed.data.protocol?.transport ?? "").trim(),
292
+ format: (parsed.data.protocol?.format ?? "").trim(),
293
+ auth: (parsed.data.protocol?.auth ?? "").trim(),
294
+ ingestion_pattern: (parsed.data.ingestionPattern ?? "").trim(),
295
+ invocation_pattern: (parsed.data.invocationPattern ?? "").trim(),
296
+ pattern_qualifier: (parsed.data.patternQualifier ?? "").trim(),
297
+ primary_entities: (parsed.data.dataFlow?.primaryEntities ?? []).join(";"),
298
+ declared_in: "component-manifest",
299
+ source_sdd_id: "",
300
+ });
301
+ }
302
+ return rows;
303
+ }
304
+ /**
305
+ * §9.3 / §10.3 cross-check. The SDD-level pattern is the aggregate of the
306
+ * matching-direction components' patterns, so a declared SDD-level value that
307
+ * appears nowhere in the tier is a disagreement worth surfacing. Cross-checked,
308
+ * never derived — the disagreement is the informative case.
309
+ *
310
+ * Silent when either side is undeclared: absence is not disagreement, and the tier
311
+ * is optional.
312
+ */
313
+ function checkAggregate(args) {
314
+ const { findings, integrationId, sddIngestion, sddInvocation, rows } = args;
315
+ const patternsFor = (dir, col) => {
316
+ const out = new Set();
317
+ for (const r of rows) {
318
+ const d = String(r["direction"] ?? "");
319
+ // `bidirectional` counts toward both directions.
320
+ if (d !== dir && d !== "bidirectional")
321
+ continue;
322
+ const p = String(r[col] ?? "");
323
+ if (p !== "")
324
+ out.add(p);
325
+ }
326
+ return out;
327
+ };
328
+ for (const [sddValue, dir, col, field] of [
329
+ [sddIngestion, "inbound", "ingestion_pattern", "ingestionPattern"],
330
+ [sddInvocation, "outbound", "invocation_pattern", "invocationPattern"],
331
+ ]) {
332
+ if (sddValue === "")
333
+ continue; // nothing declared at SDD level
334
+ const tier = patternsFor(dir, col);
335
+ if (tier.size === 0)
336
+ continue; // tier declares no pattern for this direction
337
+ if (!tier.has(sddValue)) {
338
+ findings.add("convention", `integration_component: ${integrationId} declares SDD-level ${field}="${sddValue}" but its ${dir} components declare {${[...tier].sort().join(", ")}} — ` +
339
+ `the SDD-level value is the aggregate of the component tier (M-ADR 0171 §9.3), so either the tier is incomplete or the SDD-level value is stale. ` +
340
+ `Cross-checked, not derived (§10.3).`);
341
+ }
342
+ }
343
+ }
344
+ //# sourceMappingURL=integration-components.js.map