@sdd-method/maintain-cli 0.0.0-stage → 0.1.0-rc.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 (206) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +17 -0
  3. package/README.md +6 -2
  4. package/dist/lib/adr-origin.js +59 -0
  5. package/dist/lib/agent-harness.js +28 -0
  6. package/dist/lib/briefing/index.js +13 -0
  7. package/dist/lib/briefing/render.js +123 -0
  8. package/dist/lib/briefing/variables.js +92 -0
  9. package/dist/lib/bundle.js +123 -0
  10. package/dist/lib/catalogue/aggregate.js +2404 -0
  11. package/dist/lib/catalogue/aggregation-manifest-loader.js +173 -0
  12. package/dist/lib/catalogue/application-archetype/expectations.js +119 -0
  13. package/dist/lib/catalogue/application-archetype/vocabulary.js +177 -0
  14. package/dist/lib/catalogue/archetype/maps/android.yaml +66 -0
  15. package/dist/lib/catalogue/archetype/maps/swiftui.yaml +58 -0
  16. package/dist/lib/catalogue/archetype/maps/uikit.yaml +48 -0
  17. package/dist/lib/catalogue/archetype/maps/web-generic.yaml +86 -0
  18. package/dist/lib/catalogue/archetype/openui-archetype-vocabulary.yaml +46 -0
  19. package/dist/lib/catalogue/archetype/resolver.js +103 -0
  20. package/dist/lib/catalogue/archetype/wireframe.js +418 -0
  21. package/dist/lib/catalogue/baseline-check.js +151 -0
  22. package/dist/lib/catalogue/build.js +1502 -0
  23. package/dist/lib/catalogue/builders/adr-supersedes.js +103 -0
  24. package/dist/lib/catalogue/builders/adrs.js +222 -0
  25. package/dist/lib/catalogue/builders/application-conformance-coverage.js +47 -0
  26. package/dist/lib/catalogue/builders/application-data-plane-bindings.js +115 -0
  27. package/dist/lib/catalogue/builders/c4-components.js +229 -0
  28. package/dist/lib/catalogue/builders/c4-containers.js +240 -0
  29. package/dist/lib/catalogue/builders/c4-entity-rollups.js +87 -0
  30. package/dist/lib/catalogue/builders/c4-external-systems.js +194 -0
  31. package/dist/lib/catalogue/builders/c4-l1-candidates.js +107 -0
  32. package/dist/lib/catalogue/builders/c4-systems.js +98 -0
  33. package/dist/lib/catalogue/builders/capabilities.js +488 -0
  34. package/dist/lib/catalogue/builders/capability-domain-model.js +74 -0
  35. package/dist/lib/catalogue/builders/capability-edges.js +256 -0
  36. package/dist/lib/catalogue/builders/capability-spec-walker.js +99 -0
  37. package/dist/lib/catalogue/builders/capability-spec.js +348 -0
  38. package/dist/lib/catalogue/builders/container-dependency.js +183 -0
  39. package/dist/lib/catalogue/builders/contracts.js +402 -0
  40. package/dist/lib/catalogue/builders/data-items.js +344 -0
  41. package/dist/lib/catalogue/builders/data-store-mapping.js +113 -0
  42. package/dist/lib/catalogue/builders/domain-models.js +175 -0
  43. package/dist/lib/catalogue/builders/domains.js +119 -0
  44. package/dist/lib/catalogue/builders/edge-label-merge.js +19 -0
  45. package/dist/lib/catalogue/builders/entities-manifest.js +160 -0
  46. package/dist/lib/catalogue/builders/entities-mermaid.js +189 -0
  47. package/dist/lib/catalogue/builders/entity-persistence.js +102 -0
  48. package/dist/lib/catalogue/builders/entity-slug.js +47 -0
  49. package/dist/lib/catalogue/builders/event-catalog-walker.js +186 -0
  50. package/dist/lib/catalogue/builders/event-edges.js +327 -0
  51. package/dist/lib/catalogue/builders/events.js +168 -0
  52. package/dist/lib/catalogue/builders/evidence-class.js +43 -0
  53. package/dist/lib/catalogue/builders/evidence-target-repo.js +67 -0
  54. package/dist/lib/catalogue/builders/external-system-exemption.js +120 -0
  55. package/dist/lib/catalogue/builders/feature-contract.js +77 -0
  56. package/dist/lib/catalogue/builders/feature-domain-model.js +50 -0
  57. package/dist/lib/catalogue/builders/feature-grounding-walker.js +254 -0
  58. package/dist/lib/catalogue/builders/feature-grounding.js +391 -0
  59. package/dist/lib/catalogue/builders/feature-groups.js +63 -0
  60. package/dist/lib/catalogue/builders/feature-subscription-channels.js +228 -0
  61. package/dist/lib/catalogue/builders/feature-tree-walker.js +295 -0
  62. package/dist/lib/catalogue/builders/features.js +300 -0
  63. package/dist/lib/catalogue/builders/glossary-xref.js +378 -0
  64. package/dist/lib/catalogue/builders/glossary.js +204 -0
  65. package/dist/lib/catalogue/builders/implementations.js +214 -0
  66. package/dist/lib/catalogue/builders/index.js +64 -0
  67. package/dist/lib/catalogue/builders/integration-components.js +344 -0
  68. package/dist/lib/catalogue/builders/integrations.js +181 -0
  69. package/dist/lib/catalogue/builders/persona-c4system.js +149 -0
  70. package/dist/lib/catalogue/builders/persona-container.js +133 -0
  71. package/dist/lib/catalogue/builders/persona-journey-edges.js +133 -0
  72. package/dist/lib/catalogue/builders/personas.js +152 -0
  73. package/dist/lib/catalogue/builders/product-edges.js +106 -0
  74. package/dist/lib/catalogue/builders/product-platform-dependencies.js +405 -0
  75. package/dist/lib/catalogue/builders/products.js +162 -0
  76. package/dist/lib/catalogue/builders/quality-scores.js +635 -0
  77. package/dist/lib/catalogue/builders/repo-capability-exemption.js +61 -0
  78. package/dist/lib/catalogue/builders/repo-dependency.js +127 -0
  79. package/dist/lib/catalogue/builders/repo-edges.js +340 -0
  80. package/dist/lib/catalogue/builders/repositories.js +458 -0
  81. package/dist/lib/catalogue/builders/scaffolds.js +197 -0
  82. package/dist/lib/catalogue/builders/screen-tree.js +222 -0
  83. package/dist/lib/catalogue/builders/screens.js +396 -0
  84. package/dist/lib/catalogue/builders/service-capability-exemption.js +63 -0
  85. package/dist/lib/catalogue/builders/service-contract-consumes.js +186 -0
  86. package/dist/lib/catalogue/builders/service-contract.js +74 -0
  87. package/dist/lib/catalogue/builders/service-entity-tables.js +187 -0
  88. package/dist/lib/catalogue/builders/services.js +232 -0
  89. package/dist/lib/catalogue/builders/system-external-dependency.js +115 -0
  90. package/dist/lib/catalogue/builders/user-journeys.js +253 -0
  91. package/dist/lib/catalogue/canonical-schema.yaml +4117 -0
  92. package/dist/lib/catalogue/cli-version.js +10 -0
  93. package/dist/lib/catalogue/contract-id.js +49 -0
  94. package/dist/lib/catalogue/contract-path-resolution.js +149 -0
  95. package/dist/lib/catalogue/coverage-predicates.js +603 -0
  96. package/dist/lib/catalogue/coverage-render.js +552 -0
  97. package/dist/lib/catalogue/coverage-run.js +115 -0
  98. package/dist/lib/catalogue/csv-reader.js +97 -0
  99. package/dist/lib/catalogue/csv-writer.js +96 -0
  100. package/dist/lib/catalogue/data-item-coverage.js +142 -0
  101. package/dist/lib/catalogue/data-plane-coherence.js +77 -0
  102. package/dist/lib/catalogue/ddl-generator.js +233 -0
  103. package/dist/lib/catalogue/dedupe-keyed-tables.js +0 -0
  104. package/dist/lib/catalogue/engine-validator.js +239 -0
  105. package/dist/lib/catalogue/ext-id.js +76 -0
  106. package/dist/lib/catalogue/feature-grounding-aliases-loader.js +65 -0
  107. package/dist/lib/catalogue/feature-repo-zero-resolution-summary.js +31 -0
  108. package/dist/lib/catalogue/finding-identity.js +183 -0
  109. package/dist/lib/catalogue/findings.js +46 -0
  110. package/dist/lib/catalogue/fk-validator.js +136 -0
  111. package/dist/lib/catalogue/freshness.js +69 -0
  112. package/dist/lib/catalogue/gate-checks.js +338 -0
  113. package/dist/lib/catalogue/graph-projection.js +346 -0
  114. package/dist/lib/catalogue/graph-run.js +64 -0
  115. package/dist/lib/catalogue/impact-run.js +228 -0
  116. package/dist/lib/catalogue/manifest-loader.js +435 -0
  117. package/dist/lib/catalogue/platform-scope.js +7 -0
  118. package/dist/lib/catalogue/population-checks.js +87 -0
  119. package/dist/lib/catalogue/relationship-semantics.js +66 -0
  120. package/dist/lib/catalogue/reverse-product-quality.js +99 -0
  121. package/dist/lib/catalogue/rubric-coverage-map.yaml +871 -0
  122. package/dist/lib/catalogue/runway-vocabulary-loader.js +56 -0
  123. package/dist/lib/catalogue/scaffold-delta.js +261 -0
  124. package/dist/lib/catalogue/schema-compat.js +52 -0
  125. package/dist/lib/catalogue/schema-loader.js +87 -0
  126. package/dist/lib/catalogue/scores-coverage.js +573 -0
  127. package/dist/lib/catalogue/screen-validation-loader.js +89 -0
  128. package/dist/lib/catalogue/service-architecture-parser.js +193 -0
  129. package/dist/lib/catalogue/service-contract-aliases-loader.js +109 -0
  130. package/dist/lib/catalogue/staleness-verdicts.js +345 -0
  131. package/dist/lib/catalogue/traceability-loader.js +350 -0
  132. package/dist/lib/catalogue/validate.js +200 -0
  133. package/dist/lib/catalogue/version.js +18 -0
  134. package/dist/lib/ci-binding/index.js +209 -0
  135. package/dist/lib/ci-binding/module.js +204 -0
  136. package/dist/lib/conformance/binding.js +70 -0
  137. package/dist/lib/context-pull/index.js +151 -0
  138. package/dist/lib/context-pull/resolve.js +70 -0
  139. package/dist/lib/generate-briefing/index.js +77 -0
  140. package/dist/lib/hooks-seed/copilot.js +167 -0
  141. package/dist/lib/hooks-seed/entries.js +47 -0
  142. package/dist/lib/hooks-seed/index.js +17 -0
  143. package/dist/lib/hooks-seed/lifecycle-mapping.js +28 -0
  144. package/dist/lib/hooks-seed/merge.js +128 -0
  145. package/dist/lib/hooks-seed/types.js +20 -0
  146. package/dist/lib/init/ci-workflow.js +89 -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 +432 -0
  151. package/dist/lib/mcp-seed/entries.js +14 -0
  152. package/dist/lib/mcp-seed/index.js +28 -0
  153. package/dist/lib/mcp-seed/merge.js +99 -0
  154. package/dist/lib/mcp-seed/types.js +13 -0
  155. package/dist/lib/pack/builder.js +303 -0
  156. package/dist/lib/pack/index.js +87 -0
  157. package/dist/lib/pack/leakage.js +144 -0
  158. package/dist/lib/pack/log.js +18 -0
  159. package/dist/lib/pack/publish-transform.js +233 -0
  160. package/dist/lib/pack/reference-closure.js +125 -0
  161. package/dist/lib/pack/schema.js +121 -0
  162. package/dist/lib/pack/seed.js +182 -0
  163. package/dist/lib/pack/validate.js +49 -0
  164. package/dist/lib/package-version.js +92 -0
  165. package/dist/lib/repo-kind.js +129 -0
  166. package/dist/lib/scripts.js +7 -0
  167. package/dist/lib/snapshot/catalogue-diff.js +219 -0
  168. package/dist/lib/snapshot/manifest.js +206 -0
  169. package/dist/lib/sync/adr-transitions.js +250 -0
  170. package/dist/lib/sync/apply.js +91 -0
  171. package/dist/lib/sync/briefing-merge.js +90 -0
  172. package/dist/lib/sync/bundle-cache.js +109 -0
  173. package/dist/lib/sync/bundle-source.js +61 -0
  174. package/dist/lib/sync/config.js +72 -0
  175. package/dist/lib/sync/copilot-hooks-merge.js +108 -0
  176. package/dist/lib/sync/diff.js +251 -0
  177. package/dist/lib/sync/index.js +809 -0
  178. package/dist/lib/sync/settings-merge.js +216 -0
  179. package/dist/lib/sync/yaml-block-merge.js +120 -0
  180. package/dist/lib/validate/aggregate.js +51 -0
  181. package/dist/lib/validate/bundle-coherence.js +74 -0
  182. package/dist/lib/validate/dispatch.js +243 -0
  183. package/dist/lib/validate/index.js +110 -0
  184. package/dist/lib/validate/plan.js +54 -0
  185. package/dist/lib/validate/runner.js +85 -0
  186. package/dist/lib/validate/yaml-parse.js +112 -0
  187. package/dist/maintain-bin.js +8 -0
  188. package/dist/maintain-program.js +91 -0
  189. package/dist/maintain-version.json +4 -0
  190. package/dist/verbs/catalogue-maintain.js +110 -0
  191. package/dist/verbs/catalogue-verify.js +114 -0
  192. package/dist/verbs/context.js +39 -0
  193. package/dist/verbs/generate-briefing.js +29 -0
  194. package/dist/verbs/list.js +45 -0
  195. package/dist/verbs/pack-build.js +31 -0
  196. package/dist/verbs/pack-validate.js +17 -0
  197. package/dist/verbs/path-option.js +25 -0
  198. package/dist/verbs/render.js +87 -0
  199. package/dist/verbs/sync.js +98 -0
  200. package/dist/verbs/validate.js +48 -0
  201. package/dist/verify-program.js +122 -0
  202. package/dist/verify-version.json +4 -0
  203. package/package.json +32 -4
  204. package/scripts/validate-app-dependencies.sh +664 -0
  205. package/scripts/validate-app-docs.sh +199 -0
  206. package/scripts/validate-integration-docs.sh +505 -0
