@sdd-method/maintain-cli 0.2.0 → 0.4.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 (85) hide show
  1. package/NOTICE +1 -1
  2. package/dist/lib/briefing/render.js +1 -2
  3. package/dist/lib/briefing/variables.js +1 -2
  4. package/dist/lib/catalogue/aggregate.js +38 -39
  5. package/dist/lib/catalogue/application-archetype/expectations.js +1 -1
  6. package/dist/lib/catalogue/archetype/wireframe.js +1 -1
  7. package/dist/lib/catalogue/build.js +17 -23
  8. package/dist/lib/catalogue/builders/application-conformance-coverage.js +1 -1
  9. package/dist/lib/catalogue/builders/c4-components.js +4 -5
  10. package/dist/lib/catalogue/builders/c4-containers.js +4 -4
  11. package/dist/lib/catalogue/builders/c4-external-systems.js +1 -1
  12. package/dist/lib/catalogue/builders/c4-l1-candidates.js +2 -2
  13. package/dist/lib/catalogue/builders/capabilities.js +2 -2
  14. package/dist/lib/catalogue/builders/capability-edges.js +4 -4
  15. package/dist/lib/catalogue/builders/capability-spec-walker.js +3 -3
  16. package/dist/lib/catalogue/builders/container-dependency.js +19 -20
  17. package/dist/lib/catalogue/builders/contracts.js +5 -6
  18. package/dist/lib/catalogue/builders/data-store-mapping.js +4 -4
  19. package/dist/lib/catalogue/builders/edge-label-merge.js +2 -2
  20. package/dist/lib/catalogue/builders/entities-manifest.js +1 -1
  21. package/dist/lib/catalogue/builders/entities-mermaid.js +3 -3
  22. package/dist/lib/catalogue/builders/entity-slug.js +11 -12
  23. package/dist/lib/catalogue/builders/event-catalog-walker.js +3 -3
  24. package/dist/lib/catalogue/builders/evidence-class.js +2 -2
  25. package/dist/lib/catalogue/builders/external-system-exemption.js +1 -1
  26. package/dist/lib/catalogue/builders/feature-contract.js +1 -3
  27. package/dist/lib/catalogue/builders/feature-grounding.js +1 -2
  28. package/dist/lib/catalogue/builders/feature-groups.js +0 -1
  29. package/dist/lib/catalogue/builders/feature-subscription-channels.js +2 -3
  30. package/dist/lib/catalogue/builders/feature-tree-walker.js +1 -1
  31. package/dist/lib/catalogue/builders/features.js +3 -4
  32. package/dist/lib/catalogue/builders/glossary-xref.js +1 -1
  33. package/dist/lib/catalogue/builders/persona-c4system.js +7 -7
  34. package/dist/lib/catalogue/builders/persona-container.js +14 -14
  35. package/dist/lib/catalogue/builders/personas.js +1 -2
  36. package/dist/lib/catalogue/builders/product-platform-dependencies.js +2 -2
  37. package/dist/lib/catalogue/builders/quality-scores.js +1 -1
  38. package/dist/lib/catalogue/builders/repositories.js +2 -2
  39. package/dist/lib/catalogue/builders/screen-tree.js +2 -2
  40. package/dist/lib/catalogue/builders/screens.js +3 -4
  41. package/dist/lib/catalogue/builders/system-external-dependency.js +7 -7
  42. package/dist/lib/catalogue/canonical-schema.yaml +23 -23
  43. package/dist/lib/catalogue/contract-path-resolution.js +3 -3
  44. package/dist/lib/catalogue/coverage-predicates.js +4 -4
  45. package/dist/lib/catalogue/coverage-run.js +1 -1
  46. package/dist/lib/catalogue/dedupe-keyed-tables.js +0 -0
  47. package/dist/lib/catalogue/ext-id.js +4 -4
  48. package/dist/lib/catalogue/feature-grounding-aliases-loader.js +1 -1
  49. package/dist/lib/catalogue/finding-identity.js +3 -3
  50. package/dist/lib/catalogue/freshness.js +1 -1
  51. package/dist/lib/catalogue/manifest-loader.js +2 -2
  52. package/dist/lib/catalogue/scaffold-delta.js +1 -1
  53. package/dist/lib/catalogue/schema-compat.js +1 -2
  54. package/dist/lib/catalogue/scores-coverage.js +1 -1
  55. package/dist/lib/catalogue/screen-validation-loader.js +1 -1
  56. package/dist/lib/catalogue/service-contract-aliases-loader.js +1 -1
  57. package/dist/lib/catalogue/staleness-verdicts.js +2 -2
  58. package/dist/lib/catalogue/traceability-loader.js +2 -3
  59. package/dist/lib/ci-binding/index.js +147 -35
  60. package/dist/lib/ci-binding/module.js +74 -2
  61. package/dist/lib/hooks-seed/entries.js +43 -21
  62. package/dist/lib/hooks-seed/index.js +1 -1
  63. package/dist/lib/init/ci-workflow.js +26 -2
  64. package/dist/lib/lock-file.js +11 -1
  65. package/dist/lib/pack/builder.js +56 -4
  66. package/dist/lib/pack/index.js +1 -0
  67. package/dist/lib/pack/publish-transform.js +1 -1
  68. package/dist/lib/pack/reference-closure.js +3 -3
  69. package/dist/lib/package-version.js +1 -1
  70. package/dist/lib/snapshot/manifest.js +1 -1
  71. package/dist/lib/sync/adr-transitions.js +1 -1
  72. package/dist/lib/sync/diff.js +1 -1
  73. package/dist/lib/sync/index.js +34 -5
  74. package/dist/lib/sync/settings-merge.js +1 -1
  75. package/dist/lib/validate/index.js +1 -1
  76. package/dist/lib/validate/plan.js +7 -4
  77. package/dist/lib/validate/yaml-parse.js +1 -1
  78. package/dist/maintain-program.js +1 -1
  79. package/dist/maintain-version.json +2 -2
  80. package/dist/verbs/pack-build.js +22 -0
  81. package/dist/verbs/sync.js +18 -1
  82. package/dist/verbs/validate.js +6 -1
  83. package/dist/verify-program.js +1 -1
  84. package/dist/verify-version.json +2 -2
  85. package/package.json +2 -6
