@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,435 @@
1
+ /**
2
+ * Loader for the per-SDD `.sdd/sdd-manifest.yaml` per the schema at
3
+ * docs/method/methods/sdd-manifest-schema.yaml in sdd-method.
4
+ *
5
+ * Validates schema_version against SCHEMA_COMPAT and the
6
+ * profile↔category invariant from product-category-taxonomy.md.
7
+ */
8
+ import { readFile } from "node:fs/promises";
9
+ import { join } from "node:path";
10
+ import { parse as parseYaml } from "yaml";
11
+ import { z } from "zod";
12
+ import { SCHEMA_COMPAT, formatSchemaVersionMismatch } from "./schema-compat.js";
13
+ export const MANIFEST_REL_PATH = ".sdd/sdd-manifest.yaml";
14
+ export const PROFILES = [
15
+ "platform-profile",
16
+ "application-product",
17
+ "integration-product",
18
+ "support-repo",
19
+ "organisation-profile",
20
+ ];
21
+ export const CATEGORIES = [
22
+ "platform-root",
23
+ "platform-subsystem",
24
+ "application-product",
25
+ "integration-provider",
26
+ "integration-umbrella",
27
+ "support-repo",
28
+ "organisation-root",
29
+ ];
30
+ const PROFILE_CATEGORY_INVARIANT = {
31
+ "platform-profile": ["platform-root", "platform-subsystem"],
32
+ "application-product": ["application-product"],
33
+ "integration-product": ["integration-provider", "integration-umbrella"],
34
+ "support-repo": ["support-repo"],
35
+ "organisation-profile": ["organisation-root"],
36
+ };
37
+ // M-ADR 0193 — the data-plane axis. `pattern` and `locality` are CLOSED
38
+ // method-tier enums; `transport`/`target`/`runtime`/`notes` are free
39
+ // (adopter-tier). Malformed bindings FAIL at load, consistent with every other
40
+ // fixed manifest enum.
41
+ export const DATA_PLANE_PATTERNS = [
42
+ // Inbound (data-sourcing) sub-set.
43
+ "retrieve",
44
+ "own-store",
45
+ "stream-ingest",
46
+ "project",
47
+ // Output-side sub-set (M-ADR 0193 §4.7): how the service produces or exposes
48
+ // data. `generate` produces a scheduled/periodic artefact (compute-bearing,
49
+ // carries a runtime); `publish` exposes an outward contract/API (not
50
+ // compute-bearing).
51
+ "generate",
52
+ "publish",
53
+ ];
54
+ // Inbound data-sourcing patterns MUST name where the other end sits. `own-store`
55
+ // is a self-binding (no other end); the output-side patterns (`generate`/
56
+ // `publish`) MAY carry a locality but do not require one (M-ADR 0193 §4.7).
57
+ const LOCALITY_REQUIRED_PATTERNS = new Set([
58
+ "retrieve",
59
+ "stream-ingest",
60
+ "project",
61
+ ]);
62
+ export const DATA_PLANE_LOCALITIES = [
63
+ "internal-platform",
64
+ "internal-domain",
65
+ "external-vendor",
66
+ ];
67
+ // The OUTPUT-SIDE sub-set (M-ADR 0193 §4.7). This is the single method-tier
68
+ // direction mapping: every consumer that needs to branch on direction reads it
69
+ // from here rather than re-deriving the pattern split, so the sub-set the ADR
70
+ // declares has exactly one definition in the codebase.
71
+ const OUTPUT_SIDE_PATTERNS = new Set(["generate", "publish"]);
72
+ /**
73
+ * Direction of a data-plane pattern. DERIVED, never authored — `direction` is not
74
+ * a manifest field, so it cannot disagree with `pattern`. Three §4.7 rules branch
75
+ * on it: platform-dep reconciliation is inbound-only, the archetype-default WARN
76
+ * compares like with like, and the M-ADR 0187 convergence reasoning differs by
77
+ * direction.
78
+ */
79
+ export function dataPlaneDirection(pattern) {
80
+ return OUTPUT_SIDE_PATTERNS.has(pattern) ? "output" : "inbound";
81
+ }
82
+ /** True for the output-side sub-set (`generate`/`publish`). */
83
+ export function isOutputSidePattern(pattern) {
84
+ return OUTPUT_SIDE_PATTERNS.has(pattern);
85
+ }
86
+ // Patterns that carry a backend-compute role and therefore a runtime choice
87
+ // (M-ADR 0193 §4.1, extended by §4.7). `stream-ingest`/`project` (inbound
88
+ // compute) and `generate` (scheduled-generation) carry a runtime; `retrieve`
89
+ // (BFF/client), `own-store` (private-api), and `publish` (an exposed contract,
90
+ // not compute in the sourcing sense) do not, so a `runtime` on them is malformed.
91
+ const COMPUTE_BEARING_PATTERNS = new Set([
92
+ "stream-ingest",
93
+ "project",
94
+ "generate",
95
+ ]);
96
+ const DataPlaneBindingSchema = z
97
+ .object({
98
+ pattern: z.enum(DATA_PLANE_PATTERNS),
99
+ locality: z.enum(DATA_PLANE_LOCALITIES).optional(),
100
+ transport: z.string().optional(),
101
+ runtime: z.string().optional(),
102
+ target: z.string().optional(),
103
+ notes: z.string().optional(),
104
+ })
105
+ .refine((b) => !LOCALITY_REQUIRED_PATTERNS.has(b.pattern) || b.locality !== undefined, {
106
+ message: "locality is required for a data-sourcing binding (retrieve/stream-ingest/project); own-store and the output-side patterns (generate/publish) may omit it",
107
+ path: ["locality"],
108
+ })
109
+ .refine((b) => b.runtime === undefined || COMPUTE_BEARING_PATTERNS.has(b.pattern), {
110
+ message: "runtime is only valid on a compute-bearing binding (stream-ingest/project/generate); retrieve/own-store/publish carry no runtime choice",
111
+ path: ["runtime"],
112
+ });
113
+ // A declared departure from the archetype's default composition (M-ADR 0191
114
+ // §4.3 overlay). `expectation` names the archetype-default pattern the product
115
+ // knowingly diverges from; the rationale makes it sanctioned variance.
116
+ const DataPlaneDeviationSchema = z.object({
117
+ expectation: z.string(),
118
+ rationale: z.string().min(1, {
119
+ message: "a data_plane deviation must carry a non-empty rationale",
120
+ }),
121
+ });
122
+ const DataPlaneSchema = z.object({
123
+ bindings: z.array(DataPlaneBindingSchema),
124
+ deviations: z.array(DataPlaneDeviationSchema).optional(),
125
+ });
126
+ // Conformance coverage (M-ADR 0191 §4.3, second overlay element — WS4). An
127
+ // application-product asserts which of its archetype's conformance expectations
128
+ // its own conformance suite exercises (`coverage`), and declares a rationale for
129
+ // any expected tag it deliberately does not cover (`deviations`). The method
130
+ // gates the DECLARATION — it does not run the adopter's conformance tests.
131
+ const ConformanceSchema = z.object({
132
+ coverage: z.array(z.string()).optional(),
133
+ deviations: z
134
+ .array(z.object({
135
+ tag: z.string(),
136
+ rationale: z.string().min(1, {
137
+ message: "a conformance deviation must carry a non-empty rationale",
138
+ }),
139
+ }))
140
+ .optional(),
141
+ });
142
+ /**
143
+ * Owner-declared product lifecycle. Exported as a runtime array, not just a type,
144
+ * so the canonical-conformance test can compare the CANONICAL vocabulary against
145
+ * the IMPLEMENTATION rather than against a list duplicated in the test — a
146
+ * hardcoded expectation cannot detect the implementation drifting.
147
+ *
148
+ * `planned` = real and intended but does not exist yet (sdd-manifest-schema
149
+ * 1.9.0). Absent still reads as `active`.
150
+ */
151
+ export const LIFECYCLES = ["planned", "active", "deprecated"];
152
+ const ManifestSchema = z.object({
153
+ schema_version: z.string(),
154
+ sdd_id: z.string().regex(/^[a-z][a-z0-9-]*$/, {
155
+ message: "sdd_id must match /^[a-z][a-z0-9-]*$/",
156
+ }),
157
+ profile: z.enum(PROFILES),
158
+ category: z.enum(CATEGORIES),
159
+ domain_id: z
160
+ .string()
161
+ .regex(/^[a-z][a-z0-9-]*$/)
162
+ .optional(),
163
+ ecosystem_root: z.string().optional(),
164
+ // M-ADR 0208 §4.3: the platform this member is contained by. Canonical name;
165
+ // `ecosystem_root` above is its deprecated alias (schema 1.10.0, removed at 2.0.0).
166
+ platform_sdd_id: z.string().optional(),
167
+ // M-ADR 0056 (amended 2026-09-29, manifest schema 1.11.0): the SDD whose
168
+ // feature-tree.md holds this SDD's runway classifications, when it has none.
169
+ feature_tree_anchor: z
170
+ .string()
171
+ .regex(/^[a-z][a-z0-9-]*$/)
172
+ .optional(),
173
+ aggregation_manifest_path: z.string().optional(),
174
+ description: z.string().optional(),
175
+ // Optional canonical C4 system id. Defaults to `{sdd_id}-system`. Set this
176
+ // when the system was already catalogued under an established id elsewhere
177
+ // (e.g. a reverse-extracted system pulled into its own SDD) so the id stays
178
+ // stable across SDDs and aggregation.
179
+ c4_system_id: z
180
+ .string()
181
+ .regex(/^[a-z][a-z0-9-]*$/, {
182
+ message: "c4_system_id must match /^[a-z][a-z0-9-]*$/",
183
+ })
184
+ .optional(),
185
+ // Owner-declared product lifecycle (sdd-manifest-schema 1.1.0). Absent
186
+ // means "active". Unknown values are a hard load failure, consistent with
187
+ // every other manifest enum — a typo'd lifecycle silently defaulting to
188
+ // active would recreate the exact false-active bug the field exists to fix.
189
+ // `planned` (sdd-manifest-schema 1.9.0): the product is real and intended but
190
+ // does not exist yet — the normal OPENING state of a forward-authored product,
191
+ // where the SDD is written before the code. The absent-means-"active" default
192
+ // was the one state a pre-realisation estate cannot be in, so it silently
193
+ // asserted that every unbuilt product was running. A fact about the PRODUCT,
194
+ // deliberately not about the document (that is `status`).
195
+ lifecycle: z.enum(LIFECYCLES).optional(),
196
+ // Application-product surface trait (M-ADR 0167, sdd-manifest-schema 1.2.0).
197
+ // Fixed enum: ui (human-facing, default) | api (headless). Unknown values
198
+ // are a hard load failure like every other fixed manifest enum. Ignored off
199
+ // application-product (cleared with a convention finding in the products
200
+ // builder). Absent reads as "ui".
201
+ surface: z.enum(["ui", "api"]).optional(),
202
+ // Application-product packaging trait (M-ADR 0175, sdd-manifest-schema
203
+ // 1.7.0). Fixed enum, deliberately narrow: `vendor-saas` marks a
204
+ // packaged-SaaS product (vendor-operated runtime, vendor-versioned base);
205
+ // absent reads as self-operated, the unmarked norm. Unknown values are a hard
206
+ // load failure like every other fixed manifest enum. Ignored off
207
+ // application-product (cleared with a convention finding in the products
208
+ // builder). Per M-ADR 0175 §4 no builder may branch on this beyond
209
+ // evidence/deployment handling.
210
+ packaging: z.enum(["vendor-saas"]).optional(),
211
+ // Reference-exemplar trait (sdd-manifest-schema 1.8.0). PROFILE-AGNOSTIC,
212
+ // unlike the three traits it sits between — a demonstration
213
+ // application-product is as conceivable as the integration-products the first
214
+ // cohort came from — so the products builder must NOT clear it off
215
+ // application-product. Absent reads as a real SDD, the unmarked norm, with no
216
+ // finding. Only `true` is meaningful; `false` is redundant but accepted.
217
+ exemplar: z.boolean().optional(),
218
+ // Shared-library trait (sdd-manifest-schema 1.12.0, M-ADR 0217 §4.4). Profile-agnostic,
219
+ // like `exemplar`; absent is the unmarked norm, no finding.
220
+ shared_library: z.boolean().optional(),
221
+ // Application-product archetype trait (M-ADR 0191, sdd-manifest-schema
222
+ // 1.2.0). RESOLVED enum, not fixed: the loader validates only the slug
223
+ // shape here. Whether the value resolves against the active vocabulary
224
+ // (method ∪ RA-bundle ∪ adopter) is checked in the build step, which has
225
+ // the bundle cache. Absent reads as "unclassified" (with a finding). Named
226
+ // application_archetype to stay distinct from the ADR 0173 domain archetype
227
+ // and the ADR 0169 openui archetype.
228
+ application_archetype: z
229
+ .string()
230
+ .regex(/^[a-z][a-z0-9-]*$/, {
231
+ message: "application_archetype must match /^[a-z][a-z0-9-]*$/",
232
+ })
233
+ .optional(),
234
+ deprecated_date: z
235
+ .string()
236
+ .regex(/^\d{4}-\d{2}-\d{2}$/, {
237
+ message: "deprecated_date must be a YYYY-MM-DD date",
238
+ })
239
+ .optional(),
240
+ successor_sdd_id: z
241
+ .string()
242
+ .regex(/^[a-z][a-z0-9-]*$/, {
243
+ message: "successor_sdd_id must match /^[a-z][a-z0-9-]*$/ (sdd_id vocabulary)",
244
+ })
245
+ .optional(),
246
+ // Application-product data-plane axis (M-ADR 0193, sdd-manifest-schema 1.3.0);
247
+ // `data_plane.deviations` added in 1.4.0 (M-ADR 0191 §4.3 overlay). A
248
+ // composition of typed bindings (malformed bindings FAIL at load) plus
249
+ // optional declared deviations from the archetype default, each carrying a
250
+ // rationale so the mismatch is sanctioned variance, not silent drift.
251
+ data_plane: DataPlaneSchema.optional(),
252
+ // Conformance coverage declaration (M-ADR 0191 §4.3 WS4, sdd-manifest-schema
253
+ // 1.5.0). Which archetype conformance expectations the product's suite
254
+ // exercises, plus rationales for any it deliberately does not.
255
+ conformance: ConformanceSchema.optional(),
256
+ });
257
+ /** What `products.packaging` carries when the manifest declares nothing. */
258
+ export const PACKAGING_DEFAULT = "self-operated";
259
+ /**
260
+ * Read and validate the `.sdd/sdd-manifest.yaml` at the given SDD repo root.
261
+ *
262
+ * Throws on:
263
+ * - file missing
264
+ * - YAML parse error
265
+ * - shape violation (missing fields, bad enum value, bad slug pattern)
266
+ * - profile↔category invariant violation
267
+ * - schema_version outside SCHEMA_COMPAT.sdd_manifest range
268
+ */
269
+ /**
270
+ * Top-level keys this build understands. Derived from the zod shape so it cannot
271
+ * drift from what is actually parsed — a hand-maintained list would go stale and
272
+ * start reporting known fields as unknown, which is worse than saying nothing.
273
+ */
274
+ const KNOWN_MANIFEST_KEYS = new Set(Object.keys(ManifestSchema.shape));
275
+ /**
276
+ * Top-level keys present in the file that this build does not know.
277
+ *
278
+ * Deliberately TOP-LEVEL only. Nested blocks (`data_plane`, `conformance`) are
279
+ * validated by their own object schemas and strip their own unknowns; walking
280
+ * them would need a per-schema recursion this does not yet warrant. Stated so the
281
+ * limit is known rather than assumed — an unknown key nested inside `data_plane`
282
+ * is still silently dropped today.
283
+ */
284
+ function findUnknownTopLevelKeys(raw) {
285
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw))
286
+ return [];
287
+ return Object.keys(raw)
288
+ .filter((k) => !KNOWN_MANIFEST_KEYS.has(k))
289
+ .sort();
290
+ }
291
+ export async function loadSddManifest(sddPath, options) {
292
+ const sourcePath = join(sddPath, MANIFEST_REL_PATH);
293
+ let text;
294
+ try {
295
+ text = await readFile(sourcePath, "utf-8");
296
+ }
297
+ catch (err) {
298
+ const code = err.code;
299
+ if (code === "ENOENT") {
300
+ throw new Error(`SDD manifest not found at ${sourcePath}\n\n` +
301
+ `Every SDD repo MUST declare an .sdd/sdd-manifest.yaml per ADR 0134 §4.6.\n` +
302
+ `Schema: docs/method/methods/sdd-manifest-schema.yaml in sdd-method.\n` +
303
+ `Bootstrap: \`sdd-cli init\` (when migrated to support manifest authoring).`);
304
+ }
305
+ throw err;
306
+ }
307
+ let raw;
308
+ try {
309
+ raw = parseYaml(text);
310
+ }
311
+ catch (err) {
312
+ throw new Error(`failed to parse YAML at ${sourcePath}: ${err.message}`);
313
+ }
314
+ // Unknown top-level keys are REPORTED, not silently dropped and not fatal.
315
+ //
316
+ // zod's default is `strip`, so a key this build does not know vanished without a
317
+ // trace: an adopter following a schema that documents a field their sdd-cli
318
+ // predates got a clean validation and a silent no-op. Not hypothetical —
319
+ // M-ADR 0175's own version history records exactly this happening to
320
+ // `packaging`, where the ADR instructed adopters to declare a field the CLI had
321
+ // never implemented.
322
+ //
323
+ // `.strict()` is the WRONG fix: it turns the same situation into a hard failure,
324
+ // so an older CLI could not build an SDD whose manifest declares anything newer
325
+ // than itself. Newer baselines ship newer schemas by design, so that inverts the
326
+ // problem instead of solving it. Reporting keeps the declaration visible while
327
+ // leaving the build working.
328
+ const unknownKeys = findUnknownTopLevelKeys(raw);
329
+ const parsed = ManifestSchema.safeParse(raw);
330
+ if (!parsed.success) {
331
+ const issues = parsed.error.issues
332
+ .map((i) => ` - ${i.path.join(".") || "<root>"}: ${i.message}`)
333
+ .join("\n");
334
+ throw new Error(`invalid SDD manifest at ${sourcePath}:\n${issues}`);
335
+ }
336
+ // Compat range check.
337
+ if (!isInRange(parsed.data.schema_version, SCHEMA_COMPAT.sdd_manifest)) {
338
+ throw new Error(formatSchemaVersionMismatch({
339
+ schema: "sdd_manifest",
340
+ readVersion: parsed.data.schema_version,
341
+ cliVersion: options.cliVersion,
342
+ sourcePath,
343
+ }));
344
+ }
345
+ // Profile ↔ category invariant.
346
+ const permitted = PROFILE_CATEGORY_INVARIANT[parsed.data.profile];
347
+ if (!permitted.includes(parsed.data.category)) {
348
+ throw new Error(`invalid profile/category combination at ${sourcePath}:\n` +
349
+ ` profile=${parsed.data.profile}, category=${parsed.data.category}\n` +
350
+ ` permitted categories under profile=${parsed.data.profile}: ${permitted.join(", ")}\n` +
351
+ ` per docs/method/methods/product-category-taxonomy.md`);
352
+ }
353
+ return {
354
+ schemaVersion: parsed.data.schema_version,
355
+ sddId: parsed.data.sdd_id,
356
+ profile: parsed.data.profile,
357
+ category: parsed.data.category,
358
+ ...(parsed.data.domain_id !== undefined ? { domainId: parsed.data.domain_id } : {}),
359
+ ...(parsed.data.c4_system_id !== undefined ? { c4SystemId: parsed.data.c4_system_id } : {}),
360
+ ...(parsed.data.ecosystem_root !== undefined
361
+ ? { ecosystemRoot: parsed.data.ecosystem_root }
362
+ : {}),
363
+ ...((parsed.data.platform_sdd_id ?? parsed.data.ecosystem_root) !== undefined
364
+ ? {
365
+ platformSddId: (parsed.data.platform_sdd_id ?? parsed.data.ecosystem_root),
366
+ platformFieldDeprecated: parsed.data.platform_sdd_id === undefined && parsed.data.ecosystem_root !== undefined,
367
+ }
368
+ : {}),
369
+ ...(parsed.data.aggregation_manifest_path !== undefined
370
+ ? { aggregationManifestPath: parsed.data.aggregation_manifest_path }
371
+ : {}),
372
+ ...(parsed.data.feature_tree_anchor !== undefined
373
+ ? { featureTreeAnchor: parsed.data.feature_tree_anchor }
374
+ : {}),
375
+ ...(parsed.data.description !== undefined ? { description: parsed.data.description } : {}),
376
+ ...(parsed.data.lifecycle !== undefined ? { lifecycle: parsed.data.lifecycle } : {}),
377
+ ...(parsed.data.surface !== undefined ? { surface: parsed.data.surface } : {}),
378
+ ...(parsed.data.packaging !== undefined ? { packaging: parsed.data.packaging } : {}),
379
+ ...(parsed.data.exemplar !== undefined ? { exemplar: parsed.data.exemplar } : {}),
380
+ ...(parsed.data.shared_library !== undefined ? { sharedLibrary: parsed.data.shared_library } : {}),
381
+ ...(parsed.data.application_archetype !== undefined
382
+ ? { applicationArchetype: parsed.data.application_archetype }
383
+ : {}),
384
+ ...(parsed.data.deprecated_date !== undefined
385
+ ? { deprecatedDate: parsed.data.deprecated_date }
386
+ : {}),
387
+ ...(parsed.data.successor_sdd_id !== undefined
388
+ ? { successorSddId: parsed.data.successor_sdd_id }
389
+ : {}),
390
+ ...(parsed.data.data_plane !== undefined ? { dataPlane: parsed.data.data_plane } : {}),
391
+ ...(parsed.data.conformance !== undefined ? { conformance: parsed.data.conformance } : {}),
392
+ ...(unknownKeys.length > 0 ? { unknownKeys } : {}),
393
+ sourcePath,
394
+ };
395
+ }
396
+ /**
397
+ * Caret-prefixed semver range check. Accepts X in `^1.2.3` if X.major === 1
398
+ * and X >= 1.2.3.
399
+ *
400
+ * Restricted to the `^X.Y.Z` shape — that's the only form SCHEMA_COMPAT uses.
401
+ */
402
+ export function isInRange(version, range) {
403
+ if (!range.startsWith("^")) {
404
+ throw new Error(`unsupported range syntax: ${range}`);
405
+ }
406
+ const v = parseSemver(version);
407
+ const r = parseSemver(range.slice(1));
408
+ if (v === null || r === null)
409
+ return false;
410
+ if (v.major !== r.major)
411
+ return false;
412
+ if (v.major === 0) {
413
+ // Per semver, 0.x is unstable; ^0.x.y matches only 0.x.y itself.
414
+ return v.minor === r.minor && v.patch >= r.patch;
415
+ }
416
+ if (v.minor < r.minor)
417
+ return false;
418
+ if (v.minor === r.minor && v.patch < r.patch)
419
+ return false;
420
+ return true;
421
+ }
422
+ function parseSemver(s) {
423
+ const m = /^(\d+)\.(\d+)\.(\d+)/.exec(s);
424
+ if (!m)
425
+ return null;
426
+ const [, maj, min, pat] = m;
427
+ if (maj === undefined || min === undefined || pat === undefined)
428
+ return null;
429
+ return {
430
+ major: parseInt(maj, 10),
431
+ minor: parseInt(min, 10),
432
+ patch: parseInt(pat, 10),
433
+ };
434
+ }
435
+ //# sourceMappingURL=manifest-loader.js.map
@@ -0,0 +1,7 @@
1
+ // Catalogues whose key is platform-scoped at organisation tier (M-ADR 0208 §4.4): two
2
+ // platforms may each own a row of one id, so the key is (platform_sdd_id, id). Its own
3
+ // module so a read-only verb (catalogue validate, reached by verify-cli) can use it
4
+ // without importing the aggregator.
5
+ // data_items (M-ADR 0216): each platform owns its vocabulary, as it owns its domains.
6
+ export const PLATFORM_SCOPED_AT_ORGANISATION = ["domains", "data_items"];
7
+ //# sourceMappingURL=platform-scope.js.map
@@ -0,0 +1,87 @@
1
+ const norm = (s) => s.toLowerCase().replace(/^ext-/, "").replace(/[^a-z0-9]/g, "");
2
+ const slug = (s) => s.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
3
+ /** Does a services[] id name a declared C4 container — by id, by the system-anchored id, or by the slug of its name? */
4
+ export function matchesContainer(sid, containers) {
5
+ const target = sid.toLowerCase();
6
+ return containers.some((c) => {
7
+ const id = c.id.toLowerCase();
8
+ if (id === target || slug(c.name) === target)
9
+ return true;
10
+ const sys = (c.system_id ?? "").toLowerCase();
11
+ return sys !== "" && id === `${sys}-${target}`;
12
+ });
13
+ }
14
+ /**
15
+ * A near-miss is an external-system id that the unresolved id contains or is contained by after
16
+ * normalisation (a separator changed, a prefix or suffix added — the three measured shapes), with a
17
+ * length floor so a short common token does not fire on everything. It is NAMED, never applied.
18
+ */
19
+ export function nearMiss(id, externalIds) {
20
+ const n = norm(id);
21
+ let best;
22
+ for (const ext of externalIds) {
23
+ const e = norm(ext);
24
+ if (ext === id || e === "" || Math.min(n.length, e.length) < 6)
25
+ continue;
26
+ if (n === e || n.includes(e) || e.includes(n)) {
27
+ if (best === undefined || e.length > norm(best).length)
28
+ best = ext; // prefer the most specific
29
+ }
30
+ }
31
+ return best;
32
+ }
33
+ // @rule population-capability-service
34
+ export function checkCapabilityServicePopulation(inputs) {
35
+ let declared = 0, unresolved = 0, deduplicated = 0, capabilityUnknown = 0, crossSdd = 0;
36
+ let externalSystem = 0, declaredContainer = 0, nowhere = 0, expectedRows = 0;
37
+ const seen = new Set();
38
+ for (const m of inputs.manifests) {
39
+ const capId = m.capability.id;
40
+ for (const entry of m.traceability.services ?? []) {
41
+ declared++;
42
+ if (!inputs.knownCapabilityIds.has(capId)) {
43
+ capabilityUnknown++;
44
+ continue;
45
+ }
46
+ const sid = entry.id;
47
+ // Resolution order (M-ADR 0211 §4.3): owner_sdd FIRST — a declared cross-SDD
48
+ // reference is resolved by declaration, not by the local services catalogue.
49
+ const resolved = entry.owner_sdd !== undefined || inputs.knownServiceIds.has(sid);
50
+ if (entry.owner_sdd !== undefined)
51
+ crossSdd++;
52
+ if (!resolved) {
53
+ unresolved++;
54
+ if (inputs.knownExternalSystemIds.has(sid)) {
55
+ externalSystem++;
56
+ }
57
+ else if (matchesContainer(sid, inputs.knownContainers)) {
58
+ declaredContainer++;
59
+ inputs.findings.add("convention", `capability_service: ${capId} services[] id "${sid}" resolves as a DECLARED C4 CONTAINER, not a service — capability_service has no locus for it; the id is correct and there is nothing to fix here (M-ADR 0211 §4.3, class 2: a dependency on a container that is not a service, filed as its own item)`);
60
+ }
61
+ else {
62
+ nowhere++;
63
+ const cand = nearMiss(sid, inputs.knownExternalSystemIds);
64
+ if (cand !== undefined) {
65
+ inputs.findings.add("convention", `capability_service: ${capId} services[] id "${sid}" resolves nowhere; nearest external-system id is "${cand}" — a CANDIDATE, not applied (M-ADR 0211 §4.3, class 3)`);
66
+ }
67
+ }
68
+ continue;
69
+ }
70
+ const key = `${capId}__${sid}__${(entry.role ?? "") !== "" ? entry.role : "related"}`;
71
+ if (seen.has(key)) {
72
+ deduplicated++;
73
+ continue;
74
+ }
75
+ seen.add(key);
76
+ expectedRows++;
77
+ }
78
+ }
79
+ const rows = inputs.capabilityServiceRows.length;
80
+ const balances = declared === rows + unresolved + deduplicated + capabilityUnknown;
81
+ inputs.findings.add("metric", `capability_service: declared=${declared} rows=${rows} unresolved=${unresolved} deduplicated=${deduplicated} capability-unknown=${capabilityUnknown} (cross-SDD via owner_sdd=${crossSdd}); unresolved by class: external-system=${externalSystem} declared-container=${declaredContainer} nowhere=${nowhere}`);
82
+ if (!balances) {
83
+ inputs.findings.add("coverage", `capability_service: the population does not balance — declared ${declared} ≠ rows ${rows} + unresolved ${unresolved} + deduplicated ${deduplicated} + capability-unknown ${capabilityUnknown} (expected rows from declarations: ${expectedRows}). Two instruments disagree; adjudicate, do not smooth (M-ADR 0211 §4.3)`);
84
+ }
85
+ return { declared, rows, unresolved, deduplicated, capabilityUnknown, crossSdd, externalSystem, declaredContainer, nowhere, balances };
86
+ }
87
+ //# sourceMappingURL=population-checks.js.map
@@ -0,0 +1,66 @@
1
+ // Relationship semantics and catalogue kinds, read from the vendored
2
+ // rubric-coverage-map.yaml — READ-ONLY. Split out of `graph-run.ts`, which also
3
+ // holds `runCatalogueGraph`, the verb that writes `<catalogues-dir>-graph.jsonl`
4
+ // by default. `catalogue impact` needs these loaders and nothing that writes,
5
+ // and the verify-only CLI ships `impact` (M-ADR 0212 §4.3b): while they lived in
6
+ // graph-run the writer came along with them, and the package build caught it.
7
+ // `graph-run.ts` re-exports everything here, so existing imports are unchanged.
8
+ import { readFile } from "node:fs/promises";
9
+ import { fileURLToPath } from "node:url";
10
+ import { join } from "node:path";
11
+ import { parse as parseYaml } from "yaml";
12
+ /**
13
+ * Vendored copy of sdd-method's orchestration/rubric-coverage-map.yaml —
14
+ * byte-identical to the canonical (rubric-coverage-map-drift.test.ts guards
15
+ * it, same one-way contract as canonical-schema.yaml). The build copies it
16
+ * alongside the compiled module via scripts/copy-yaml-assets.mjs.
17
+ */
18
+ export const VENDORED_COVERAGE_MAP_PATH = join(fileURLToPath(new URL(".", import.meta.url)), "rubric-coverage-map.yaml");
19
+ function toDecl(where, e) {
20
+ if (e.class === undefined) {
21
+ throw new Error(`rubric-coverage-map: ${where} has no class declaration (M-ADR 0206)`);
22
+ }
23
+ if ((e.impact_flow === undefined) === (e.impact_flow_by === undefined)) {
24
+ throw new Error(`rubric-coverage-map: ${where} must declare exactly one of impact_flow / impact_flow_by`);
25
+ }
26
+ return {
27
+ class: e.class,
28
+ ...(e.impact_flow !== undefined
29
+ ? { impactFlow: e.impact_flow }
30
+ : {}),
31
+ ...(e.impact_flow_by !== undefined
32
+ ? {
33
+ impactFlowBy: {
34
+ column: e.impact_flow_by.column,
35
+ values: e.impact_flow_by.values,
36
+ },
37
+ }
38
+ : {}),
39
+ };
40
+ }
41
+ export async function loadRelationshipSemantics(mapPath = VENDORED_COVERAGE_MAP_PATH) {
42
+ const raw = parseYaml(await readFile(mapPath, "utf-8"));
43
+ const kinds = new Map();
44
+ const edges = new Map();
45
+ const entityFks = new Map();
46
+ for (const [name, entry] of Object.entries(raw.coverage)) {
47
+ kinds.set(name, entry.kind);
48
+ if (entry.kind === "edge" || entry.kind === "annotation") {
49
+ // an annotation classifies at top level exactly as an edge does (schema 3.9.0)
50
+ edges.set(name, toDecl(`edge ${name}`, entry));
51
+ }
52
+ for (const [col, decl] of Object.entries(entry.relationships ?? {})) {
53
+ entityFks.set(`${name}.${col}`, toDecl(`entity FK ${name}.${col}`, decl));
54
+ }
55
+ }
56
+ return { kinds, edges, entityFks };
57
+ }
58
+ export async function loadCatalogueKinds(mapPath = VENDORED_COVERAGE_MAP_PATH) {
59
+ const raw = parseYaml(await readFile(mapPath, "utf-8"));
60
+ const kinds = new Map();
61
+ for (const [name, entry] of Object.entries(raw.coverage)) {
62
+ kinds.set(name, entry.kind);
63
+ }
64
+ return kinds;
65
+ }
66
+ //# sourceMappingURL=relationship-semantics.js.map