@sdd-method/author-cli 0.0.0-stage → 0.2.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 +100 -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 +447 -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 +57 -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 +53 -0
  198. package/dist/verify-program.js +122 -0
  199. package/dist/verify-version.json +4 -0
  200. package/package.json +28 -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,100 @@
1
+ /**
2
+ * Metamodel-driven primary-key normalisation for the per-SDD build.
3
+ *
4
+ * Every catalogue declares a `primary_key` in the canonical schema. The build
5
+ * collects each catalogue's rows from one or more per-source walkers and
6
+ * concatenates them (see `build.ts` `collectRows`). For most catalogues each
7
+ * walker emits PK-unique rows or dedupes internally, but **edge tables fed by
8
+ * more than one source** (`feature_service`, `feature_repo` — from the
9
+ * feature-grounding *and* capability-spec walkers) can legitimately have the
10
+ * same fact asserted by both sources. Each walker's own `seen…` guard only
11
+ * dedupes intra-walker, so the concatenated result carried duplicate PKs into
12
+ * `catalogues/*.csv`, which `catalogue aggregate` then correctly rejected.
13
+ *
14
+ * This pass makes keyed-table emission **set-correct over the declared PK** for
15
+ * every catalogue, by construction:
16
+ * - identical rows for the same PK → keep one (silent union — the same fact
17
+ * asserted by ≥2 sources is not an error);
18
+ * - same PK with a **differing** payload → a hard conflict the caller fails
19
+ * the build on, with provenance, so this class of defect surfaces at
20
+ * `build` (where the offending SDD is unambiguous) instead of only at
21
+ * `aggregate`.
22
+ *
23
+ * Background: the method's coordination record feature-edge-cross-source-duplicate.
24
+ * Sibling precedents (the unconditional-emit fixes whose per-walker dedup this
25
+ * generalises across sources): `feature-grounding-collision-edges-coordination.md`
26
+ * (0.30.2) and `capability-spec-collision-edges-coordination.md` (0.30.3).
27
+ */
28
+ /**
29
+ * Render a cell exactly as the CSV writer will, so two rows count as identical
30
+ * iff they would serialise to the same CSV line. Kept in lock-step with
31
+ * `csv-writer.ts` `formatCell` (null/undefined and "" collapse to the same
32
+ * empty cell; booleans normalise to "true"/"false").
33
+ */
34
+ function cell(value) {
35
+ if (value === null || value === undefined)
36
+ return "";
37
+ if (typeof value === "boolean")
38
+ return (value ? "true" : "false");
39
+ return String(value);
40
+ }
41
+ /** Signature over the schema-declared columns — the row's rendered identity. */
42
+ function rowSignature(row, columns) {
43
+ // NUL separator can't appear in a rendered cell, so it's an unambiguous join.
44
+ return columns.map((c) => cell(row[c])).join("\u0000");
45
+ }
46
+ /**
47
+ * Normalise every keyed catalogue in `rowsByCatalogue` to PK-unique rows.
48
+ * Mutates the map in place (replacing only catalogues that actually had
49
+ * exact-duplicate PKs) and returns the conflicts + union count.
50
+ *
51
+ * First-seen row wins for ordering; the CSV writer re-sorts by PK anyway, so
52
+ * order here is immaterial beyond determinism.
53
+ */
54
+ export function dedupeKeyedTables(schema, rowsByCatalogue, findings) {
55
+ const conflicts = [];
56
+ let unionedRows = 0;
57
+ for (const cat of schema.catalogues) {
58
+ const rows = rowsByCatalogue.get(cat.name) ?? [];
59
+ if (rows.length < 2)
60
+ continue;
61
+ const columns = cat.columns.map((c) => c.name);
62
+ const pk = cat.primaryKey;
63
+ const seen = new Map(); // PK value → row signature
64
+ const deduped = [];
65
+ let changed = false;
66
+ for (const row of rows) {
67
+ const pkRaw = row[pk];
68
+ // Rows with no PK value can't be set-keyed — keep verbatim and let FK
69
+ // validation surface the empty key (mirrors the aggregate dup-check,
70
+ // which also skips empty PKs).
71
+ if (pkRaw === undefined || pkRaw === null || pkRaw === "") {
72
+ deduped.push(row);
73
+ continue;
74
+ }
75
+ const value = String(pkRaw);
76
+ const sig = rowSignature(row, columns);
77
+ const existingSig = seen.get(value);
78
+ if (existingSig === undefined) {
79
+ seen.set(value, sig);
80
+ deduped.push(row);
81
+ continue;
82
+ }
83
+ changed = true;
84
+ if (existingSig === sig) {
85
+ // Same PK, identical payload — the same fact asserted by another
86
+ // source. Silent union: drop the duplicate.
87
+ unionedRows += 1;
88
+ continue;
89
+ }
90
+ // Same PK, differing payload — a genuine data-integrity conflict.
91
+ conflicts.push({ catalogue: cat.name, primaryKey: pk, value });
92
+ findings.add("divergence", `${cat.name}: primary key ${pk}="${value}" emitted with conflicting ` +
93
+ `payloads by multiple build sources — cannot union (fix the source data)`);
94
+ }
95
+ if (changed)
96
+ rowsByCatalogue.set(cat.name, deduped);
97
+ }
98
+ return { conflicts, unionedRows };
99
+ }
100
+ //# sourceMappingURL=dedupe-keyed-tables.js.map
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Engine-path FK/PK/enum validation — M-ADR 0205 §4.4 (Phase A2).
3
+ *
4
+ * Runs the ddl-generator's queries on the embedded engine and returns
5
+ * results in the SAME shapes as fk-validator.ts / enum-validator.ts, so
6
+ * the parity harness can compare count AND list, field for field. The
7
+ * input is the same rowsByCatalogue map the in-memory validators take:
8
+ * A2 compares the two VALIDATION implementations on identical parsed
9
+ * input — CSV-read equivalence is deliberately out of this comparison's
10
+ * scope and belongs to the cutover evaluation.
11
+ *
12
+ * The engine package is a devDependency until cutover (M-ADR 0205 §4.1's
13
+ * no-fallback rule governs how it eventually ships). It is imported
14
+ * dynamically inside the entry function so merely loading this module
15
+ * never requires it: no production path imports the engine today, and an
16
+ * adopter install without the package only fails if something actually
17
+ * calls it — loudly.
18
+ *
19
+ * PK-duplicate detection has NO in-memory counterpart at validate time
20
+ * (builders dedupe at emission instead), so pkDuplicates is engine-only
21
+ * information: reported, never part of the parity claim — you cannot
22
+ * claim agreement with an instrument that does not exist.
23
+ */
24
+ import { generateConstraintQueries, generateExternalFindingQueries, generateTableDdl, sqlType, } from "./ddl-generator.js";
25
+ const INSERT_CHUNK = 500;
26
+ function sqlStringLiteral(value) {
27
+ return `'${value.replace(/'/g, "''")}'`;
28
+ }
29
+ /**
30
+ * One loaded engine: schema DDL applied, every provided catalogue's rows
31
+ * inserted. Shared by the FK/PK/enum path and the coverage predicates so
32
+ * there is exactly one load semantics to reason about.
33
+ */
34
+ export async function createEngineSession(schema, rowsByCatalogue) {
35
+ const { DuckDBInstance } = await import("@duckdb/node-api");
36
+ const instance = await DuckDBInstance.create(":memory:");
37
+ const conn = await instance.connect();
38
+ for (const ddl of generateTableDdl(schema)) {
39
+ await conn.run(ddl);
40
+ }
41
+ await loadRows(conn, schema, rowsByCatalogue);
42
+ return { conn, dispose: () => conn.disconnectSync() };
43
+ }
44
+ // @rule engine-fk-validation
45
+ export async function validateWithEngine(schema, rowsByCatalogue, options = {}) {
46
+ const session = await createEngineSession(schema, rowsByCatalogue);
47
+ const conn = session.conn;
48
+ try {
49
+ const typeNonconformances = await findTypeNonconformances(conn, schema, rowsByCatalogue);
50
+ const failures = [];
51
+ const enumViolations = [];
52
+ const pkDuplicates = [];
53
+ for (const q of generateConstraintQueries(schema, options)) {
54
+ const reader = await conn.runAndReadAll(q.sql);
55
+ for (const row of reader.getRows()) {
56
+ if (q.kind === "fk") {
57
+ failures.push({
58
+ catalogue: q.catalogue,
59
+ column: q.column,
60
+ referencingRow: String(row[0] ?? ""),
61
+ value: String(row[1] ?? ""),
62
+ target: q.target,
63
+ });
64
+ }
65
+ else if (q.kind === "enum") {
66
+ const enumDef = schema.enums[q.target];
67
+ enumViolations.push({
68
+ catalogue: q.catalogue,
69
+ column: q.column,
70
+ rowPk: row[0] === null || row[0] === undefined ? "<no-pk>" : String(row[0]),
71
+ value: String(row[1] ?? ""),
72
+ allowed: enumDef.values.map((v) => v.value).join(" | "),
73
+ });
74
+ }
75
+ else {
76
+ pkDuplicates.push({
77
+ catalogue: q.catalogue,
78
+ rowPk: String(row[0] ?? ""),
79
+ count: Number(row[1]),
80
+ });
81
+ }
82
+ }
83
+ }
84
+ const externalFindingDetails = [];
85
+ for (const q of generateExternalFindingQueries(schema, options)) {
86
+ const reader = await conn.runAndReadAll(q.sql);
87
+ for (const row of reader.getRows()) {
88
+ externalFindingDetails.push({
89
+ catalogue: q.catalogue,
90
+ column: q.column,
91
+ target: q.target,
92
+ rowPk: row[0] === null || row[0] === undefined ? "?" : String(row[0]),
93
+ value: String(row[1] ?? ""),
94
+ });
95
+ }
96
+ }
97
+ return {
98
+ fk: {
99
+ internalPass: failures.length === 0,
100
+ internalFailures: failures,
101
+ externalFindings: externalFindingDetails.length,
102
+ },
103
+ enums: { pass: enumViolations.length === 0, violations: enumViolations },
104
+ externalFindingDetails,
105
+ pkDuplicates,
106
+ typeNonconformances,
107
+ };
108
+ }
109
+ finally {
110
+ session.dispose();
111
+ }
112
+ }
113
+ /**
114
+ * The A4 cutover surface: runs the engine validation and writes findings
115
+ * into the caller's FindingsLog with BYTE-IDENTICAL text to what
116
+ * fk-validator.ts / enum-validator.ts wrote — adopters' committed
117
+ * findings baselines hold those literal lines, so the text is a
118
+ * compatibility contract, not a style choice. Line ORDER within a
119
+ * category follows the engine's total ORDER BY (M-ADR 0205 §4.2) and may
120
+ * differ from the old file-order rendering; baselines compare by
121
+ * membership, so ordering is free — declared in the change-set anyway.
122
+ */
123
+ export async function validateWithEngineToLog(schema, rowsByCatalogue, findings, options = {}, extras = {}) {
124
+ const logEnumFindings = extras.logEnumFindings !== false;
125
+ const res = await validateWithEngine(schema, rowsByCatalogue, options);
126
+ for (const d of res.externalFindingDetails) {
127
+ findings.add("external", `${d.catalogue}.${d.column} → ${d.target}=${d.value} ` +
128
+ `(referencing row pk=${d.rowPk}; resolves at aggregation)`);
129
+ }
130
+ if (logEnumFindings) {
131
+ const enumNameByCatalogueColumn = new Map();
132
+ for (const cat of schema.catalogues) {
133
+ for (const [column, enumRef] of Object.entries(cat.columnEnums)) {
134
+ enumNameByCatalogueColumn.set(`${cat.name}.${column}`, enumRef.replace(/^enums\./, ""));
135
+ }
136
+ }
137
+ for (const v of res.enums.violations) {
138
+ const enumName = enumNameByCatalogueColumn.get(`${v.catalogue}.${v.column}`);
139
+ findings.add("divergence", `${v.catalogue}.${v.column}: row ${v.rowPk} value "${v.value}" is outside the bound enum ${enumName} (${v.allowed})`, "error");
140
+ }
141
+ }
142
+ return { fk: res.fk, enums: res.enums };
143
+ }
144
+ async function loadRows(conn, schema, rowsByCatalogue) {
145
+ for (const cat of schema.catalogues) {
146
+ const rows = rowsByCatalogue.get(cat.name) ?? [];
147
+ if (rows.length === 0)
148
+ continue;
149
+ for (let i = 0; i < rows.length; i += INSERT_CHUNK) {
150
+ const chunk = rows.slice(i, i + INSERT_CHUNK);
151
+ const tuples = chunk
152
+ .map((row) => `(${cat.columns
153
+ .map((c) => literal(row[c.name], cat.name, c.name, c.type))
154
+ .join(", ")})`)
155
+ .join(",\n");
156
+ await conn.run(`INSERT INTO "${cat.name}" VALUES\n${tuples};`);
157
+ }
158
+ }
159
+ }
160
+ /**
161
+ * Renders one cell. String columns keep the empty string AS an empty
162
+ * string — the in-memory validators' emptiness predicates see '' from the
163
+ * CSV reader, and the generated queries test both NULL and ''. Non-string
164
+ * columns TRY_CAST with '' becoming NULL: one uncastable cell must not
165
+ * abort a whole run (measured on a real estate: a prose value in a
166
+ * date-typed column), and the miss is not smoothed either —
167
+ * findTypeNonconformances re-derives every such cell with the engine's
168
+ * own cast semantics and reports it.
169
+ */
170
+ function literal(value, catalogue, column, type) {
171
+ const s = value === null || value === undefined ? "" : String(value);
172
+ if (type === "string")
173
+ return sqlStringLiteral(s);
174
+ if (s === "")
175
+ return "NULL";
176
+ return `TRY_CAST(${sqlStringLiteral(s)} AS ${sqlType(catalogue, column, type)})`;
177
+ }
178
+ /**
179
+ * Exact engine-semantics detection of the cells TRY_CAST nulled: the
180
+ * distinct non-empty values of every typed column are re-checked with the
181
+ * SAME cast the load used — a JS re-implementation of the engine's parsers
182
+ * would be a second opinion pretending to be the first.
183
+ */
184
+ async function findTypeNonconformances(conn, schema, rowsByCatalogue) {
185
+ const out = [];
186
+ for (const cat of schema.catalogues) {
187
+ const rows = rowsByCatalogue.get(cat.name) ?? [];
188
+ if (rows.length === 0)
189
+ continue;
190
+ for (const col of cat.columns) {
191
+ if (col.type === "string")
192
+ continue;
193
+ const counts = new Map();
194
+ for (const row of rows) {
195
+ const v = row[col.name];
196
+ const s = v === null || v === undefined ? "" : String(v);
197
+ if (s === "")
198
+ continue;
199
+ counts.set(s, (counts.get(s) ?? 0) + 1);
200
+ }
201
+ if (counts.size === 0)
202
+ continue;
203
+ const engineType = sqlType(cat.name, col.name, col.type);
204
+ const values = [...counts.keys()].sort();
205
+ const tuples = values.map((v) => `(${sqlStringLiteral(v)})`).join(", ");
206
+ const reader = await conn.runAndReadAll(`SELECT v FROM (VALUES ${tuples}) t(v) ` +
207
+ `WHERE TRY_CAST(v AS ${engineType}) IS NULL ORDER BY v;`);
208
+ for (const row of reader.getRows()) {
209
+ const value = String(row[0]);
210
+ out.push({
211
+ catalogue: cat.name,
212
+ column: col.name,
213
+ declaredType: col.type,
214
+ value,
215
+ rows: counts.get(value) ?? 0,
216
+ });
217
+ }
218
+ }
219
+ }
220
+ return out;
221
+ }
222
+ /**
223
+ * The population a PASS was measured over, for the verdict line. An FK or enum
224
+ * PASS over zero rows is true of nothing (empty-set sweep, R2): the count makes
225
+ * the zero visible. The `PASS` token itself is kept for anything that greps it.
226
+ */
227
+ export function validatedPopulation(rowsByCatalogue) {
228
+ let rows = 0;
229
+ let nonEmpty = 0;
230
+ for (const r of rowsByCatalogue.values()) {
231
+ rows += r.length;
232
+ if (r.length > 0)
233
+ nonEmpty += 1;
234
+ }
235
+ return rows === 0
236
+ ? "0 rows — nothing was validated"
237
+ : `${rows} row(s) across ${nonEmpty} non-empty catalogue(s)`;
238
+ }
239
+ //# sourceMappingURL=engine-validator.js.map
@@ -0,0 +1,76 @@
1
+ // External-system row identity (schema 3.0.0) and the legacy-id migration
2
+ // map — M-ADR 0203 §4.2, completing M-ADR 0189 §4.2.
3
+ //
4
+ // From 3.0.0 a `c4_external_systems` row id derives from the DECLARED
5
+ // diagram entry id (`System_Ext(<entry-id>, …)`), not from a slug of the
6
+ // display name. The declared id is the identity the aggregator resolves
7
+ // (M-ADR 0189 §4.2); keying the row on anything else meant the row's own
8
+ // PK was label-keyed evidence — the exact identity class 0189 measured
9
+ // false-positiving at 14 of 15.
10
+ //
11
+ // The 2.x name-slug ids remain derivable from data every 2.30.0+ catalogue
12
+ // carries (`name`), so the old→new map is a build artefact, not an authored
13
+ // migration: `sdd-cli catalogue migration-map` emits it, and the exemption
14
+ // loader consults it during the cut window (an authored exemption keyed by
15
+ // a mapped old id is regenerable, NOT drift — the F30 rule at schema scale).
16
+ /**
17
+ * Normalise a declared diagram entry id to slug form. Camel humps split
18
+ * BEFORE lowercasing (the F11a rule, matching the aggregator's resolver):
19
+ * most SDD ids are hyphenated, and `bvfmGatewayFramework` must derive the
20
+ * same identity the adjudicator resolves for it (`bvfm-gateway-framework`).
21
+ */
22
+ export function normaliseEntryId(entryId) {
23
+ return entryId
24
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
25
+ .toLowerCase()
26
+ .replace(/[^a-z0-9]+/g, "-")
27
+ .replace(/^-+|-+$/g, "");
28
+ }
29
+ /** The 3.0.0 row id for a declared diagram entry id, or "" when the entry id normalises to nothing. */
30
+ export function extIdFromEntryId(entryId) {
31
+ const slug = normaliseEntryId(entryId);
32
+ return slug === "" ? "" : `ext-${slug}`;
33
+ }
34
+ /**
35
+ * The 2.x row id for a display name — the pre-3.0.0 derivation, kept
36
+ * VERBATIM (including the parenthetical strip) so the migration map
37
+ * reproduces exactly the ids the old builder minted.
38
+ */
39
+ export function legacyExtIdFromName(name) {
40
+ const slug = name
41
+ .toLowerCase()
42
+ .replace(/\s*\([^)]+\)/g, "")
43
+ .replace(/[^a-z0-9]+/g, "-")
44
+ .replace(/^-+|-+$/g, "");
45
+ return slug === "" ? "" : `ext-${slug}`;
46
+ }
47
+ /**
48
+ * legacy (2.x, name-derived) id → the 3.0.0 ids it maps to, from built
49
+ * rows. Usually one; MORE than one means two distinct declared identities
50
+ * shared a display-name slug and were wrongly merged as one row under 2.x
51
+ * — the map surfaces the split rather than guessing (the consumer decides,
52
+ * loudly).
53
+ */
54
+ export function buildLegacyExtIdMap(rows) {
55
+ const map = new Map();
56
+ for (const row of rows) {
57
+ const legacy = legacyExtIdFromName(String(row["name"] ?? ""));
58
+ if (legacy === "")
59
+ continue;
60
+ const current = String(row["id"] ?? "");
61
+ if (current === "" || legacy === current)
62
+ continue;
63
+ const ids = map.get(legacy);
64
+ if (ids) {
65
+ if (!ids.includes(current))
66
+ ids.push(current);
67
+ }
68
+ else {
69
+ map.set(legacy, [current]);
70
+ }
71
+ }
72
+ for (const ids of map.values())
73
+ ids.sort((a, b) => a.localeCompare(b));
74
+ return map;
75
+ }
76
+ //# sourceMappingURL=ext-id.js.map
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Loader for the optional `.sdd/feature-grounding-aliases.yaml` config
3
+ * per ADR 0143 (schema 2.5.0).
4
+ *
5
+ * Adopters use this map to translate display names in
6
+ * `feature-grounding.md` tables (class names, file basenames, route
7
+ * fragments) into canonical service / repo ids that resolve against
8
+ * `services.csv` and `repositories.csv`. Follows the same shape
9
+ * convention as the existing capability_service alias map.
10
+ *
11
+ * File shape:
12
+ *
13
+ * service_aliases:
14
+ * AuthHandler: example-service-api
15
+ * web-ui: example-web-ui
16
+ * repo_aliases:
17
+ * camera-bff: global-camera-bff
18
+ *
19
+ * Either block is optional. Empty / missing file = no aliases (every
20
+ * reference must resolve directly against the canonical id).
21
+ */
22
+ import { readFile } from "node:fs/promises";
23
+ import { join } from "node:path";
24
+ import { parse as parseYaml } from "yaml";
25
+ const ALIASES_FILENAME = join(".sdd", "feature-grounding-aliases.yaml");
26
+ /**
27
+ * Load the optional alias map. Returns `null` when the file is absent
28
+ * — callers treat that as "no aliases, all references must resolve
29
+ * directly against the canonical id." Throws on malformed YAML.
30
+ */
31
+ export async function loadFeatureGroundingAliases(sddPath) {
32
+ const full = join(sddPath, ALIASES_FILENAME);
33
+ let text;
34
+ try {
35
+ text = await readFile(full, "utf-8");
36
+ }
37
+ catch (err) {
38
+ const code = err.code;
39
+ if (code === "ENOENT")
40
+ return null;
41
+ throw err;
42
+ }
43
+ const raw = parseYaml(text);
44
+ if (raw === null || typeof raw !== "object") {
45
+ throw new Error(`${ALIASES_FILENAME}: expected a mapping at the document root`);
46
+ }
47
+ return {
48
+ serviceAliases: toStringMap(raw.service_aliases),
49
+ repoAliases: toStringMap(raw.repo_aliases),
50
+ relPath: ALIASES_FILENAME,
51
+ };
52
+ }
53
+ function toStringMap(raw) {
54
+ const out = new Map();
55
+ if (raw === null || raw === undefined)
56
+ return out;
57
+ if (typeof raw !== "object" || Array.isArray(raw))
58
+ return out;
59
+ for (const [k, v] of Object.entries(raw)) {
60
+ if (typeof v === "string" && v !== "")
61
+ out.set(k, v);
62
+ }
63
+ return out;
64
+ }
65
+ //# sourceMappingURL=feature-grounding-aliases-loader.js.map
@@ -0,0 +1,31 @@
1
+ /**
2
+ * feature_repo zero-resolution SUMMARY line (M-ADR 0143 §4.4), split against
3
+ * the final feature_repo table.
4
+ *
5
+ * A builder (feature-grounding: Evidence cell; capability-spec:
6
+ * Implementation cell) fires one per-row finding for every feature row whose
7
+ * cell is non-empty but resolves to zero repos. That measures the CELL. The
8
+ * feature may still be anchored by another surface — feature-tree
9
+ * `## Feature Services`, feature-spec, or the feature_service ⋈
10
+ * service_repo derivation — and only the build, holding the final
11
+ * feature_repo, can say so. The summary therefore states both numbers:
12
+ * how many rows fired, and how many of those fired on features that no
13
+ * surface anchors.
14
+ *
15
+ * Returns null when nothing fired: an absent line is the designed shape
16
+ * for zero (the per-row lines are absent too), and this keeps the finding
17
+ * surface identical to pre-1.20.2 in that case.
18
+ */
19
+ export function zeroResolutionSummary(cellLabel, evidenceRows, firedFeatureIds, anchoredFeatures) {
20
+ if (firedFeatureIds.length === 0)
21
+ return null;
22
+ const distinctFired = new Set(firedFeatureIds);
23
+ const unanchoredFeatures = new Set([...distinctFired].filter((fid) => !anchoredFeatures.has(fid)));
24
+ const unanchoredRows = firedFeatureIds.filter((fid) => unanchoredFeatures.has(fid)).length;
25
+ const anchoredElsewhere = distinctFired.size - unanchoredFeatures.size;
26
+ return (`feature_repo: ${firedFeatureIds.length} of ${evidenceRows} feature rows with a non-empty ${cellLabel} cell resolved to zero repos, ` +
27
+ `of which ${unanchoredRows} on ${unanchoredFeatures.size} feature(s) with no anchor from ANY surface in the final feature_repo ` +
28
+ `(${anchoredElsewhere} of the ${distinctFired.size} named features are anchored by another surface — those rows are cell quality, not coverage) ` +
29
+ `(member tier; evidence→repo resolution coverage only — a resolved row is not thereby correct; M-ADR 0143 §4.4)`);
30
+ }
31
+ //# sourceMappingURL=feature-repo-zero-resolution-summary.js.map