package/NOTICE CHANGED
@@ -2,7 +2,7 @@ sdd-cli
2
2
  Copyright 2026 Tom Foster
3
3
 
4
4
  This product includes software developed as part of the SDD method
5
- distribution (https://github.com/tomfosteruk/sdd-method).
5
+ distribution.
6
6
 
7
7
  Licensed under the Apache License, Version 2.0 (the "License");
8
8
  you may not use this file except in compliance with the License.
@@ -1,6 +1,5 @@
1
1
  /**
2
- * Renderer for the briefing-layer templates per ADR 0148 + Phase 4 of
3
- * `adopt-agent-instructions-briefing-layer.md`.
2
+ * Renderer for the briefing-layer templates per ADR 0148.
4
3
  *
5
4
  * Templates ship via the method baseline bundle and land at
6
5
  * `<target>/orchestration/templates/briefing/...` after sync's
@@ -1,6 +1,5 @@
1
1
  /**
2
- * Variable resolution for the briefing layer per ADR 0148 §4.1 + the
3
- * Phase 4 setup notes in `adopt-agent-instructions-briefing-layer.md`.
2
+ * Variable resolution for the briefing layer per ADR 0148 §4.1.
4
3
  *
5
4
  * The four variables are sourced from target-repo state:
6
5
  *
@@ -161,8 +161,7 @@ export async function runCatalogueAggregate(inputs, write) {
161
161
  // capability_service.service_id, emitting one row per resolved
162
162
  // (consuming-product, platform-capability) pair. Per-SDD builders
163
163
  // emit nothing for this catalogue; the aggregator owns it entirely.
164
- // See content/plans/v0-9-0-cohort-sweep-derived-bundle.md
165
- // §"Cross-SDD platform-capability-edge" + ADR 0134.
164
+ // See ADR 0134.
166
165
  const crossSddRows = deriveCrossSddPlatformCapability(rowsByCatalogue.get("product_platform_dependencies") ?? [], rowsByCatalogue.get("capability_service") ?? [], findings, rowsByCatalogue.get("capabilities") ?? []);
167
166
  rowsByCatalogue.set("cross_sdd_platform_capability", crossSddRows);
168
167
  // @rule aggregate-feature-integration-support
@@ -184,8 +183,8 @@ export async function runCatalogueAggregate(inputs, write) {
184
183
  // The same roots, additionally reachable by DIRECTORY BASENAME. An author who
185
184
  // writes a `platform/<name>/...` contract path is reading that name off the
186
185
  // filesystem, so it is the directory — which is routinely not the sdd_id (one
187
- // estate declares `experience-platform` in a directory named
188
- // `sdd-experience-platform`). Kept separate from sddRootsById on purpose: the
186
+ // estate declares `loyalty-platform` in a directory named
187
+ // `sdd-loyalty-platform`). Kept separate from sddRootsById on purpose: the
189
188
  // try-every-root pass must iterate each root ONCE, or a root reachable under two
190
189
  // names would count as two hits and report a false ambiguity.
191
190
  const sddRootAliases = new Map(sddRootsById);
@@ -714,7 +713,7 @@ export function dedupExternalSystems(rowsByCatalogue, findings) {
714
713
  // adopter-AUTHORED platform/domain ADRs untouched — a collision there is a genuine
715
714
  // error and MUST still fail the duplicate-PK check. Mirrors dedupExternalSystems.
716
715
  //
717
- // WHAT COUNTS AS METHOD-DISTRIBUTED (M-ADR 0208 / gov F-E): a row whose doc_path is
716
+ // WHAT COUNTS AS METHOD-DISTRIBUTED (M-ADR 0208): a row whose doc_path is
718
717
  // BUNDLE-MANAGED in the contributing SDD's method-baseline.lock. Provenance, not
719
718
  // path prefix. The earlier test `startsWith("docs/method/adr/")` was a proxy for
720
719
  // the same property and it failed the first time two platform-roots aggregated:
@@ -1384,9 +1383,9 @@ export function adjudicateExternalSystems(rowsByCatalogue, findings) {
1384
1383
  const exts = rowsByCatalogue.get("c4_external_systems") ?? [];
1385
1384
  const containers = rowsByCatalogue.get("c4_containers") ?? [];
1386
1385
  const deps = rowsByCatalogue.get("system_external_dependency") ?? [];
1387
- // Camel humps split BEFORE lowercasing (F11a): most SDD ids are
1388
- // hyphenated, and `bvfmGatewayFramework` must be able to declare
1389
- // `bvfm-gateway-framework`.
1386
+ // Camel humps split BEFORE lowercasing: most SDD ids are
1387
+ // hyphenated, and `posGatewayFramework` must be able to declare
1388
+ // `pos-gateway-framework`.
1390
1389
  const norm = (s) => s
1391
1390
  .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
1392
1391
  .toLowerCase()
@@ -1400,7 +1399,7 @@ export function adjudicateExternalSystems(rowsByCatalogue, findings) {
1400
1399
  .filter((s) => String(s["product_id"] ?? "") !== "")
1401
1400
  .map((s) => [String(s["product_id"]), String(s["id"])]));
1402
1401
  // Name index — names ambiguous across systems are excluded from matching
1403
- // (post-F7 duplicates are themselves a finding).
1402
+ // (duplicate system names are themselves a finding).
1404
1403
  const systemsByName = new Map();
1405
1404
  for (const s of systems) {
1406
1405
  const key = compact(String(s["name"] ?? ""));
@@ -1412,7 +1411,7 @@ export function adjudicateExternalSystems(rowsByCatalogue, findings) {
1412
1411
  else
1413
1412
  systemsByName.set(key, [String(s["id"])]);
1414
1413
  }
1415
- // Container indexes (F11b): keys (names + slug aliases) are confident
1414
+ // Container indexes: keys (names + slug aliases) are confident
1416
1415
  // only within the DECLARING SDD's own system; across the estate, only a
1417
1416
  // full display-name match counts, and only when it is unique — a
1418
1417
  // diagram-local alias like `emailService` must never bind to an
@@ -1497,7 +1496,7 @@ export function adjudicateExternalSystems(rowsByCatalogue, findings) {
1497
1496
  }
1498
1497
  const containerNameById = new Map(containers.map((c) => [String(c["id"]), String(c["name"] ?? "")]));
1499
1498
  const containerSystemById = new Map(containers.map((c) => [String(c["id"]), String(c["system_id"] ?? "")]));
1500
- // F12: containers follow the SAME precedence as systems — declared id
1499
+ // Containers follow the SAME precedence as systems — declared id
1501
1500
  // first, corroborated by the display name; the name alone only as a
1502
1501
  // unique fallback. 0.73.0 inverted this (name-equality only, declared id
1503
1502
  // discarded) and traded two false positives for five silent true
@@ -1552,14 +1551,14 @@ export function adjudicateExternalSystems(rowsByCatalogue, findings) {
1552
1551
  }
1553
1552
  return null;
1554
1553
  };
1555
- // F11a/F12 corroboration: a declared-id hit converts (or binds) only when
1554
+ // Corroboration: a declared-id hit converts (or binds) only when
1556
1555
  // the display name agrees with the target — diagram entry ids are
1557
1556
  // conventionally local, and an old mnemonic that happens to equal an
1558
1557
  // estate id must not convert silently. Comparison is TOKEN-level, not
1559
- // substring (F12): "Tachograph Service" must not corroborate `tacho`
1558
+ // substring: "Stockroom Service" must not corroborate `stock`
1560
1559
  // (shared prefix, different entity), while "Engine (telematics
1561
1560
  // processor)" must corroborate a container named "Engine" (a richer
1562
- // label must not defeat the match) and three-letter names like "CWS"
1561
+ // label must not defeat the match) and three-letter names like "OMS"
1563
1562
  // must work (no length floor). Corroborated = compact equality with the
1564
1563
  // target's name or id, or every token of either appearing among the
1565
1564
  // label's tokens. A declared id that corroborates is an assertion the
@@ -1593,7 +1592,7 @@ export function adjudicateExternalSystems(rowsByCatalogue, findings) {
1593
1592
  const corroborates = (ext, sysId) => corroboratesLabel(String(ext["name"] ?? ""), nameBysystem.get(sysId) ?? "", sddById.get(sysId) ?? "");
1594
1593
  // A bracketed qualifier ("Email Service [unknown]") is the author
1595
1594
  // declaring uncertainty — it must never be rendered as a confident
1596
- // referent (F11b/F12): held, with the uncertainty as the stated reason.
1595
+ // referent: held, with the uncertainty as the stated reason.
1597
1596
  const isUncertainLabel = (name) => /\[[^\]]*\]/.test(name);
1598
1597
  const keptExts = [];
1599
1598
  const dropExtIds = new Map();
@@ -1609,7 +1608,7 @@ export function adjudicateExternalSystems(rowsByCatalogue, findings) {
1609
1608
  let containerConverted = 0;
1610
1609
  let containerEdgesRewritten = 0;
1611
1610
  let systemFallbackEdges = 0;
1612
- // (F17) edges per candidate, split by whether the member build resolved
1611
+ // Edges per candidate, split by whether the member build resolved
1613
1612
  // the Rel's non-external endpoint to a local container — the datum that
1614
1613
  // decides whether a container-bound candidate can convert without
1615
1614
  // destroying an edge.
@@ -1650,24 +1649,24 @@ export function adjudicateExternalSystems(rowsByCatalogue, findings) {
1650
1649
  continue;
1651
1650
  }
1652
1651
  if (sys.method === "declared" && !corroborates(ext, sys.id)) {
1653
- // F11a: declared-id hit without name corroboration — a collision
1652
+ // Declared-id hit without name corroboration — a collision
1654
1653
  // between a diagram-local mnemonic and an estate id, not a
1655
1654
  // reference. Kept external, loudly.
1656
1655
  uncorroboratedKept++;
1657
1656
  outcomeByExtId.set(extId, "uncorroborated hold");
1658
1657
  keptExts.push(ext);
1659
1658
  findings.add("convention", `c4_external_systems: "${extId}" declared_id resolves to internal system "${sys.id}" ` +
1660
- `but the display name does not corroborate — kept external; align the entry id or name to convert (M-ADR 0189 §4.2 / F11a)`);
1659
+ `but the display name does not corroborate — kept external; align the entry id or name to convert (M-ADR 0189 §4.2)`);
1661
1660
  continue;
1662
1661
  }
1663
1662
  if (isUncertainLabel(String(ext["name"] ?? ""))) {
1664
- // Declared uncertainty never converts (F12) — held with the
1663
+ // Declared uncertainty never converts — held with the
1665
1664
  // uncertainty as the stated reason.
1666
1665
  uncertainHolds++;
1667
1666
  outcomeByExtId.set(extId, "uncertain hold");
1668
1667
  keptExts.push(ext);
1669
1668
  findings.add("coverage", `c4_external_systems: "${extId}" resolves to internal system "${sys.id}" but the label declares uncertainty — ` +
1670
- `held, not converted; resolve the uncertainty to convert (M-ADR 0189 §4.4 / F12)`);
1669
+ `held, not converted; resolve the uncertainty to convert (M-ADR 0189 §4.4)`);
1671
1670
  continue;
1672
1671
  }
1673
1672
  // Internal reference — convert.
@@ -1692,7 +1691,7 @@ export function adjudicateExternalSystems(rowsByCatalogue, findings) {
1692
1691
  uncorroboratedKept++;
1693
1692
  findings.add("convention", `c4_external_systems: "${extId}"${declaredBy !== "" ? ` (declared by ${declaredBy})` : ""} ` +
1694
1693
  `declared_id matches internal container "${containerRes.containerId}" but the display name does not corroborate — ` +
1695
- `no referent asserted; align the entry id or name to bind (M-ADR 0189 §4.2 / F12)`);
1694
+ `no referent asserted; align the entry id or name to bind (M-ADR 0189 §4.2)`);
1696
1695
  keptExts.push(ext);
1697
1696
  continue;
1698
1697
  }
@@ -1701,7 +1700,7 @@ export function adjudicateExternalSystems(rowsByCatalogue, findings) {
1701
1700
  // (already marked as an outcome above)
1702
1701
  findings.add("coverage", `c4_external_systems: "${extId}"${declaredBy !== "" ? ` (declared by ${declaredBy})` : ""} ` +
1703
1702
  `matches internal container "${containerRes.containerId}" but the label declares uncertainty — ` +
1704
- `held, not asserted; resolve the uncertainty to bind (M-ADR 0189 §4.4 / F12)`);
1703
+ `held, not asserted; resolve the uncertainty to bind (M-ADR 0189 §4.4)`);
1705
1704
  keptExts.push(ext);
1706
1705
  continue;
1707
1706
  }
@@ -1709,17 +1708,17 @@ export function adjudicateExternalSystems(rowsByCatalogue, findings) {
1709
1708
  ambiguousHolds++;
1710
1709
  findings.add("coverage", `c4_external_systems: "${extId}"${declaredBy !== "" ? ` (declared by ${declaredBy})` : ""} ` +
1711
1710
  `name-matches ${containerRes.candidates.length} internal containers [${containerRes.candidates.join(", ")}] — ` +
1712
- `ambiguous tier contradiction, no referent asserted, row kept (M-ADR 0189 §4.4 / F11b)`);
1711
+ `ambiguous tier contradiction, no referent asserted, row kept (M-ADR 0189 §4.4)`);
1713
1712
  }
1714
1713
  else {
1715
- // F17: a bound candidate CONVERTS — its edges rewrite onto
1714
+ // A bound candidate CONVERTS — its edges rewrite onto
1716
1715
  // container_dependency where the from-endpoint is known. Only a
1717
1716
  // candidate with unconvertible edges is kept, with the
1718
1717
  // contradiction finding stating exactly what blocks it. No edge
1719
1718
  // is ever destroyed to make a build quieter.
1720
1719
  containerTargetByExtId.set(extId, containerRes.containerId);
1721
1720
  const bucket = edgesByExtId.get(extId);
1722
- // F24 (F17's residue): a stuck edge whose from-SYSTEM differs from
1721
+ // A stuck edge whose from-SYSTEM differs from
1723
1722
  // the target container's owning system is a correct C4 L1 edge —
1724
1723
  // it rewrites onto system_system_dependency (from-system → owning
1725
1724
  // system). Only a stuck edge WITHIN the owning system truly blocks
@@ -1736,13 +1735,13 @@ export function adjudicateExternalSystems(rowsByCatalogue, findings) {
1736
1735
  tierContradictions++;
1737
1736
  findings.add("coverage", `c4_external_systems: "${extId}"${declaredBy !== "" ? ` (declared by ${declaredBy})` : ""} ` +
1738
1737
  `resolves to ${containerRes.kind === "own" ? "its declarer's own" : "an"} internal container "${containerRes.containerId}" — ` +
1739
- `tier contradiction, row kept: ${trulyStuck} same-system dependency edge(s) have no member-resolved from-container and would be L1 self-edges (M-ADR 0189 §4.4 / F17/F24)`);
1738
+ `tier contradiction, row kept: ${trulyStuck} same-system dependency edge(s) have no member-resolved from-container and would be L1 self-edges (M-ADR 0189 §4.4)`);
1740
1739
  }
1741
1740
  }
1742
1741
  keptExts.push(ext);
1743
1742
  }
1744
1743
  // Rewrite dependency edges whose external endpoint converted — to a
1745
- // system (§4.4) or, one tier down, to a container (F17).
1744
+ // system (§4.4) or, one tier down, to a container.
1746
1745
  const keptDeps = [];
1747
1746
  const newEdges = new Map();
1748
1747
  const newContainerEdges = new Map();
@@ -1753,7 +1752,7 @@ export function adjudicateExternalSystems(rowsByCatalogue, findings) {
1753
1752
  if (containerTarget !== undefined) {
1754
1753
  const fromContainer = String(dep["from_container_id"] ?? "");
1755
1754
  if (fromContainer === "") {
1756
- // F24: no member-resolved from-container. Across systems this is a
1755
+ // No member-resolved from-container. Across systems this is a
1757
1756
  // correct C4 L1 edge — from-system → the container's owning system.
1758
1757
  // Within the owning system it would be an L1 self-edge: kept, and
1759
1758
  // its candidate row was kept for it.
@@ -1782,7 +1781,7 @@ export function adjudicateExternalSystems(rowsByCatalogue, findings) {
1782
1781
  }
1783
1782
  if (fromContainer === containerTarget) {
1784
1783
  findings.add("coverage", `container_dependency: refused self-edge ${fromContainer} → ${containerTarget} ` +
1785
- `(external declaration resolved to the declaring container itself; M-ADR 0189 §4.4 / F17)`);
1784
+ `(external declaration resolved to the declaring container itself; M-ADR 0189 §4.4)`);
1786
1785
  continue;
1787
1786
  }
1788
1787
  const cid = `${fromContainer}__${containerTarget}`;
@@ -1829,13 +1828,13 @@ export function adjudicateExternalSystems(rowsByCatalogue, findings) {
1829
1828
  }
1830
1829
  edgesRewritten++;
1831
1830
  }
1832
- // §4.6 (WS15): exemptions vs outcomes, then the coverage predicate.
1831
+ // §4.6: exemptions vs outcomes, then the coverage predicate.
1833
1832
  // An exemption on a CONVERTED candidate contradicts the adjudication —
1834
1833
  // reported and dropped (it would also dangle). A kept candidate with no
1835
- // outcome and no exemption is the silent bucket L4 lived in: reported
1834
+ // outcome and no exemption is a silent bucket: reported
1836
1835
  // unverified, one finding per candidate, retired by declared identity
1837
1836
  // or by an asserted exemption.
1838
- // F15: the builder docstring — "each row retires exactly one unverified
1837
+ // The builder docstring — "each row retires exactly one unverified
1839
1838
  // finding, and nothing else" — is enforced here, not assumed. A kept
1840
1839
  // exemption MUST retire exactly one unverified finding; one that retires
1841
1840
  // nothing (its candidate converted, or already has an adjudication
@@ -1857,17 +1856,17 @@ export function adjudicateExternalSystems(rowsByCatalogue, findings) {
1857
1856
  const containerDrop = containerDroppedByExtId.get(target);
1858
1857
  if (containerDrop !== undefined) {
1859
1858
  findings.add("convention", `external_system_exemption: "${target}" is asserted genuinely external but adjudication resolved it to ` +
1860
- `internal container "${containerDrop}" — contradiction; exemption dropped (M-ADR 0189 §4.6 / F17)`);
1859
+ `internal container "${containerDrop}" — contradiction; exemption dropped (M-ADR 0189 §4.6)`);
1861
1860
  continue;
1862
1861
  }
1863
1862
  const outcome = outcomeByExtId.get(target);
1864
1863
  if (outcome !== undefined) {
1865
1864
  findings.add("convention", `external_system_exemption: "${target}" retires nothing — the candidate already has an adjudication ` +
1866
- `outcome (${outcome}); exemption dropped (M-ADR 0189 §4.6 / F15)`);
1865
+ `outcome (${outcome}); exemption dropped (M-ADR 0189 §4.6)`);
1867
1866
  continue;
1868
1867
  }
1869
1868
  if (!keptExtIds.has(target)) {
1870
- findings.add("convention", `external_system_exemption: "${target}" names no kept external-system candidate — exemption dropped (M-ADR 0189 §4.6 / F15)`);
1869
+ findings.add("convention", `external_system_exemption: "${target}" names no kept external-system candidate — exemption dropped (M-ADR 0189 §4.6)`);
1871
1870
  continue;
1872
1871
  }
1873
1872
  keptExemptions.push(ex);
@@ -2221,7 +2220,7 @@ async function fileExists(absPath) {
2221
2220
  *
2222
2221
  * ASYNC shared-stream dependencies (M-ADR 0192) — a dependency whose
2223
2222
  * `contract_type` is in the async set (`event`/`event-stream`/`stream`) — have
2224
- * no single realiser service and carry an empty `target_service_id`. The F18
2223
+ * no single realiser service and carry an empty `target_service_id`. The
2225
2224
  * synchronous realiser-join does not apply to them: instead they resolve via
2226
2225
  * the capability leg (`target_capability_id` against any `capability_service`
2227
2226
  * row for that capability) and emit the (consuming-product, platform-capability)
@@ -2309,7 +2308,7 @@ export function deriveCrossSddPlatformCapability(productPlatformDeps, capability
2309
2308
  if (consumingProductId === "")
2310
2309
  continue;
2311
2310
  // M-ADR 0192: an async shared-stream dependency has no realiser service.
2312
- // The F18 pair-join cannot apply — resolve via the capability leg and emit
2311
+ // The pair-join cannot apply — resolve via the capability leg and emit
2313
2312
  // the (consuming-product, platform-capability) edge with an empty service.
2314
2313
  if (isAsyncContractType(contractType)) {
2315
2314
  if (targetCapabilityId === "")
@@ -2351,7 +2350,7 @@ export function deriveCrossSddPlatformCapability(productPlatformDeps, capability
2351
2350
  findings.add("orphan", `cross_sdd_platform_capability: ${consumingSddId}/${consumingProductId} declares target_service_id="${targetServiceId}" but no capability_service row resolves it ecosystem-wide`);
2352
2351
  continue;
2353
2352
  }
2354
- // F18: validate the declared PAIR, not each column alone. Both columns
2353
+ // Validate the declared PAIR, not each column alone. Both columns
2355
2354
  // resolving independently while no capability_service row binds them
2356
2355
  // is a false pairing accepted in silence — the evidence table for the
2357
2356
  // pair exists and was never consulted.
@@ -2364,7 +2363,7 @@ export function deriveCrossSddPlatformCapability(productPlatformDeps, capability
2364
2363
  .join(", ");
2365
2364
  findings.add("divergence", `product_platform_dependencies: ${consumingSddId}/${consumingProductId} declares the pair ` +
2366
2365
  `(target_capability_id="${declaredCapability}", target_service_id="${targetServiceId}") but no ` +
2367
- `capability_service row binds them — the service resolves to [${actual}] (F18)`);
2366
+ `capability_service row binds them — the service resolves to [${actual}]`);
2368
2367
  }
2369
2368
  }
2370
2369
  for (const match of matches) {
@@ -101,7 +101,7 @@ export function checkDataPlaneOverlayAtAggregation(inputs) {
101
101
  findings,
102
102
  });
103
103
  }
104
- // Conformance GATE (M-ADR 0191 §4.3 WS4): each archetype-expected tag must be
104
+ // Conformance GATE (M-ADR 0191 §4.3): each archetype-expected tag must be
105
105
  // declared covered OR deviated; an uncovered, undeviated tag is a
106
106
  // deviation-visible finding. Satisfied tags are silent.
107
107
  if (term.conformanceExpectations !== undefined && term.conformanceExpectations.length > 0) {
@@ -343,7 +343,7 @@ function humaniseName(name, prefixes) {
343
343
  let s = name;
344
344
  for (const p of prefixes) {
345
345
  // Strip only at a PascalCase boundary (next char uppercase) so a short prefix
346
- // like "St" peels "StLayout" → "Layout" but never "Status" → "atus".
346
+ // like "Ui" peels "UiLayout" → "Layout" but never "Uint" → "nt".
347
347
  if (s.length > p.length && s.startsWith(p) && /[A-Z]/.test(s.charAt(p.length))) {
348
348
  s = s.slice(p.length);
349
349
  break;
@@ -4,8 +4,7 @@
4
4
  * Per ADR 0134 §4.6, the canonical builder reads method-defined declarative
5
5
  * inputs and emits `catalogues/*.csv` matching the canonical schema.
6
6
  *
7
- * This is Phase 1 of the canonical-builder implementation per
8
- * adopt-canonical-catalogue-builder.md Step F. Phase 1 ships:
7
+ * The orchestrator provides:
9
8
  * - manifest loading + schema-version compat check,
10
9
  * - schema loading,
11
10
  * - headers-only emission for every catalogue declared in the schema,
@@ -13,9 +12,9 @@
13
12
  * - findings + row-count reporting.
14
13
  *
15
14
  * Per-source discoverers (domains, repos, services, capabilities, features,
16
- * etc.) land in subsequent phases. Until then the builder produces a valid
17
- * but empty `catalogues/` directory — useful as a smoke test that the
18
- * infrastructure works end-to-end on any SDD repo.
15
+ * etc.) live in `builders/`. With none of their inputs present the builder
16
+ * still produces a valid but empty `catalogues/` directory — useful as a
17
+ * smoke test that the infrastructure works end-to-end on any SDD repo.
19
18
  */
20
19
  import { checkCapabilityServicePopulation } from "./population-checks.js";
21
20
  import { basename, join, relative, resolve } from "node:path";
@@ -73,8 +72,7 @@ export async function runCatalogueBuild(inputs, write) {
73
72
  // more than one walker; each walker's own seen-set only dedupes intra-walker.
74
73
  // This union-dedupes identical rows across sources and hard-fails the build
75
74
  // on same-PK/different-payload, so the per-SDD catalogue is set-correct over
76
- // its declared PK before aggregation ever sees it. See
77
- // `coordination/feature-edge-cross-source-duplicate-coordination.md`.
75
+ // its declared PK before aggregation ever sees it.
78
76
  const dedupe = dedupeKeyedTables(schema, rowsByCatalogue, findings);
79
77
  // 3b. Population collapse, before anything is overwritten. A whole score family can
80
78
  // vanish while its catalogue keeps other rows: one estate's 71 traceability-manifest
@@ -293,7 +291,7 @@ async function collectRows(schema, manifest, findings, sddPath, snapshotStore) {
293
291
  // is the mistake M-ADR 0191 §4.1 / the 0.92.1 membership move corrected.
294
292
  out.set("application_data_plane_bindings", buildApplicationDataPlaneBindings({ manifest, findings }));
295
293
  out.set("application_data_plane_deviations", buildApplicationDataPlaneDeviations({ manifest, findings }));
296
- // Conformance coverage (M-ADR 0191 §4.3 WS4) — the product's declared coverage
294
+ // Conformance coverage (M-ADR 0191 §4.3) — the product's declared coverage
297
295
  // of / deviations from its archetype's conformance expectations. The
298
296
  // conformance GATE that reads these runs at aggregation tier (like the binding
299
297
  // overlay), where the reference-architecture `conformance` tags resolve.
@@ -389,8 +387,8 @@ async function collectRows(schema, manifest, findings, sddPath, snapshotStore) {
389
387
  // Slug → capability-id aliases for feature-grounding path resolution.
390
388
  // The grounding walker derives a capability slug from the design dir
391
389
  // name (docs/domains/<dom>/design/<slug>/), but a capability's declared
392
- // id may legitimately differ from its path slug — e.g. design/alerts-rules
393
- // maps to id "alerts-rules-engine". Map each capability's spec +
390
+ // id may legitimately differ from its path slug — e.g. design/gift-card-rules
391
+ // maps to id "gift-card-rules-engine". Map each capability's spec +
394
392
  // feature-directory basename to its id so the walker resolves these
395
393
  // without forcing a directory rename. Only non-identity, non-colliding
396
394
  // slugs are aliased (first manifest wins).
@@ -412,7 +410,7 @@ async function collectRows(schema, manifest, findings, sddPath, snapshotStore) {
412
410
  const knownServiceIds = new Set(services.map((s) => String(s["id"])));
413
411
  // C4 L1 external systems are built here (rather than at step 15b) so the
414
412
  // capability_service builder can categorise unresolved `services[].id`
415
- // findings — an id naming a known external system (authzed, workos, …) is
413
+ // findings — an id naming a known external system (an auth provider, an identity SaaS, …) is
416
414
  // a convention finding, not an orphan. The rows are emitted at their usual
417
415
  // place in step 15b; only the computation is hoisted.
418
416
  const c4External = await buildC4ExternalSystems({
@@ -593,8 +591,7 @@ async function collectRows(schema, manifest, findings, sddPath, snapshotStore) {
593
591
  // emit even when every grounding row collides on feature id with an
594
592
  // earlier source (`feature-tree` / `feature-spec`). Pre-0.30.2 the
595
593
  // conditional above gated feature_repo on the row emission, dropping
596
- // the edges in the all-collide case. Fixed per
597
- // `coordination/feature-grounding-collision-edges-coordination.md`.
594
+ // the edges in the all-collide case. Fixed.
598
595
  out.set("feature_repo", [
599
596
  ...(out.get("feature_repo") ?? []),
600
597
  ...grounding.featureRepoRows,
@@ -635,8 +632,7 @@ async function collectRows(schema, manifest, findings, sddPath, snapshotStore) {
635
632
  // emit even when every capability-spec row collides on feature id with an
636
633
  // earlier source. Pre-fix the conditional above gated these edges on the
637
634
  // row emission, dropping them in the all-collide case (same two-level bug
638
- // feature-grounding had pre-0.30.2). Fixed per
639
- // `coordination/capability-spec-collision-edges-coordination.md`.
635
+ // feature-grounding had pre-0.30.2). Fixed.
640
636
  out.set("feature_repo", [
641
637
  ...(out.get("feature_repo") ?? []),
642
638
  ...capabilitySpec.featureRepoRows,
@@ -646,10 +642,9 @@ async function collectRows(schema, manifest, findings, sddPath, snapshotStore) {
646
642
  ...capabilitySpec.featureServiceRows,
647
643
  ]);
648
644
  // 10b-ter. feature_service — application-product Feature Services table.
649
- // WS0c (catalogue-coverage-invariants.md) found this table authored and
650
- // unread: 12 SDDs carry a populated `## Feature Services` table in
651
- // feature-tree.md, 155 declared rows / 385 feature×service pairs, 0
652
- // emitted, because the two extractors above only ever walk
645
+ // A coverage audit found this table authored and unread: 12 SDDs carry a
646
+ // populated `## Feature Services` table in feature-tree.md, 155 declared
647
+ // rows / 385 feature×service pairs, 0 emitted, because the two extractors above only ever walk
653
648
  // feature-grounding.md / capability-spec paths (platform / integration
654
649
  // profiles). Runs last so knownFeatureIds reflects every prior source.
655
650
  // No `source` column exists on feature_service (schema has none), so rows
@@ -1157,8 +1152,7 @@ async function collectRows(schema, manifest, findings, sddPath, snapshotStore) {
1157
1152
  // declared (repo-config depends_on) and derived (per-component sharedLibraries).
1158
1153
  out.set("repo_dependency", await buildRepoDependencies({ sddPath, configs: reposResult.configs, knownRepoIds: knownRepoIdsFinal, findings }));
1159
1154
  // 15. C4 systems + containers + components (L1 + L2 + L3). The L3 walker
1160
- // shipped per `coordination/c4-l3-walker-and-path-convention-coordination.md`
1161
- // (sdd-method-gov, 2026-05-29) — forward-method authoring path reading
1155
+ // is the forward-method authoring path reading
1162
1156
  // `c4-l3-*.mermaid.md` files alongside the L2 layout. The earlier Path-B
1163
1157
  // deferral assumed reverse-method code-walking; this walker is the
1164
1158
  // forward-method analogue, granularity-agnostic (per-container or
@@ -1218,7 +1212,7 @@ async function collectRows(schema, manifest, findings, sddPath, snapshotStore) {
1218
1212
  externalIdByEntryId: c4External.idByEntryId,
1219
1213
  containerIdByEntryId,
1220
1214
  }));
1221
- // container_dependency member tier (F19): the C4-correct authoring —
1215
+ // container_dependency member tier: the C4-correct authoring —
1222
1216
  // Rel(container_a, container_b) — emits its edge here; the aggregator's
1223
1217
  // adjudication leg merges rather than being the only source.
1224
1218
  out.set("container_dependency", await buildContainerDependency({
@@ -1228,7 +1222,7 @@ async function collectRows(schema, manifest, findings, sddPath, snapshotStore) {
1228
1222
  containerIdByEntryId,
1229
1223
  externalIdByEntryId: c4External.idByEntryId,
1230
1224
  }));
1231
- // persona_container per M-ADR 0189 F26 §3 (schema 2.34.0) — the
1225
+ // persona_container per M-ADR 0189 (schema 2.34.0) — the
1232
1226
  // L2-granularity companion to persona_c4system.
1233
1227
  out.set("persona_container", await buildPersonaContainer({
1234
1228
  sddPath,
@@ -1,4 +1,4 @@
1
- // application_conformance_coverage builder (M-ADR 0191 §4.3 overlay, WS4).
1
+ // application_conformance_coverage builder (M-ADR 0191 §4.3 overlay).
2
2
  //
3
3
  // Emits one row per conformance tag the product declares about its archetype's
4
4
  // conformance expectations: `status=covered` (its suite exercises the tag) or
@@ -4,9 +4,8 @@
4
4
  // - docs/platform/diagrams/c4-l3-*.mermaid.md
5
5
  // - docs/c4-l3-*.mermaid.md (per-product, application/integration SDDs)
6
6
  //
7
- // Mirrors c4-containers.ts at one level down. Source convention per
8
- // `coordination/c4-l3-walker-and-path-convention-coordination.md`
9
- // (sdd-method-gov, 2026-05-29). The walker is granularity-agnostic — both
7
+ // Mirrors c4-containers.ts at one level down, reading the method's
8
+ // `c4-l3-*.mermaid.md` source convention. The walker is granularity-agnostic — both
10
9
  // per-container files (one `Container_Boundary` block per file) and
11
10
  // per-domain rollup files (multiple `Container_Boundary` blocks per file)
12
11
  // produce identical row output. Adopters pick the file shape that suits
@@ -44,8 +43,8 @@ export async function buildC4Components(inputs) {
44
43
  return [];
45
44
  // Index containers by suffix-after-system-prefix for slug-of-name matching.
46
45
  // c4_containers.id shape per c4-containers.ts:93 is `{systemId}-{slug}`.
47
- // The suffix-match lets us resolve `Container_Boundary(_, "Asset Twin")`
48
- // (slug = "asset-twin") to `{anything}-asset-twin`.
46
+ // The suffix-match lets us resolve `Container_Boundary(_, "Asset Register")`
47
+ // (slug = "asset-register") to `{anything}-asset-register`.
49
48
  const containerBySuffix = new Map();
50
49
  for (const row of inputs.c4ContainerRows) {
51
50
  const id = String(row["id"] ?? "");
@@ -76,7 +76,7 @@ export async function buildC4Containers(inputs) {
76
76
  }
77
77
  const collapsed = rowById.get(id);
78
78
  if (collapsed !== undefined) {
79
- // F21: dedup-by-id is first-seen and was SILENT — two containers
79
+ // Dedup-by-id is first-seen and was SILENT — two containers
80
80
  // sharing a display name collapse into one row, and every edge
81
81
  // referencing either binds to the merge. When their technology
82
82
  // strings flatly contradict, that is two different things wearing
@@ -85,7 +85,7 @@ export async function buildC4Containers(inputs) {
85
85
  inputs.findings.add("convention", `c4_containers: two containers share the display name "${entry.name}" with contradictory ` +
86
86
  `technologies ("${String(collapsed["technology"])}" vs "${entry.technology}") — collapsed ` +
87
87
  `into one row "${id}" — either different referents wearing one name (rename one) ` +
88
- `or a misused technology field on a shared reference (correct it) (F21)`);
88
+ `or a misused technology field on a shared reference (correct it)`);
89
89
  }
90
90
  continue;
91
91
  }
@@ -218,8 +218,8 @@ function slugify(s) {
218
218
  .replace(/^-+|-+$/g, "");
219
219
  }
220
220
  /**
221
- * F21: two technology strings flatly contradict when both are present and
222
- * share no token — ".NET 6 (in Linton)" vs "Java" needs no NLP. Absent or
221
+ * Two technology strings flatly contradict when both are present and
222
+ * share no token — ".NET 6 (in Kestrel)" vs "Java" needs no NLP. Absent or
223
223
  * overlapping strings are NOT a contradiction (silence, not a guess).
224
224
  */
225
225
  export function techContradicts(a, b) {
@@ -134,7 +134,7 @@ function parseExternals(content) {
134
134
  /**
135
135
  * Every C4 diagram file an SDD can carry (all L1 candidates incl.
136
136
  * domain-scoped, plus every L2), scope-tagged. Shared with
137
- * system-external-dependency (F11c: that builder kept a private,
137
+ * system-external-dependency (that builder kept a private,
138
138
  * pre-0.30.1-ordered copy MISSING the domain-scoped path — the fourth
139
139
  * divergent copy of this list, and a silent drop inside adjudication:
140
140
  * a Rel in a domain-scoped L1 was never harvested, so a converted
@@ -5,7 +5,7 @@
5
5
  // Under federation a sibling SDD typically carries a syndicated copy of the
6
6
  // platform-root's L1 at `docs/platform/diagrams/c4-l1-context.mermaid.md`
7
7
  // for cross-reference, AND may author its own L1. The sibling's own L1 must
8
- // outrank the syndicated copy — and, per F7/WS8, the syndicated copy must
8
+ // outrank the syndicated copy — and the syndicated copy must
9
9
  // not source a sibling's identity AT ALL: on two measured estates roughly
10
10
  // half of all systems carried the platform's name and description because
11
11
  // the fallback fired for every sibling that authored no own L1. A sibling
@@ -20,7 +20,7 @@
20
20
  // personas instead of the sibling's. c4-external-systems UNIONS every
21
21
  // existing L1 file rather than picking one, so candidate order is irrelevant
22
22
  // there; it shares these paths only to avoid a divergent copy.
23
- // See sdd-method-gov/coordination/persona-c4system-l1-priority-bug-coordination.md.
23
+ // See the method's coordination record persona-c4system-l1-priority-bug.
24
24
  import { readdir, stat } from "node:fs/promises";
25
25
  import { join } from "node:path";
26
26
  /** Own-L1 candidates usable by ANY SDD, in priority order. */
@@ -146,12 +146,12 @@ export async function buildCapabilities(inputs) {
146
146
  if (specRel) {
147
147
  usedSpecPaths.add(specRel);
148
148
  const specAbs = join(inputs.sddPath, specRel);
149
- // F22: an emitted spec_path must exist — a dangling path silently
149
+ // An emitted spec_path must exist — a dangling path silently
150
150
  // produced an empty name/description while the column pointed at
151
151
  // nothing. The row still emits (the capability is declared); the
152
152
  // dangle is loud.
153
153
  if (!(await fileExists(specAbs))) {
154
- inputs.findings.add("coverage", `${m.relPath}: capability "${cap.id}" declares capability_spec "${specRel}" which does not exist — name/description left empty, spec_path emitted dangling (F22)`);
154
+ inputs.findings.add("coverage", `${m.relPath}: capability "${cap.id}" declares capability_spec "${specRel}" which does not exist — name/description left empty, spec_path emitted dangling`);
155
155
  }
156
156
  // M-ADR 0211 §4.2 — the manifest is a declaration and its row is correct. The finding
157
157
  // fires ONLY on a CONTRADICTION between two declaration surfaces: this manifest
@@ -5,7 +5,7 @@
5
5
  // canonical schema declares capability_id and the target column as
6
6
  // required FKs — so each builder skips entries whose target doesn't
7
7
  // resolve in the known-target set passed in.
8
- // capability_service is the exception (F4, schema 2.29.0): its service_id
8
+ // capability_service is the exception (schema 2.29.0): its service_id
9
9
  // FK is external, and an entry marked `owner_sdd:` declares a cross-SDD
10
10
  // reference — the edge is emitted despite not resolving locally and is
11
11
  // validated hard at aggregation against the owning sibling's rows.
@@ -40,7 +40,7 @@ export function buildCapabilityService(inputs) {
40
40
  if (!inputs.knownServiceIds.has(sid)) {
41
41
  // Never drop silently (61 missing edges sat invisible in the field
42
42
  // until a manual sweep). Resolution order:
43
- // - `owner_sdd:` marked (F4, schema 2.29.0) — a DECLARED cross-SDD
43
+ // - `owner_sdd:` marked (schema 2.29.0) — a DECLARED cross-SDD
44
44
  // reference (e.g. the service moved to a sibling at a carve).
45
45
  // Emit the edge; the external FK logs it and aggregation
46
46
  // validates it hard against the sibling's rows.
@@ -174,7 +174,7 @@ export function buildCapabilityContract(inputs) {
174
174
  const contractId = resolveContractPath(path, inputs.contractIdByPath);
175
175
  if (contractId === "") {
176
176
  // Never drop silently. This guard dropped every unresolvable entry with no
177
- // trace until WS2 of the coverage-invariants work measured it: 62 of 188
177
+ // trace until it was measured: 62 of 188
178
178
  // declared entries across two estates, and NOT ONE of them dangling — every
179
179
  // one named a real artefact. So the resolution failure is the interesting
180
180
  // fact, and the reader needs it.
@@ -236,7 +236,7 @@ function resolveContractPath(path, index) {
236
236
  const direct = index.get(cleaned);
237
237
  if (direct !== undefined)
238
238
  return direct;
239
- // Suffix: last two parts (e.g. "asset/asset-twin.yaml")
239
+ // Suffix: last two parts (e.g. "asset/asset-register.yaml")
240
240
  const parts = cleaned.split(sep);
241
241
  if (parts.length >= 2) {
242
242
  const suffix = parts.slice(-2).join("/");
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * | Feature | Implementation | Grounding Doc |
8
8
  * |---|---|---|
9
- * | `alerts-module` | `alerts` Vuex module + `src/components/alerts` | docs/.../feature-grounding.md |
9
+ * | `banners-module` | `banners` store module + `src/components/banners` | docs/.../feature-grounding.md |
10
10
  *
11
11
  * Path convention (per ADR 0145 §4.3):
12
12
  * - platform-profile: `docs/domains/<dom>/product/<capability>.md`
@@ -67,8 +67,8 @@ export async function findCapabilitySpecFiles(sddPath) {
67
67
  * Resolve the capability id from a capability spec file's absolute
68
68
  * path. Returns the filename stem (`.md` stripped).
69
69
  *
70
- * docs/domains/applications/product/alerts-and-notifications.md
71
- * → "alerts-and-notifications"
70
+ * docs/domains/storefront/product/banners-and-toasts.md
71
+ * → "banners-and-toasts"
72
72
  */
73
73
  export function capabilityIdFromSpecPath(absPath, sddPath) {
74
74
  const rel = relative(sddPath, absPath);