@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,552 @@
1
+ /**
2
+ * Coverage renderers — M-ADR 0205 §4.4 (A4 CS-2).
3
+ *
4
+ * Each function renders a predicate's structured result into the EXACT
5
+ * stdout lines its reference shell script prints. The text is a
6
+ * compatibility contract: adopters' findings baselines hold these literal
7
+ * lines, so every string here is copied from the reference, never
8
+ * paraphrased. Line ORDER within a predicate follows the reference's
9
+ * print order (metrics/controls/findings blocks); WITHIN a block the
10
+ * engine's total ORDER BY governs, which may differ from the reference's
11
+ * file-row order — baselines compare by membership, and the line-parity
12
+ * driver compares sorted multisets.
13
+ *
14
+ * Percentages use Python's %.1f semantics (round-half-even on the scaled
15
+ * value), because the references print through Python f-strings.
16
+ */
17
+ import { renderFindingLine } from "./finding-identity.js";
18
+ /** Python `%.1f` — half to even at an exact scaled .5, else nearest. */
19
+ export function fmt1f(x) {
20
+ const scaled = x * 10;
21
+ const floor = Math.floor(scaled);
22
+ const diff = scaled - floor;
23
+ let n;
24
+ if (diff > 0.5)
25
+ n = floor + 1;
26
+ else if (diff < 0.5)
27
+ n = floor;
28
+ else
29
+ n = floor % 2 === 0 ? floor : floor + 1;
30
+ return (n / 10).toFixed(1);
31
+ }
32
+ export function renderRepoCapability(findings, metrics) {
33
+ const lines = [];
34
+ // These four templates are the measured reason identity is EMITTED rather
35
+ // than recovered: they embed the subject in prose, and between them they
36
+ // account for all 243 baseline lines across 62 repos that no general parse
37
+ // can key. The subject was structurally available at every one of them all
38
+ // along — `f.subjectId` — it simply never reached the line.
39
+ for (const f of findings) {
40
+ const subject = `repo:${f.subjectId}`;
41
+ if (f.classification === "conflict") {
42
+ lines.push(renderFindingLine({
43
+ category: "divergence",
44
+ subject,
45
+ summary: `repo ${f.subjectId} declares capability_exemption ` +
46
+ `(${f.exemptionReason ?? ""}) but also has a repo_capability edge — an exempt repository ` +
47
+ `must not claim a capability (per M-ADR 0188 §4.1)`,
48
+ }));
49
+ }
50
+ else if (f.classification === "shape_a") {
51
+ const names = (f.unclaimedServices ?? []).join(", ") +
52
+ ((f.unclaimedCount ?? 0) > 3 ? " …" : "");
53
+ lines.push(renderFindingLine({
54
+ category: "orphan",
55
+ subject,
56
+ summary: `repo ${f.subjectId} resolves to no capability in this SDD's ` +
57
+ `own catalogues; its service(s) [${names}] have no capability_service row ` +
58
+ `here — check the aggregate's capability_service.csv for this service_id ` +
59
+ `before assuming the edge is missing (per M-ADR 0188 §6.2b)`,
60
+ }));
61
+ }
62
+ else if (f.classification === "claimed_unresolved") {
63
+ lines.push(renderFindingLine({
64
+ category: "orphan",
65
+ subject,
66
+ summary: `repo ${f.subjectId} resolves to no capability although its ` +
67
+ `service(s) are claimed — check services.primary_repo_id is populated ` +
68
+ `(per M-ADR 0157 §4.3)`,
69
+ }));
70
+ }
71
+ else {
72
+ lines.push(renderFindingLine({
73
+ category: "orphan",
74
+ subject,
75
+ summary: `repo ${f.subjectId} resolves to no capability, has no service ` +
76
+ `row, and declares no capability_exemption — declare capabilities: or ` +
77
+ `capability_exemption (per M-ADR 0188 §4.1)`,
78
+ }));
79
+ }
80
+ }
81
+ if (metrics.total === 0 && findings.length === 0) {
82
+ lines.push(`[metric] repo_capability_resolution: n/a — 0 repository rows, nothing to resolve`);
83
+ }
84
+ if (metrics.total > 0) {
85
+ const resolved = metrics.byCapability + metrics.byExemption;
86
+ const pct = fmt1f((100.0 * resolved) / metrics.total);
87
+ lines.push(`[metric] repo_capability_resolution: ${resolved}/${metrics.total} (${pct}%) ` +
88
+ `— ${metrics.byCapability} by capability, ${metrics.byExemption} by exemption, ` +
89
+ `${metrics.conflicting} conflicting, ${metrics.unresolved} unresolved`);
90
+ if (metrics.unresolved > 0) {
91
+ lines.push(`[metric] repo_capability_orphan_shape: ${metrics.shapeA} with no local ` +
92
+ `capability_service row for the service (check the aggregate before ` +
93
+ `assuming the edge is missing), ${metrics.shapeB} with no service row (declare ` +
94
+ `capabilities: or capability_exemption), ${metrics.other} other`);
95
+ }
96
+ }
97
+ return lines;
98
+ }
99
+ function leverageNote(f) {
100
+ if (f.leverage === undefined)
101
+ return "";
102
+ if (f.leverage === "na")
103
+ return " [leverage: n/a — no primary_repo_id]";
104
+ if (f.leverage === "yes") {
105
+ return (` [leverage: yes — claiming this service would also resolve ` +
106
+ `repo ${f.primaryRepo}'s repo_capability shape-(a) orphan via Path X]`);
107
+ }
108
+ return ` [leverage: no — repo ${f.primaryRepo} already resolves]`;
109
+ }
110
+ export function renderServiceCapability(findings, metrics, tier) {
111
+ const lines = [];
112
+ for (const f of findings) {
113
+ if (f.classification === "conflict") {
114
+ lines.push(`[finding] divergence: service ${f.subjectId} declares service_capability_exemption ` +
115
+ `(${f.exemptionReason ?? ""}) but also has a capability_service edge — an exempt service ` +
116
+ `must not claim a capability (per M-ADR 0133 §4.2b)`);
117
+ }
118
+ else if (tier === "aggregate") {
119
+ lines.push(`[finding] orphan: service ${f.subjectId} resolves to no capability anywhere in ` +
120
+ `the merged estate and declares no exemption — M-ADR 0196 §4.1 gap class ` +
121
+ `unauthored-surface (not itself a method defect; remedy is to claim, ` +
122
+ `exempt, or accept the surface)${leverageNote(f)}`);
123
+ }
124
+ else {
125
+ lines.push(`[finding] coverage: service ${f.subjectId} has no capability_service row and no ` +
126
+ `exemption in this SDD's own catalogues — classification cannot complete ` +
127
+ `at member tier (a claim may exist in a sibling SDD's own ` +
128
+ `capability_service.csv); check the aggregate next${leverageNote(f)}`);
129
+ }
130
+ }
131
+ if (metrics.total === 0 && findings.length === 0) {
132
+ lines.push(`[metric] service_capability_resolution (tier=${tier}): n/a — 0 service rows, nothing to resolve`);
133
+ }
134
+ if (metrics.total > 0) {
135
+ const resolved = metrics.claimed + metrics.exempt;
136
+ const pct = fmt1f((100.0 * resolved) / metrics.total);
137
+ const label = tier === "aggregate" ? "orphaned" : "unresolved-locally";
138
+ lines.push(`[metric] service_capability_resolution (tier=${tier}): ${resolved}/${metrics.total} ` +
139
+ `(${pct}%) — ${metrics.claimed} claimed, ${metrics.exempt} exempt, ` +
140
+ `${metrics.conflicting} conflicting, ${metrics.unresolved} ${label}`);
141
+ if (metrics.leverage !== undefined && metrics.unresolved > 0) {
142
+ lines.push(`[metric] service_capability_leverage (tier=${tier}): ${metrics.leverage.yes} would ` +
143
+ `also resolve a repo_capability shape-(a) orphan via Path X, ${metrics.leverage.no} ` +
144
+ `would not (repo already resolves), ${metrics.leverage.na} n/a (no primary_repo_id)`);
145
+ }
146
+ }
147
+ return lines;
148
+ }
149
+ export function renderEventCoverage(res, tier) {
150
+ if (res.na) {
151
+ return [
152
+ `[metric] event_coverage (tier=${tier}): n/a — this unit holds no events.csv ` +
153
+ `rows. Both event sources (contracts/avro discovery; per-domain ` +
154
+ `event-catalog.yaml) are platform-profile surfaces, so for app/integration ` +
155
+ `units this cell classifies BY-METAMODEL (M-ADR 0196 §4.1): no event node ` +
156
+ `surface exists here. For a platform unit, this is an unauthored/undiscovered ` +
157
+ `surface instead — check contracts/avro/ and docs/domains/*/contracts/events/.`,
158
+ ];
159
+ }
160
+ const m = res.metrics;
161
+ const c = res.controls;
162
+ const label = tier === "member" ? "unresolved-locally" : "orphaned";
163
+ const lines = [
164
+ `[metric] event_coverage (tier=${tier}): ${m.covered + m.viaContract}/${m.total} covered ` +
165
+ `(${m.covered} attributed, ${m.viaContract} covered-as-contract), ` +
166
+ `${m.unresolved} ${label}`,
167
+ `[metric] capability-reach (chain readout, never a verdict): ${m.capReach}/${m.total} ` +
168
+ `events whose attributed service is claimed by a capability`,
169
+ `[metric] consumer-enumerability (M-ADR 0057 analogue, never a verdict): ` +
170
+ `${m.consumerEnum}/${m.total} events with at least one role=consumes attribution`,
171
+ ];
172
+ if (c.colUnresolving.length > 0) {
173
+ lines.push(`[control] publisher column set but UNRESOLVING against services.csv: ` +
174
+ `${c.colUnresolving.length}`);
175
+ for (const x of c.colUnresolving) {
176
+ lines.push(` - ${x.eventId} (publisher_service_id=${x.publisher})`);
177
+ }
178
+ }
179
+ if (c.colNoEdge.length > 0) {
180
+ lines.push(`[control] column-vs-edge disagreement — publisher resolves but no ` +
181
+ `publishes edge exists: ${c.colNoEdge.length}`);
182
+ for (const x of c.colNoEdge)
183
+ lines.push(` - ${x}`);
184
+ }
185
+ if (c.edgeNoCol.length > 0) {
186
+ lines.push(`[control] column-vs-edge disagreement — publishes edge exists but the ` +
187
+ `row's publisher column is empty: ${c.edgeNoCol.length}`);
188
+ for (const x of c.edgeNoCol)
189
+ lines.push(` - ${x}`);
190
+ }
191
+ for (const f of res.findings) {
192
+ if (tier === "aggregate") {
193
+ lines.push(`[finding] orphan: event ${f.subjectId} is attributed to nothing in the merged ` +
194
+ `estate — no service_event row and no resolving publisher_service_id. ` +
195
+ `Next checks, by authoring direction: forward — is a repo-config ` +
196
+ `\`events:\` block authored for its producer, or is this an avsc-only ` +
197
+ `discovery? reverse — is the event-catalog.yaml \`publisher\` field ` +
198
+ `empty, or the catalog file absent for its domain (the 0156 class)? ` +
199
+ `Do not 'declare the event' — the row already exists; what is missing ` +
200
+ `is attribution.`);
201
+ }
202
+ else {
203
+ lines.push(`[report] unresolved-locally: event ${f.subjectId} carries no local attribution. ` +
204
+ `A sibling's event-catalog consumers[] may attribute it at aggregate ` +
205
+ `tier; absence is not assertable here (M-ADR 0199 §4.3).`);
206
+ }
207
+ }
208
+ return lines;
209
+ }
210
+ export function renderContractCoverage(res, tier) {
211
+ if (res.na) {
212
+ return [
213
+ `[metric] contract_coverage (tier=${tier}): n/a — this unit holds no ` +
214
+ `contracts.csv rows, so there is no population to score. The contracts ` +
215
+ `discoverer is unconditional across profiles, so this is an unauthored ` +
216
+ `surface at the node catalogue (M-ADR 0196 §4.1), not out of scope by profile.`,
217
+ ];
218
+ }
219
+ const lines = [];
220
+ if (res.availability !== undefined) {
221
+ lines.push(`[info] availability manifest: ${res.availability.attested} attested artefact(s), ` +
222
+ `${res.availability.unmodelled.length} with no contract row (attested but not ` +
223
+ `declared-external — participation is a separate claim, M-ADR 0202)`);
224
+ for (const pp of res.availability.unmodelled)
225
+ lines.push(` - ${pp}`);
226
+ }
227
+ if (res.availabilityWarning !== undefined) {
228
+ lines.push(`[warn] availability manifest unreadable: ${res.availabilityWarning}`);
229
+ }
230
+ for (const f of res.findings) {
231
+ if (f.kind === "divergence") {
232
+ lines.push(`[finding] divergence: contract ${f.subjectId} is excluded from scoring ` +
233
+ `(${f.classification}) but also carries a live ` +
234
+ `capability_contract or service_contract edge — the exclusion asserts it ` +
235
+ `is not expected to be reachable and the edge says it is; check which is ` +
236
+ `stale`);
237
+ }
238
+ else if (tier === "aggregate") {
239
+ lines.push(`[finding] orphan: contract ${f.subjectId} is reachable from nothing in the ` +
240
+ `merged estate — no capability_contract row (no owner in the product ` +
241
+ `model) and no service_contract row (no runtime attribution). Under ` +
242
+ `M-ADR 0057 lock semantics its consumers cannot be enumerated, so it ` +
243
+ `can be neither safely changed nor safely retired. Next checks: is a ` +
244
+ `traceability.contracts entry authored but unresolved (see the ` +
245
+ `capability_contract orphan findings from the build), and does this ` +
246
+ `estate have an exposes surface at all (repo-config contracts: ` +
247
+ `blocks)? If this contract is legitimately unreachable, none of the ` +
248
+ `four scoped-out states covers it — raise it, because that case is ` +
249
+ `the trigger for opening an exemption vocabulary`);
250
+ }
251
+ else {
252
+ lines.push(`[finding] coverage: contract ${f.subjectId} has no capability_contract and no ` +
253
+ `service_contract row in this SDD's own catalogues — classification ` +
254
+ `cannot complete at member tier, because a service_contract row about ` +
255
+ `this contract may be authored in a sibling SDD (both its FKs are ` +
256
+ `external). Check the aggregate next`);
257
+ }
258
+ }
259
+ const m = res.metrics;
260
+ const resolved = m.covered + m.viaEvent + m.excluded;
261
+ const label = tier === "aggregate" ? "orphaned" : "unresolved-locally";
262
+ lines.push(`[metric] contract_coverage (tier=${tier}): ${resolved}/${m.total} ` +
263
+ `(${fmt1f((100.0 * resolved) / m.total)}%) — ${m.covered} covered, ${m.viaEvent} ` +
264
+ `covered-as-event, ${m.excluded} excluded, ${m.conflicting} conflicting, ` +
265
+ `${m.unresolved} ${label}`);
266
+ const exclusionKeys = Object.keys(m.byExclusion).sort();
267
+ if (exclusionKeys.length > 0) {
268
+ lines.push(`[metric] contract_excluded (tier=${tier}): ` +
269
+ exclusionKeys.map((k) => `${k} ${m.byExclusion[k]}`).join(", "));
270
+ }
271
+ lines.push(`[metric] contract_consumer_enumerable (tier=${tier}): ${m.consumersNamed}/${m.total} ` +
272
+ `(${fmt1f((100.0 * m.consumersNamed) / m.total)}%) carry a service_contract role=consumes ` +
273
+ `row. This is the property M-ADR 0057 lock semantics need and it is REPORTED, NOT ` +
274
+ `VERDICTED: the consumes surface is reverse-method only (an adopter alias map over ` +
275
+ `service-architecture.md), so a forward-authored estate scores zero here by ` +
276
+ `construction rather than by neglect`);
277
+ lines.push(`[metric] contract_attribution_shape (tier=${tier}): ${m.ownedOnly} owner-only, ` +
278
+ `${m.attrOnly} runtime-only, ${m.both} both — a population covered wholly by one edge ` +
279
+ `rests on one authoring surface`);
280
+ return lines;
281
+ }
282
+ // ─── the nine Phase-0 gate checks ────────────────────────────────────────
283
+ export function renderEventCatalogueCheck(gaps) {
284
+ return gaps.map((g) => `[finding] coverage: domain ${g.domain} event-catalog.yaml declares ${g.declared} events but events.csv contains ${g.emitted} rows for that domain (per ADR 0156 Phase 0)`);
285
+ }
286
+ export function renderRepoEdgesCheck(res) {
287
+ const lines = [];
288
+ if (res.repoCapabilityGap) {
289
+ const g = res.repoCapabilityGap;
290
+ lines.push(`[finding] coverage: repo_capability.csv is empty but ${g.capabilityService} capability_service rows + ${g.servicesWithRepo} services with primary_repo_id present — reverse-method derivation likely missing (per ADR 0157 §4.3, Phase 0)`);
291
+ }
292
+ if (res.repoContractForwardGap) {
293
+ const g = res.repoContractForwardGap;
294
+ lines.push(`[finding] coverage: repo_contract.csv is empty but ${g.contracts} contracts and ${g.repoConfigsWithContracts} repo-configs with contracts: blocks present — forward-method derivation likely missing (per ADR 0157 §4.2, Phase 0)`);
295
+ }
296
+ if (res.repoContractPathXGap) {
297
+ const g = res.repoContractPathXGap;
298
+ lines.push(`[finding] coverage: repo_contract.csv is empty but ${g.serviceContracts} service_contract rows + ${g.servicesWithRepo} services with primary_repo_id present — reverse-method Path X (service_contract ⋈ services.primary_repo_id) derivation likely missing (per ADR 0157 §4.2, Phase 0)`);
299
+ }
300
+ if (res.repoContractReverseGap) {
301
+ const g = res.repoContractReverseGap;
302
+ lines.push(`[finding] coverage: repo_contract.csv is empty with ${g.contracts} contracts present and no repo-config.contracts: blocks or service_contract rows — reverse-method derivation via contracts.source_repo_id likely missing (per ADR 0157 §4.2, Phase 0)`);
303
+ }
304
+ return lines;
305
+ }
306
+ export function renderGlossaryXrefCheck(finding) {
307
+ return finding
308
+ ? [
309
+ `[finding] coverage: docs/platform/reference/glossary-alignment-manifest.yaml exists but glossary_xref.csv emits 0 rows — multi-source derivation likely missing (per ADR 0158 §4.5, Phase 0)`,
310
+ ]
311
+ : [];
312
+ }
313
+ export function renderDataModelEntitiesCheck(res) {
314
+ const lines = [];
315
+ if (res.entitiesGap) {
316
+ lines.push(`[finding] coverage: ${res.entitiesGap.domainModels} domain-model files catalogued but entities.csv emits 0 rows — reverse-method entity extraction missing (per ADR 0159 Phase 0)`);
317
+ }
318
+ if (res.serviceEntityGap) {
319
+ const g = res.serviceEntityGap;
320
+ lines.push(`[finding] coverage: ${g.entities} entities + ${g.services} services catalogued but service_entity.csv emits 0 rows — Data Store Mapping ingestion missing (per ADR 0159 Phase 0)`);
321
+ }
322
+ if (res.containerEntityGap) {
323
+ const g = res.containerEntityGap;
324
+ lines.push(`[finding] coverage: ${g.containersWithService} containers map to services + ${g.serviceEntity} service_entity edges present but c4_container_entity.csv emits 0 rows — Phase 3 derivation missing (per ADR 0159 Phase 0)`);
325
+ }
326
+ return lines;
327
+ }
328
+ export function renderCapabilityEventCheck(res) {
329
+ const lines = [];
330
+ if (res.derivableGap) {
331
+ const g = res.derivableGap;
332
+ lines.push(`[finding] coverage: service_event (${g.serviceEvent}) + capability_service (${g.capabilityService}) populated but capability_event emits 0 rows — reverse-derivable edge missing (per ADR 0160 Phase 0)`);
333
+ }
334
+ if (res.authoredGap) {
335
+ lines.push(`[finding] coverage: capability_event has ${res.authoredGap.derived} derived rows but 0 authored rows — no traceability.events[] or traceability.contracts.{published,consumed}_events declarations are landing in the catalogue. Governance queries filtering to source=authored will return empty (per ADR 0160 Phase 0)`);
336
+ }
337
+ return lines;
338
+ }
339
+ export function renderFeatureEventCheck(res) {
340
+ return res.gap
341
+ ? [
342
+ `[finding] coverage: service_event (${res.gap.serviceEvent}) + feature_service (${res.gap.featureService}) populated but feature_event emits 0 rows — reverse-derivable edge missing (per ADR 0161 Phase 0)`,
343
+ ]
344
+ : [];
345
+ }
346
+ export function renderPlatformDependenciesCheck(finding) {
347
+ return finding
348
+ ? [
349
+ `[finding] coverage: app-platform-dependencies.yaml declares a platform_dependencies block but product_platform_dependencies.csv emits 0 rows — forward-method ingestion missing (per ADR 0162 Phase 0). Resolves once the builder reads the forward source.`,
350
+ ]
351
+ : [];
352
+ }
353
+ export function renderFeatureSpecPromotionCheck(res) {
354
+ if (!res.finding)
355
+ return [];
356
+ const f = res.finding;
357
+ return [
358
+ `[finding] coverage: ${f.intermediate} of ${f.total} features are reverse-intermediate ` +
359
+ `(capability-spec/feature-grounding) not yet promoted to feature-spec — ` +
360
+ `unresolvable as realises:/FK targets (${f.pctResolvable}% resolvable, ${f.note}; ` +
361
+ `per ADR 0177 §4.2)`,
362
+ ];
363
+ }
364
+ export function renderFeatureSurfaceDuplicationCheck(res) {
365
+ if (!res.finding)
366
+ return [];
367
+ const f = res.finding;
368
+ const capWord = f.capabilities === 1 ? "capability" : "capabilities";
369
+ return [
370
+ `[finding] coverage: ${f.pairs} near-duplicate feature surface(s) across ` +
371
+ `${f.capabilities} ${capWord} — a capability-spec (rollup) and feature-grounding ` +
372
+ `(detailed) slug naming the same surface; reconcile to one slug per surface, ` +
373
+ `detailed slug canonical (per ADR 0177 §4.3)`,
374
+ ];
375
+ }
376
+ // ─── Scores coverage (M-ADR 0205 §4.5, B2 — the fourteenth check) ─────────
377
+ /**
378
+ * Unlike the thirteen predicates above, this text has NO reference shell
379
+ * script to be byte-identical with — it is a new surface, so the lines are
380
+ * chosen rather than reproduced. Two rules they follow, both paid for
381
+ * elsewhere in this arc:
382
+ *
383
+ * - every count states its basis in the same breath as the number, so a
384
+ * figure can never be read against the wrong denominator;
385
+ * - a number the predicate CANNOT reach is printed beside the number it
386
+ * reports, so a gap is never mistaken for backlog. Here that is the
387
+ * rubrics with no catalogued population and the scores whose currency is
388
+ * unknowable — both stated, never dropped.
389
+ */
390
+ export function renderScoresCoverage(res, tier) {
391
+ // Only silent when there is genuinely nothing to say. An SDD that measured
392
+ // NOTHING still reports which rubrics it skipped and why — suppressing that
393
+ // was this renderer's own first draft, and it is precisely the silent-cap
394
+ // shape the predicate exists to remove.
395
+ if (res.totalScores === 0 && res.metrics.length === 0 && res.notAdopted.length === 0) {
396
+ return [];
397
+ }
398
+ const out = [];
399
+ for (const f of res.findings) {
400
+ if (f.kind === "uncovered") {
401
+ out.push(`[finding] coverage: ${f.subject} carries no ${f.rubricId} score — ` +
402
+ `unscored is not passed; assess it or record why it is exempt ` +
403
+ `(no exemption vocabulary exists yet, per M-ADR 0199)`);
404
+ }
405
+ else if (f.kind === "out-of-scope-score") {
406
+ // Named remedy, and it is deliberately NOT "delete this". M-ADR 0199 §4.4
407
+ // binds a remedy to be true at the emitting tier: the assessment itself is
408
+ // real work and a real judgment, and the ruled disposition (2026-08-07) is
409
+ // that it travels to the producer as feedback rather than being discarded.
410
+ // The forward estate's 24 rows moved that way — packaged, ingested,
411
+ // auditable — and a remedy line reading "withdraw" would have advised
412
+ // throwing away exactly what was worth transferring.
413
+ out.push(`[finding] coverage: ${f.rubricId} score for '${f.subject}' targets a ` +
414
+ `method-managed artefact — sync overwrites any remediation made here, so it ` +
415
+ `is out of this repo's scope to maintain; raise the assessment with the ` +
416
+ `producer and retire the row (it is counted in neither coverage nor orphans)`);
417
+ }
418
+ else if (f.kind === "orphaned-score") {
419
+ // COVERAGE class, not orphan, and the choice is governed rather than
420
+ // stylistic. M-ADR 0205 §4.5 ships this instrument "reporting-only at
421
+ // both tiers first"; M-ADR 0132 §4.3 makes `orphan` and `divergence`
422
+ // gate-acting. Emitting orphan here would have made the predicate gate
423
+ // on its first run, silently reversing the ADR's staging decision for a
424
+ // whole finding class — and on a real estate that meant 85 lines going
425
+ // straight into a findings baseline as grandfathered debt.
426
+ //
427
+ // Promotion to gate-acting is a deliberate later act: change this one
428
+ // word, and say so in the change-set. Until then the line still names
429
+ // the defect plainly — a score pointing at nothing is not softened by
430
+ // being informational.
431
+ out.push(`[finding] coverage: ${f.rubricId} score for '${f.subject}' resolves to no ` +
432
+ `catalogue row — the assessment describes something the model no longer ` +
433
+ `holds; re-key the registry entry or withdraw the score`);
434
+ }
435
+ else if (f.kind === "recorded-path-mismatch") {
436
+ // COVERAGE class — report-only by ruling (decision #3, 2026-09-29): it
437
+ // becomes gate-acting only once both estates read zero, and that is a
438
+ // deliberate later change to this one word, as for orphaned-score above.
439
+ out.push(`[finding] coverage: ${f.rubricId} score for ${f.subject} — the recorded file ` +
440
+ `is not the target's catalogue path, so the assessment may describe a copy the ` +
441
+ `catalogue cannot see; re-score against the catalogue path or re-point file_path`);
442
+ }
443
+ else {
444
+ out.push(`[finding] coverage: ${f.subject}`);
445
+ }
446
+ }
447
+ for (const r of res.undeclaredRubrics) {
448
+ out.push(`[finding] coverage: scores exist for rubric '${r}', which declares no ` +
449
+ `eligibility mapping — its coverage cannot be computed and its targets are ` +
450
+ `not resolution-checked; add it to RUBRIC_ELIGIBILITY`);
451
+ }
452
+ // The keying-rule sunset gauge (ruled 2026-08-07): the ADR prefix-strip is
453
+ // a migration aid, and this line is how "both estates read 0" gets measured
454
+ // rather than asserted. Silent when already 0 — a retired bridge needs no
455
+ // gauge.
456
+ if (res.transformBridgedRefs > 0) {
457
+ out.push(`[metric] scores_keying: ${res.transformBridgedRefs} adr-quality ref(s) still resolve via the ` +
458
+ `prefix-strip transform — re-key to the catalogue's own ids (scoring-registry guide, keying rule); ` +
459
+ `the transform sunsets when every estate reads 0`);
460
+ }
461
+ if (res.metrics.length === 0) {
462
+ out.push(`[metric] scores_coverage (${tier}): n/a — no rubric measurable in this SDD ` +
463
+ `(${res.notAdopted.length} not adopted, ${res.totalScores} score(s) present)`);
464
+ out.push(`[metric] scores_coverage.not_adopted: ${res.notAdopted.length} rubric(s) have no ` +
465
+ `registry in this SDD and are n/a here — ${res.notAdopted.join(", ")}; ` +
466
+ `absence of the surface is not a gap in it, but an EMPTY registry is`);
467
+ return out;
468
+ }
469
+ const withPop = res.metrics.filter((m) => m.eligible !== null);
470
+ const noPop = res.metrics.filter((m) => m.eligible === null);
471
+ const eligible = withPop.reduce((a, m) => a + (m.eligible ?? 0), 0);
472
+ const covered = withPop.reduce((a, m) => a + m.covered, 0);
473
+ const orphaned = res.metrics.reduce((a, m) => a + m.orphaned, 0);
474
+ const pct = eligible === 0 ? "n/a" : `${fmt1f((covered / eligible) * 100)}%`;
475
+ out.push(
476
+ // The denominator is (artefact, rubric) PAIRS, not distinct artefacts — one
477
+ // domain is eligible under domain-architecture, intent-quality AND glossary,
478
+ // so 14 domains contribute 42 eligible pairs. Naming the unit here is the
479
+ // difference between a measurement and a number that reads 3x too large.
480
+ `[metric] scores_coverage (${tier}): ${covered}/${eligible} eligible ` +
481
+ `(artefact, rubric) pair(s) scored (${pct}) across ${withPop.length} rubric(s) ` +
482
+ `with a catalogued population; ${orphaned} score(s) orphaned of ${res.totalScores} total`);
483
+ for (const m of withPop) {
484
+ out.push(`[metric] scores_coverage.${m.rubricId}: ${m.covered}/${m.eligible} ` +
485
+ `${m.targetKind}(s) scored, ${m.scored} score(s) present, ${m.orphaned} orphaned`);
486
+ }
487
+ // Recorded-path check (decision #3): stated for every rubric with scores,
488
+ // including the ones it cannot check — n/a is named, never read as agreement.
489
+ const scoredMetrics = res.metrics.filter((m) => m.scored > 0);
490
+ const checkable = scoredMetrics.filter((m) => m.recordedPath !== null);
491
+ const notCheckable = scoredMetrics.filter((m) => m.recordedPath === null);
492
+ if (scoredMetrics.length > 0) {
493
+ const sum = (k) => checkable.reduce((a, m) => a + (m.recordedPath?.[k] ?? 0), 0);
494
+ out.push(`[metric] scores_coverage.recorded_path: ${sum("agree")} agree, ${sum("mismatch")} mismatch, ` +
495
+ `${sum("noRecordedPath")} with no recorded path — across ${checkable.length} rubric(s) whose ` +
496
+ `catalogue holds the scored file's path` +
497
+ (notCheckable.length > 0
498
+ ? `; n/a for ${notCheckable.length} (${notCheckable.map((m) => m.rubricId).join(", ")}) — ` +
499
+ `the catalogue does not hold the path of the file those rubrics score`
500
+ : "") +
501
+ ` (report-only; a path filled in from the catalogue agrees by construction)`);
502
+ }
503
+ // The excluded population is printed BESIDE the denominator it shrank, never
504
+ // folded into it. A reader comparing this run against a pre-filter baseline
505
+ // must be able to see why the denominator moved without re-deriving it, and
506
+ // the bundle breakdown answers the immediate next question — which producer's
507
+ // content is it — in the same line rather than in a follow-up investigation.
508
+ const excluded = withPop.filter((m) => m.managedExcluded > 0);
509
+ for (const m of excluded) {
510
+ const byBundle = Object.entries(m.managedExcludedByBundle)
511
+ .sort((a, b2) => b2[1] - a[1] || a[0].localeCompare(b2[0]))
512
+ .map(([name, n]) => `${name} ${n}`)
513
+ .join(", ");
514
+ out.push(`[metric] scores_coverage.${m.rubricId}.method_managed: ${m.managedExcluded} ` +
515
+ `${m.targetKind}(s) excluded from the denominator as method-managed ` +
516
+ `(${byBundle}) — sync overwrites remediation here, so their quality belongs ` +
517
+ `to the producer's registry; NOT netted against the gap count above`);
518
+ }
519
+ if (noPop.length > 0) {
520
+ out.push(`[metric] scores_coverage.no_denominator: ${noPop.length} rubric(s) score ` +
521
+ `artefacts the catalogue does not model — ` +
522
+ `${noPop.map((m) => `${m.rubricId}(${m.scored})`).join(", ")}; ` +
523
+ `their coverage is NOT measurable here and is not counted above`);
524
+ }
525
+ if (res.notAdopted.length > 0) {
526
+ out.push(`[metric] scores_coverage.not_adopted: ${res.notAdopted.length} rubric(s) have no ` +
527
+ `registry in this SDD and are n/a here — ${res.notAdopted.join(", ")}; ` +
528
+ `absence of the surface is not a gap in it, but an EMPTY registry is`);
529
+ }
530
+ const b = res.stalenessBasis;
531
+ out.push(`[metric] scores_staleness_basis: ${b.corpusRef} corpus_ref, ${b.scoredAt} scored_at, ` +
532
+ `${b.none} none — a row with basis 'none' has UNKNOWN currency, not fresh`);
533
+ return out;
534
+ }
535
+ export function renderRelationshipCoherenceCheck(res) {
536
+ const out = [];
537
+ for (const f of res.ownedUntraced) {
538
+ out.push(`[finding] coverage: features/${f.featureId} is owned by capabilities/${f.capabilityId} but capability_feature carries no trace — owned-but-untraced (M-ADR 0206 §4.5)`);
539
+ }
540
+ for (const d of res.publisherDrift) {
541
+ out.push(d.missing === "publisher-fk"
542
+ ? `[finding] coverage: service_event declares ${d.serviceId} publishes ${d.eventId} but events.publisher_service_id disagrees — publisher-projection drift (M-ADR 0206 §4.5)`
543
+ : `[finding] coverage: events/${d.eventId} names publisher ${d.serviceId} but service_event has no publishes row — publisher-projection drift (M-ADR 0206 §4.5)`);
544
+ }
545
+ for (const s of res.supersessionDrift) {
546
+ out.push(s.missing === "fk-backfill"
547
+ ? `[finding] coverage: adr_supersedes ${s.supersedingId} -> ${s.supersededId} has no superseded_by_id backfill — supersession drift (M-ADR 0206 §4.5)`
548
+ : `[finding] coverage: adrs/${s.supersededId} carries superseded_by_id ${s.supersedingId} but adr_supersedes has no row — supersession drift (M-ADR 0206 §4.5)`);
549
+ }
550
+ return out;
551
+ }
552
+ //# sourceMappingURL=coverage-render.js.map