@@ -0,0 +1,173 @@
1
+ // Aggregation-manifest loader per
2
+ // docs/method/methods/aggregation-manifest-schema.yaml.
3
+ //
4
+ // v1 supports `pull.kind: filesystem` only; other kinds (git-ref,
5
+ // release-asset, mcp-resource) are schema-reserved and reject with a
6
+ // clear "deferred" error so adopters get unambiguous feedback.
7
+ import { readFile } from "node:fs/promises";
8
+ import { isAbsolute, relative, resolve } from "node:path";
9
+ import { parse as parseYaml } from "yaml";
10
+ import { z } from "zod";
11
+ import { SCHEMA_COMPAT, formatSchemaVersionMismatch } from "./schema-compat.js";
12
+ import { isInRange } from "./manifest-loader.js";
13
+ export const DEFAULT_AGGREGATION_MANIFEST_REL = "orchestration/aggregation-manifest.yaml";
14
+ const PullSchema = z
15
+ .object({
16
+ kind: z.string(),
17
+ path: z.string().optional(),
18
+ })
19
+ .passthrough();
20
+ const SiblingSchema = z.object({
21
+ sdd_id: z.string().regex(/^[a-z][a-z0-9-]*$/, {
22
+ message: "sdd_id must match /^[a-z][a-z0-9-]*$/",
23
+ }),
24
+ pull: PullSchema,
25
+ description: z.string().optional(),
26
+ level: z.string().optional(),
27
+ });
28
+ /**
29
+ * An ecosystem member that is real and intended but has no SDD yet
30
+ * (aggregation-manifest-schema 1.1.0, per M-ADR 0195 §4.3).
31
+ *
32
+ * A SEPARATE list rather than a flag on `siblings[]`: `pull` is required on a sibling
33
+ * and a pending member has nothing to pull, and `siblings[]` must keep meaning exactly
34
+ * "members whose catalogues merge in" — adding skippable entries would give one list
35
+ * two meanings, which every existing reader would keep miscounting.
36
+ *
37
+ * `reason` is required and non-empty. A bare marker suppresses the build failure while
38
+ * saying nothing about why, losing the signal that a pending member is a WORK ITEM —
39
+ * the state this replaces.
40
+ */
41
+ const PendingMemberSchema = z.object({
42
+ sdd_id: z.string().regex(/^[a-z][a-z0-9-]*$/, {
43
+ message: "pending_members[].sdd_id must match /^[a-z][a-z0-9-]*$/",
44
+ }),
45
+ reason: z.string().min(1, {
46
+ message: "pending_members[].reason must be non-empty — a marker without a reason suppresses a failure without explaining it",
47
+ }),
48
+ blocked_on: z.string().optional(),
49
+ });
50
+ const SLUG = /^[a-z][a-z0-9-]*$/;
51
+ // M-ADR 0208 §4.3: `root_sdd_id` is canonical; `ecosystem_root` is its DEPRECATED alias
52
+ // (at organisation tier the old name called an organisation-root an "ecosystem root").
53
+ // Exactly one of the two must be present. Removed at 2.0.0.
54
+ const AggregationManifestSchema = z
55
+ .object({
56
+ schema_version: z.string(),
57
+ root_sdd_id: z.string().regex(SLUG).optional(),
58
+ ecosystem_root: z.string().regex(SLUG).optional(),
59
+ level: z.enum(["platform", "organisation"]).optional(),
60
+ siblings: z.array(SiblingSchema),
61
+ pending_members: z.array(PendingMemberSchema).optional(),
62
+ })
63
+ .refine((m) => (m.root_sdd_id !== undefined) !== (m.ecosystem_root !== undefined), {
64
+ message: "exactly one of `root_sdd_id` (canonical, M-ADR 0208 §4.3) or `ecosystem_root` (deprecated alias) must be present",
65
+ });
66
+ export async function loadAggregationManifest(manifestPath, options) {
67
+ let text;
68
+ try {
69
+ text = await readFile(manifestPath, "utf-8");
70
+ }
71
+ catch (err) {
72
+ if (err.code === "ENOENT") {
73
+ throw new Error(`Aggregation manifest not found at ${manifestPath}\n\n` +
74
+ `Ecosystem-root SDDs declare an aggregation manifest at the path\n` +
75
+ `recorded in .sdd/sdd-manifest.yaml#aggregation_manifest_path, or\n` +
76
+ `at ${DEFAULT_AGGREGATION_MANIFEST_REL} by default.\n` +
77
+ `Schema: docs/method/methods/aggregation-manifest-schema.yaml.`);
78
+ }
79
+ throw err;
80
+ }
81
+ let raw;
82
+ try {
83
+ raw = parseYaml(text);
84
+ }
85
+ catch (err) {
86
+ throw new Error(`failed to parse YAML at ${manifestPath}: ${err.message}`);
87
+ }
88
+ const parsed = AggregationManifestSchema.safeParse(raw);
89
+ if (!parsed.success) {
90
+ const issues = parsed.error.issues
91
+ .map((i) => ` - ${i.path.join(".") || "<root>"}: ${i.message}`)
92
+ .join("\n");
93
+ throw new Error(`invalid aggregation manifest at ${manifestPath}:\n${issues}`);
94
+ }
95
+ if (!isInRange(parsed.data.schema_version, SCHEMA_COMPAT.aggregation_manifest)) {
96
+ throw new Error(formatSchemaVersionMismatch({
97
+ schema: "aggregation_manifest",
98
+ readVersion: parsed.data.schema_version,
99
+ cliVersion: options.cliVersion,
100
+ sourcePath: manifestPath,
101
+ }));
102
+ }
103
+ // Duplicate sibling-id check.
104
+ const seen = new Set();
105
+ for (const s of parsed.data.siblings) {
106
+ if (seen.has(s.sdd_id)) {
107
+ throw new Error(`aggregation manifest has duplicate sibling sdd_id "${s.sdd_id}"\n` +
108
+ ` (${manifestPath})`);
109
+ }
110
+ seen.add(s.sdd_id);
111
+ }
112
+ // Resolve each sibling's pull. v1 supports `kind: filesystem` only.
113
+ const resolved = [];
114
+ for (const s of parsed.data.siblings) {
115
+ const kind = s.pull.kind;
116
+ if (kind !== "filesystem") {
117
+ throw new Error(`aggregation manifest: sibling "${s.sdd_id}" uses pull.kind="${kind}"\n` +
118
+ ` v1 supports filesystem only. Other kinds (git-ref, release-asset,\n` +
119
+ ` mcp-resource) are schema-reserved and not yet implemented.\n` +
120
+ ` See: docs/method/methods/aggregation-manifest-schema.yaml#pull_kinds`);
121
+ }
122
+ const path = s.pull.path;
123
+ if (!path || typeof path !== "string") {
124
+ throw new Error(`aggregation manifest: sibling "${s.sdd_id}" pull.kind=filesystem requires \`path\``);
125
+ }
126
+ const cataloguesPath = isAbsolute(path)
127
+ ? path
128
+ : resolve(options.ecosystemRootPath, path);
129
+ // The sibling's repo root is the parent of `catalogues/`.
130
+ const sddRoot = resolve(cataloguesPath, "..");
131
+ // Portable form for the committed JSON snapshot: keep a relative source
132
+ // verbatim; anchor an absolute source to the ecosystem root so the emitted
133
+ // value never carries a machine-specific $HOME/checkout prefix.
134
+ const relativeCataloguesPath = isAbsolute(path)
135
+ ? relative(options.ecosystemRootPath, cataloguesPath)
136
+ : path;
137
+ resolved.push({
138
+ sddId: s.sdd_id,
139
+ description: s.description ?? "",
140
+ cataloguesPath,
141
+ relativeCataloguesPath,
142
+ sddRoot,
143
+ level: s.level,
144
+ });
145
+ }
146
+ // A member is pending authoring OR contributing catalogues, never both. Declaring
147
+ // both is a contradiction rather than something to merge, and silently preferring
148
+ // one reading would make the work queue untrustworthy — so it fails with the id.
149
+ const pendingMembers = [];
150
+ const siblingIds = new Set(resolved.map((r) => r.sddId));
151
+ for (const p of parsed.data.pending_members ?? []) {
152
+ if (siblingIds.has(p.sdd_id)) {
153
+ throw new Error(`aggregation manifest: "${p.sdd_id}" appears in BOTH siblings and pending_members.\n` +
154
+ ` A member is either pending authoring or contributing catalogues, not both.\n` +
155
+ ` Remove whichever declaration is stale (${parsed.data.root_sdd_id ?? parsed.data.ecosystem_root}).`);
156
+ }
157
+ pendingMembers.push({
158
+ sddId: p.sdd_id,
159
+ reason: p.reason,
160
+ ...(p.blocked_on !== undefined ? { blockedOn: p.blocked_on } : {}),
161
+ });
162
+ }
163
+ return {
164
+ schemaVersion: parsed.data.schema_version,
165
+ ecosystemRoot: (parsed.data.root_sdd_id ?? parsed.data.ecosystem_root),
166
+ level: parsed.data.level,
167
+ rootFieldDeprecated: parsed.data.root_sdd_id === undefined,
168
+ siblings: resolved,
169
+ pendingMembers,
170
+ sourcePath: manifestPath,
171
+ };
172
+ }
173
+ //# sourceMappingURL=aggregation-manifest-loader.js.map
@@ -0,0 +1,119 @@
1
+ // Archetype expectations overlay — the deviation-visible WARN (M-ADR 0191 §4.3,
2
+ // given concrete content by M-ADR 0193 §4.4).
3
+ //
4
+ // An archetype supplies a DEFAULT data-plane composition (its `expected_bindings`
5
+ // in the reference-architecture vocabulary). A product's declared bindings are
6
+ // checked against that default:
7
+ // - a `required` expected pattern with no declared binding of that pattern, OR
8
+ // - a declared binding whose pattern is `absent-by-shape` for the archetype,
9
+ // is a deviation. A deviation is a WARN (`convention` finding) UNLESS the product
10
+ // declares a matching `data_plane.deviations` rationale — a declared deviation is
11
+ // sanctioned variance, not silent drift.
12
+ //
13
+ // `typical` and `optional` expectations are advisory only: neither presence nor
14
+ // absence warns. The check runs only when the archetype's overlay is discoverable
15
+ // (the reference-architecture bundle is consumed); absence of an overlay is a
16
+ // silent skip, never a coupling to bundle presence (M-ADR 0191 §4.1).
17
+ import { UNCLASSIFIED } from "./vocabulary.js";
18
+ /**
19
+ * Emit a deviation-visible WARN for each undeclared departure from the
20
+ * archetype's default composition. Returns the number of WARNs emitted.
21
+ */
22
+ export function checkDataPlaneExpectations(inputs) {
23
+ const { productId, archetype, expectedBindings, declaredBindings, deviations, findings } = inputs;
24
+ const declaredPatterns = new Set(declaredBindings.map((b) => b.pattern));
25
+ const covered = new Set(deviations.map((d) => d.expectation));
26
+ let warned = 0;
27
+ for (const expected of expectedBindings) {
28
+ if (covered.has(expected.pattern))
29
+ continue; // declared deviation — sanctioned
30
+ if (expected.presence === "required" && !declaredPatterns.has(expected.pattern)) {
31
+ findings.add("convention", `data_plane: archetype "${archetype}" expects a required "${expected.pattern}" binding for ${productId}, ` +
32
+ `but none is declared and no data_plane.deviations rationale covers it (M-ADR 0191 §4.3)`);
33
+ warned++;
34
+ }
35
+ else if (expected.presence === "absent-by-shape" && declaredPatterns.has(expected.pattern)) {
36
+ findings.add("convention", `data_plane: archetype "${archetype}" is not expected to carry a "${expected.pattern}" binding ` +
37
+ `(absent-by-shape), but ${productId} declares one and no data_plane.deviations rationale covers it ` +
38
+ `(M-ADR 0191 §4.3)`);
39
+ warned++;
40
+ }
41
+ }
42
+ return warned;
43
+ }
44
+ /**
45
+ * Run the M-ADR 0191 §4.3 overlay at AGGREGATION tier — the deviation-visible
46
+ * WARN and the conformance surface — across every application-product. This is
47
+ * the tier where the reference-architecture bundle (and its `expected_bindings`)
48
+ * resolves; a per-SDD run would be inert for an SDD that consumes an adopter
49
+ * context-pack rather than the RA bundle (M-ADR 0191 §4.1; the 0.92.1 membership
50
+ * move). An archetype with no discoverable overlay is silently skipped.
51
+ */
52
+ export function checkDataPlaneOverlayAtAggregation(inputs) {
53
+ const { products, bindings, deviations, conformanceCoverage, vocab, findings } = inputs;
54
+ const bindingsByProduct = new Map();
55
+ for (const b of bindings) {
56
+ const pid = String(b["product_id"] ?? "");
57
+ if (pid === "")
58
+ continue;
59
+ (bindingsByProduct.get(pid) ?? bindingsByProduct.set(pid, []).get(pid)).push({
60
+ pattern: String(b["pattern"] ?? ""),
61
+ });
62
+ }
63
+ const deviationsByProduct = new Map();
64
+ for (const d of deviations) {
65
+ const pid = String(d["product_id"] ?? "");
66
+ if (pid === "")
67
+ continue;
68
+ (deviationsByProduct.get(pid) ?? deviationsByProduct.set(pid, []).get(pid)).push({
69
+ expectation: String(d["expectation"] ?? ""),
70
+ });
71
+ }
72
+ // Conformance coverage: per product, the set of tags declared covered or
73
+ // deviated (both satisfy the gate — deviated is sanctioned variance).
74
+ const conformanceByProduct = new Map();
75
+ for (const c of conformanceCoverage) {
76
+ const pid = String(c["product_id"] ?? "");
77
+ const tag = String(c["tag"] ?? "");
78
+ if (pid === "" || tag === "")
79
+ continue;
80
+ (conformanceByProduct.get(pid) ?? conformanceByProduct.set(pid, new Set()).get(pid)).add(tag);
81
+ }
82
+ for (const p of products) {
83
+ if (String(p["profile"] ?? "") !== "application-product")
84
+ continue;
85
+ const archetype = String(p["application_archetype"] ?? "");
86
+ if (archetype === "" || archetype === UNCLASSIFIED)
87
+ continue;
88
+ const productId = String(p["id"] ?? "");
89
+ if (productId === "")
90
+ continue;
91
+ const term = vocab.terms.find((t) => t.id === archetype);
92
+ if (term === undefined)
93
+ continue;
94
+ if (term.expectedBindings !== undefined && term.expectedBindings.length > 0) {
95
+ checkDataPlaneExpectations({
96
+ productId,
97
+ archetype,
98
+ expectedBindings: term.expectedBindings,
99
+ declaredBindings: bindingsByProduct.get(productId) ?? [],
100
+ deviations: deviationsByProduct.get(productId) ?? [],
101
+ findings,
102
+ });
103
+ }
104
+ // Conformance GATE (M-ADR 0191 §4.3 WS4): each archetype-expected tag must be
105
+ // declared covered OR deviated; an uncovered, undeviated tag is a
106
+ // deviation-visible finding. Satisfied tags are silent.
107
+ if (term.conformanceExpectations !== undefined && term.conformanceExpectations.length > 0) {
108
+ const declared = conformanceByProduct.get(productId) ?? new Set();
109
+ for (const tag of term.conformanceExpectations) {
110
+ if (declared.has(tag))
111
+ continue; // covered or deviated — satisfied
112
+ findings.add("coverage", `conformance: archetype "${archetype}" expects the "${tag}" conformance for ${productId}, ` +
113
+ `but it is neither declared covered (conformance.coverage) nor deviated (conformance.deviations) ` +
114
+ `— assert coverage or declare why it does not apply (M-ADR 0191 §4.3)`);
115
+ }
116
+ }
117
+ }
118
+ }
119
+ //# sourceMappingURL=expectations.js.map
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Application-archetype vocabulary resolution (M-ADR 0191 §4.2).
3
+ *
4
+ * The `application_archetype` manifest trait is a *classification* over a
5
+ * tiered vocabulary. This module resolves a declared value against the union
6
+ * of:
7
+ * - the **method tier** — `unclassified` only, built into sdd-cli (mirrors
8
+ * how the ADR 0173/0176 method-tier scaffold archetypes are built in);
9
+ * - the **reference-architecture / adopter bundle tiers** — seedless
10
+ * vocabulary files (`application-archetypes.yaml`) shipped in consumed
11
+ * bundles and extracted to the local bundle cache
12
+ * (`.sdd/bundle-cache/<bundle>@<version>/`);
13
+ * - an optional **adopter-local** extension file at
14
+ * `.sdd/application-archetypes.yaml` in the SDD repo.
15
+ *
16
+ * This deliberately does NOT reuse the ADR 0176 scaffold registry resolver
17
+ * (`scaffold/registry.ts`): that resolver is seed-oriented and drops any
18
+ * entry with no `seeds[]`, whereas a classification archetype is seedless by
19
+ * design. It DOES share the same bundle-cache distribution substrate — this
20
+ * is a distinct data type over a shared substrate, not a second distribution
21
+ * mechanism (M-ADR 0191 §6).
22
+ */
23
+ import { readFile, readdir } from "node:fs/promises";
24
+ import { join } from "node:path";
25
+ import { parse as parseYaml } from "yaml";
26
+ import { BUNDLE_CACHE_REL, VOCAB_FILE } from "../../sync/bundle-cache.js";
27
+ /** The sentinel that lives in the method tier — the back-compat default. */
28
+ export const UNCLASSIFIED = "unclassified";
29
+ export { VOCAB_FILE };
30
+ /** Adopter-local extension file, relative to the SDD repo root. */
31
+ export const ADOPTER_VOCAB_REL = join(".sdd", VOCAB_FILE);
32
+ const VALID_PRESENCE = new Set([
33
+ "required",
34
+ "typical",
35
+ "optional",
36
+ "absent-by-shape",
37
+ ]);
38
+ /** The built-in method tier (M-ADR 0191 §4.2 — `unclassified` only). */
39
+ const METHOD_TIER = [
40
+ {
41
+ id: UNCLASSIFIED,
42
+ tier: "method",
43
+ shape: "the back-compat default; a product that has not declared a shape",
44
+ source: "method",
45
+ },
46
+ ];
47
+ function parseVocabFile(raw, source) {
48
+ let parsed;
49
+ try {
50
+ parsed = parseYaml(raw);
51
+ }
52
+ catch {
53
+ return []; // malformed vocab — skip rather than break the build
54
+ }
55
+ if (parsed === null || typeof parsed !== "object")
56
+ return [];
57
+ if (!Array.isArray(parsed.archetypes))
58
+ return [];
59
+ const tier = typeof parsed.tier === "string" ? parsed.tier : "unknown";
60
+ const out = [];
61
+ for (const a of parsed.archetypes) {
62
+ if (a === null || typeof a !== "object")
63
+ continue;
64
+ const rec = a;
65
+ const id = typeof rec.id === "string" ? rec.id : undefined;
66
+ if (!id)
67
+ continue;
68
+ const expectedBindings = parseExpectedBindings(rec.expected_bindings);
69
+ const conformanceExpectations = parseStringList(rec.conformance);
70
+ out.push({
71
+ id,
72
+ tier,
73
+ source,
74
+ ...(typeof rec.layer === "string" && { layer: rec.layer }),
75
+ ...(typeof rec.shape === "string" && { shape: rec.shape }),
76
+ ...(expectedBindings.length > 0 && { expectedBindings }),
77
+ ...(conformanceExpectations.length > 0 && { conformanceExpectations }),
78
+ });
79
+ }
80
+ return out;
81
+ }
82
+ /** Parse an archetype's `expected_bindings:` list, dropping malformed entries
83
+ * (an unknown presence or a missing pattern is skipped, not fatal). */
84
+ function parseExpectedBindings(raw) {
85
+ if (!Array.isArray(raw))
86
+ return [];
87
+ const out = [];
88
+ for (const e of raw) {
89
+ if (e === null || typeof e !== "object")
90
+ continue;
91
+ const rec = e;
92
+ const pattern = typeof rec.pattern === "string" ? rec.pattern : undefined;
93
+ const presence = typeof rec.presence === "string" ? rec.presence : undefined;
94
+ if (!pattern || !presence || !VALID_PRESENCE.has(presence))
95
+ continue;
96
+ out.push({ pattern, presence: presence });
97
+ }
98
+ return out;
99
+ }
100
+ /** Parse a `conformance:` list of opaque string tags, dropping non-strings. */
101
+ function parseStringList(raw) {
102
+ if (!Array.isArray(raw))
103
+ return [];
104
+ return raw.filter((v) => typeof v === "string" && v.trim() !== "");
105
+ }
106
+ /** readdir(withFileTypes) that yields [] on any error, sorted by name. */
107
+ async function readDirSafe(dir) {
108
+ try {
109
+ const entries = await readdir(dir, { withFileTypes: true });
110
+ return entries.sort((a, b) => a.name.localeCompare(b.name));
111
+ }
112
+ catch {
113
+ return [];
114
+ }
115
+ }
116
+ /** Find every application-archetypes.yaml within a cached bundle (root + nested). */
117
+ async function findVocabFiles(dir, depth = 0) {
118
+ const found = [];
119
+ for (const e of await readDirSafe(dir)) {
120
+ if (e.isFile() && e.name === VOCAB_FILE) {
121
+ found.push(join(dir, e.name));
122
+ }
123
+ else if (e.isDirectory() && depth < 4) {
124
+ found.push(...(await findVocabFiles(join(dir, e.name), depth + 1)));
125
+ }
126
+ }
127
+ return found;
128
+ }
129
+ /**
130
+ * Discover the active vocabulary for an SDD repo: method tier ∪ every bundle
131
+ * vocab file in the bundle cache ∪ the adopter-local extension file.
132
+ */
133
+ export async function discoverVocabulary(targetRepo) {
134
+ const terms = [...METHOD_TIER];
135
+ // Bundle tiers (RA + adopter bundles both land in the cache).
136
+ const cacheRoot = join(targetRepo, BUNDLE_CACHE_REL);
137
+ for (const e of await readDirSafe(cacheRoot)) {
138
+ if (!e.isDirectory())
139
+ continue;
140
+ const bundleCacheDir = join(cacheRoot, e.name);
141
+ const at = e.name.lastIndexOf("@");
142
+ const bundleName = at > 0 ? e.name.slice(0, at) : e.name;
143
+ for (const vocabPath of await findVocabFiles(bundleCacheDir)) {
144
+ let raw;
145
+ try {
146
+ raw = await readFile(vocabPath, "utf8");
147
+ }
148
+ catch {
149
+ continue;
150
+ }
151
+ terms.push(...parseVocabFile(raw, bundleName));
152
+ }
153
+ }
154
+ // Adopter-local extension.
155
+ try {
156
+ const raw = await readFile(join(targetRepo, ADOPTER_VOCAB_REL), "utf8");
157
+ terms.push(...parseVocabFile(raw, "adopter-local"));
158
+ }
159
+ catch {
160
+ // absent — fine
161
+ }
162
+ return { terms, ids: new Set(terms.map((t) => t.id)) };
163
+ }
164
+ /**
165
+ * Resolve a declared archetype value against a vocabulary. `undefined`
166
+ * defaults to `unclassified` (always resolved). A declared value that is not
167
+ * in the vocabulary is unresolved (the caller emits an error finding).
168
+ */
169
+ export function resolveArchetype(declared, vocab) {
170
+ const value = declared ?? UNCLASSIFIED;
171
+ return {
172
+ value,
173
+ resolved: vocab.ids.has(value),
174
+ unclassified: value === UNCLASSIFIED,
175
+ };
176
+ }
177
+ //# sourceMappingURL=vocabulary.js.map
@@ -0,0 +1,66 @@
1
+ # Default archetype map — android (ADR 0169 §4.2). Method-owned.
2
+ #
3
+ # Android View + Material toolkit. These names are industry-standard (Google/AndroidX/Material)
4
+ # and identical across every Android app — never an adopter's bespoke vocabulary.
5
+ # Resolution order (ADR 0169 §4.3): adopter overlay override -> by_token -> by_suffix_heuristic -> default.
6
+ # Selected per screen by `platform: android` frontmatter.
7
+
8
+ map_version: "1.0.0"
9
+ platform: android
10
+
11
+ # Material/AndroidX vendor prefixes, stripped before token lookup.
12
+ strip_prefixes: [Material, Appcompat, AppCompat, Androidx]
13
+
14
+ by_token:
15
+ ConstraintLayout: stack
16
+ FrameLayout: stack
17
+ LinearLayout: stack
18
+ RelativeLayout: stack
19
+ CoordinatorLayout: scaffold
20
+ NestedScrollView: scroll
21
+ ScrollView: scroll
22
+ RecyclerView: list
23
+ ListView: list
24
+ GridLayout: grid
25
+ GridView: grid
26
+ ViewPager: paged-panes
27
+ ViewPager2: paged-panes
28
+ TabLayout: tab-strip
29
+ BottomNavigationView: tab-bar
30
+ NavigationView: sidebar
31
+ NavigationRailView: sidebar
32
+ Toolbar: app-bar
33
+ AppBarLayout: app-bar
34
+ ActionBar: app-bar
35
+ CollapsingToolbarLayout: app-bar
36
+ Button: button
37
+ MaterialButton: button
38
+ ImageButton: button
39
+ FloatingActionButton: fab
40
+ ExtendedFloatingActionButton: fab
41
+ Chip: tab
42
+ ChipGroup: button-group
43
+ ToggleButton: segmented-control
44
+ TextView: text
45
+ EditText: text
46
+ MaterialTextView: text
47
+ ImageView: image
48
+ ImageFilterView: image
49
+ CardView: card
50
+ MaterialCardView: card
51
+ Snackbar: banner
52
+ BottomSheetDialog: sheet
53
+ AlertDialog: modal
54
+ DialogFragment: modal
55
+
56
+ by_suffix_heuristic:
57
+ Layout: stack
58
+ View: panel
59
+ Button: button
60
+ Bar: app-bar
61
+ Dialog: modal
62
+ Card: card
63
+ Text: text
64
+ Image: image
65
+
66
+ default: panel
@@ -0,0 +1,58 @@
1
+ # Default archetype map — swiftui (ADR 0169 §4.2). Method-owned.
2
+ #
3
+ # Apple SwiftUI toolkit. Industry-standard Apple names, identical across every SwiftUI app —
4
+ # never an adopter's bespoke vocabulary.
5
+ # Resolution order (ADR 0169 §4.3): adopter overlay override -> by_token -> by_suffix_heuristic -> default.
6
+ # Selected per screen by `platform: swiftui` frontmatter.
7
+
8
+ map_version: "1.0.0"
9
+ platform: swiftui
10
+
11
+ strip_prefixes: []
12
+
13
+ by_token:
14
+ VStack: stack
15
+ HStack: row
16
+ ZStack: stack
17
+ LazyVStack: stack
18
+ LazyHStack: row
19
+ Grid: grid
20
+ LazyVGrid: grid
21
+ LazyHGrid: grid
22
+ ScrollView: scroll
23
+ List: list
24
+ Section: panel
25
+ ForEach: list-item # repeated child marker; rendered as a list-item row
26
+ Form: list
27
+ NavigationView: nav-bar
28
+ NavigationStack: nav-bar
29
+ NavigationSplitView: sidebar
30
+ NavigationLink: button
31
+ TabView: tab-bar
32
+ Button: button
33
+ Menu: list
34
+ Toolbar: app-bar
35
+ ToolbarItem: button
36
+ Text: text
37
+ Label: text
38
+ TextField: text
39
+ SecureField: text
40
+ Image: image
41
+ AsyncImage: image
42
+ Spacer: text # decorative; flagged by screen-styling-primitive-as-structure
43
+ Divider: text # decorative; flagged by screen-styling-primitive-as-structure
44
+ GroupBox: card
45
+ Sheet: sheet
46
+ Alert: modal
47
+ Picker: segmented-control
48
+
49
+ by_suffix_heuristic:
50
+ Stack: stack
51
+ View: panel
52
+ Button: button
53
+ Link: button
54
+ Field: text
55
+ Image: image
56
+ Text: text
57
+
58
+ default: panel
@@ -0,0 +1,48 @@
1
+ # Default archetype map — uikit (ADR 0169 §4.2). Method-owned.
2
+ #
3
+ # Apple UIKit toolkit. Industry-standard Apple names (UI- prefixed), identical across every
4
+ # UIKit app — never an adopter's bespoke vocabulary.
5
+ # Resolution order (ADR 0169 §4.3): adopter overlay override -> by_token -> by_suffix_heuristic -> default.
6
+ # Selected per screen by `platform: uikit` frontmatter.
7
+
8
+ map_version: "1.0.0"
9
+ platform: uikit
10
+
11
+ # The `UI` vendor prefix is stripped, so the `by_token` keys are unprefixed.
12
+ strip_prefixes: [UI, NS]
13
+
14
+ by_token:
15
+ View: panel
16
+ StackView: stack
17
+ ScrollView: scroll
18
+ TableView: list
19
+ TableViewCell: list-item
20
+ CollectionView: grid
21
+ CollectionViewCell: card
22
+ NavigationBar: nav-bar
23
+ NavigationController: nav-bar
24
+ TabBar: tab-bar
25
+ TabBarController: tab-bar
26
+ Toolbar: app-bar
27
+ BarButtonItem: button
28
+ Button: button
29
+ Label: text
30
+ TextField: text
31
+ TextView: text
32
+ ImageView: image
33
+ PageViewController: paged-panes
34
+ SegmentedControl: segmented-control
35
+ ActivityIndicatorView: text
36
+ AlertController: modal
37
+ Stack: stack
38
+
39
+ by_suffix_heuristic:
40
+ View: panel
41
+ Controller: panel
42
+ Button: button
43
+ Bar: app-bar
44
+ Label: text
45
+ Field: text
46
+ Image: image
47
+
48
+ default: panel