@sdd-method/maintain-cli 0.3.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.
- package/dist/lib/briefing/render.js +1 -2
- package/dist/lib/briefing/variables.js +1 -2
- package/dist/lib/catalogue/aggregate.js +35 -36
- package/dist/lib/catalogue/application-archetype/expectations.js +1 -1
- package/dist/lib/catalogue/archetype/wireframe.js +1 -1
- package/dist/lib/catalogue/build.js +17 -23
- package/dist/lib/catalogue/builders/application-conformance-coverage.js +1 -1
- package/dist/lib/catalogue/builders/c4-components.js +4 -5
- package/dist/lib/catalogue/builders/c4-containers.js +3 -3
- package/dist/lib/catalogue/builders/c4-external-systems.js +1 -1
- package/dist/lib/catalogue/builders/c4-l1-candidates.js +1 -1
- package/dist/lib/catalogue/builders/capabilities.js +2 -2
- package/dist/lib/catalogue/builders/capability-edges.js +4 -4
- package/dist/lib/catalogue/builders/capability-spec-walker.js +3 -3
- package/dist/lib/catalogue/builders/container-dependency.js +18 -19
- package/dist/lib/catalogue/builders/contracts.js +5 -6
- package/dist/lib/catalogue/builders/data-store-mapping.js +3 -3
- package/dist/lib/catalogue/builders/edge-label-merge.js +2 -2
- package/dist/lib/catalogue/builders/entities-manifest.js +1 -1
- package/dist/lib/catalogue/builders/entities-mermaid.js +3 -3
- package/dist/lib/catalogue/builders/entity-slug.js +11 -12
- package/dist/lib/catalogue/builders/event-catalog-walker.js +3 -3
- package/dist/lib/catalogue/builders/evidence-class.js +2 -2
- package/dist/lib/catalogue/builders/external-system-exemption.js +1 -1
- package/dist/lib/catalogue/builders/feature-contract.js +1 -3
- package/dist/lib/catalogue/builders/feature-grounding.js +1 -2
- package/dist/lib/catalogue/builders/feature-groups.js +0 -1
- package/dist/lib/catalogue/builders/feature-subscription-channels.js +2 -3
- package/dist/lib/catalogue/builders/feature-tree-walker.js +1 -1
- package/dist/lib/catalogue/builders/features.js +3 -4
- package/dist/lib/catalogue/builders/glossary-xref.js +1 -1
- package/dist/lib/catalogue/builders/persona-c4system.js +7 -7
- package/dist/lib/catalogue/builders/persona-container.js +14 -14
- package/dist/lib/catalogue/builders/personas.js +1 -2
- package/dist/lib/catalogue/builders/product-platform-dependencies.js +2 -2
- package/dist/lib/catalogue/builders/quality-scores.js +1 -1
- package/dist/lib/catalogue/builders/repositories.js +2 -2
- package/dist/lib/catalogue/builders/screen-tree.js +1 -1
- package/dist/lib/catalogue/builders/screens.js +3 -4
- package/dist/lib/catalogue/builders/system-external-dependency.js +7 -7
- package/dist/lib/catalogue/canonical-schema.yaml +23 -23
- package/dist/lib/catalogue/contract-path-resolution.js +3 -3
- package/dist/lib/catalogue/coverage-predicates.js +4 -4
- package/dist/lib/catalogue/coverage-run.js +1 -1
- package/dist/lib/catalogue/dedupe-keyed-tables.js +2 -3
- package/dist/lib/catalogue/ext-id.js +4 -4
- package/dist/lib/catalogue/feature-grounding-aliases-loader.js +1 -1
- package/dist/lib/catalogue/finding-identity.js +3 -3
- package/dist/lib/catalogue/freshness.js +1 -1
- package/dist/lib/catalogue/manifest-loader.js +2 -2
- package/dist/lib/catalogue/scaffold-delta.js +1 -1
- package/dist/lib/catalogue/schema-compat.js +1 -2
- package/dist/lib/catalogue/screen-validation-loader.js +1 -1
- package/dist/lib/catalogue/staleness-verdicts.js +2 -2
- package/dist/lib/catalogue/traceability-loader.js +2 -3
- package/dist/lib/ci-binding/index.js +106 -33
- package/dist/lib/ci-binding/module.js +1 -1
- package/dist/lib/lock-file.js +1 -1
- package/dist/lib/pack/reference-closure.js +3 -3
- package/dist/lib/package-version.js +1 -1
- package/dist/lib/snapshot/manifest.js +1 -1
- package/dist/lib/sync/adr-transitions.js +1 -1
- package/dist/lib/sync/diff.js +1 -1
- package/dist/lib/sync/index.js +8 -3
- package/dist/lib/validate/index.js +1 -1
- package/dist/lib/validate/plan.js +1 -1
- package/dist/lib/validate/yaml-parse.js +1 -1
- package/dist/maintain-version.json +2 -2
- package/dist/verbs/sync.js +1 -1
- package/dist/verify-version.json +2 -2
- package/package.json +2 -2
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Renderer for the briefing-layer templates per ADR 0148
|
|
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
|
|
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
|
|
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 `
|
|
188
|
-
// `sdd-
|
|
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
|
|
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,7 +1383,7 @@ 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
|
|
1386
|
+
// Camel humps split BEFORE lowercasing: most SDD ids are
|
|
1388
1387
|
// hyphenated, and `posGatewayFramework` must be able to declare
|
|
1389
1388
|
// `pos-gateway-framework`.
|
|
1390
1389
|
const norm = (s) => s
|
|
@@ -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
|
-
// (
|
|
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
|
|
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
|
-
//
|
|
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,11 +1551,11 @@ export function adjudicateExternalSystems(rowsByCatalogue, findings) {
|
|
|
1552
1551
|
}
|
|
1553
1552
|
return null;
|
|
1554
1553
|
};
|
|
1555
|
-
//
|
|
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
|
|
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
1561
|
// label must not defeat the match) and three-letter names like "OMS"
|
|
@@ -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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1711
|
+
`ambiguous tier contradiction, no referent asserted, row kept (M-ADR 0189 §4.4)`);
|
|
1713
1712
|
}
|
|
1714
1713
|
else {
|
|
1715
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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}]
|
|
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
|
|
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 "
|
|
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
|
-
*
|
|
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.)
|
|
17
|
-
* but empty `catalogues/` directory — useful as a
|
|
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.
|
|
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
|
|
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/
|
|
393
|
-
// maps to id "
|
|
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 (
|
|
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
|
|
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
|
|
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
|
-
//
|
|
650
|
-
//
|
|
651
|
-
//
|
|
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
|
-
//
|
|
1161
|
-
// (method governance, 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
|
|
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
|
|
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
|
|
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
|
|
8
|
-
// `
|
|
9
|
-
// (method governance, 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
|
|
48
|
-
// (slug = "asset-
|
|
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
|
-
//
|
|
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)
|
|
88
|
+
`or a misused technology field on a shared reference (correct it)`);
|
|
89
89
|
}
|
|
90
90
|
continue;
|
|
91
91
|
}
|
|
@@ -218,7 +218,7 @@ function slugify(s) {
|
|
|
218
218
|
.replace(/^-+|-+$/g, "");
|
|
219
219
|
}
|
|
220
220
|
/**
|
|
221
|
-
*
|
|
221
|
+
* Two technology strings flatly contradict when both are present and
|
|
222
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
|
*/
|
|
@@ -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 (
|
|
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
|
|
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
|
|
@@ -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
|
-
//
|
|
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
|
|
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 (
|
|
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 (
|
|
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
|
|
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-
|
|
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
|
-
* | `
|
|
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/
|
|
71
|
-
* → "
|
|
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);
|