@topogram/cli 0.3.54 → 0.3.56

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 (45) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/package.json +1 -1
  3. package/src/adoption/review-groups.js +3 -3
  4. package/src/agent-ops/query-builders.js +34 -34
  5. package/src/archive/schema.js +1 -1
  6. package/src/cli.js +173 -20
  7. package/src/generator/adapters.js +23 -7
  8. package/src/generator/context/domain-coverage.js +2 -2
  9. package/src/generator/context/shared.js +6 -22
  10. package/src/generator/context/slice.js +10 -10
  11. package/src/generator/docs.js +1 -1
  12. package/src/generator/registry.js +1 -1
  13. package/src/generator/runtime/app-bundle.js +8 -6
  14. package/src/generator/runtime/compile-check.js +7 -5
  15. package/src/generator/runtime/deployment.js +6 -4
  16. package/src/generator/runtime/environment.js +31 -28
  17. package/src/generator/runtime/shared.js +53 -39
  18. package/src/generator/surfaces/contracts.js +1 -1
  19. package/src/generator/surfaces/databases/index.js +5 -3
  20. package/src/generator/surfaces/databases/lifecycle-shared.js +1 -1
  21. package/src/generator/surfaces/databases/postgres/drizzle.js +1 -1
  22. package/src/generator/surfaces/databases/postgres/prisma.js +1 -1
  23. package/src/generator/surfaces/databases/shared.js +3 -3
  24. package/src/generator/surfaces/databases/sqlite/prisma.js +1 -1
  25. package/src/generator/surfaces/index.js +5 -3
  26. package/src/generator/surfaces/services/index.js +10 -6
  27. package/src/generator/surfaces/services/persistence-wiring.js +1 -1
  28. package/src/generator/surfaces/services/server-contract.js +1 -1
  29. package/src/generator/surfaces/shared.js +1 -1
  30. package/src/generator/surfaces/web/index.js +5 -3
  31. package/src/generator/surfaces/web/ui-surface-contract.js +1 -1
  32. package/src/generator/widget-conformance.js +6 -6
  33. package/src/generator/widgets.js +1 -1
  34. package/src/generator-policy.js +10 -10
  35. package/src/import/core/runner.js +60 -50
  36. package/src/project-config.js +5 -42
  37. package/src/proofs/contract-audit.js +1 -1
  38. package/src/realization/backend/build-backend-runtime-realization.js +3 -3
  39. package/src/realization/ui/build-ui-shared-realization.js +9 -9
  40. package/src/realization/ui/build-web-realization.js +3 -3
  41. package/src/reconcile/journeys.js +1 -1
  42. package/src/resolver/enrich/widget.js +2 -2
  43. package/src/resolver/index.js +4 -23
  44. package/src/validator/index.js +10 -10
  45. package/src/workflows.js +49 -49
package/CHANGELOG.md CHANGED
@@ -5,6 +5,24 @@
5
5
  - Install package-backed generator dependencies during `topogram template check`
6
6
  before starter validation.
7
7
 
8
+ ## 0.3.55 - 2026-05-07
9
+
10
+ - Complete the public DSL cleanup from `component` to `widget`: normalized
11
+ graph/context output now uses `widget`, `widgetBindings`, `widgetContract`,
12
+ and widget report target names without publishing component compatibility
13
+ aliases.
14
+ - Reconcile/adoption import reports now emit `widgets` in candidate model
15
+ bundles, evidence, summaries, and promoted item plans. Existing imported
16
+ workspaces with `candidates.ui.components` are still accepted as a read-only
17
+ fallback, but new import output writes `candidates.ui.widgets`.
18
+ - Old CLI names fail with explicit rename guidance instead of silently running:
19
+ `topogram component`, `--component`, `ui-component-contract`,
20
+ `component-conformance-report`, and `component-behavior-report`.
21
+ - Generator policy diagnostics now describe topology entries as runtimes
22
+ (`runtimeId`) instead of components.
23
+ - Tighten boundary tests so old public DSL vocabulary is allowed only in
24
+ migration guidance and explicit rename diagnostics.
25
+
8
26
  ## 0.3.16 - 2026-05-03
9
27
 
10
28
  - Install package-backed generator dependencies during `topogram template check`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topogram/cli",
3
- "version": "0.3.54",
3
+ "version": "0.3.56",
4
4
  "description": "Topogram CLI for checking Topogram workspaces and generating app bundles.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -10,7 +10,7 @@ export function buildProjectionReviewGroups(items) {
10
10
  id: dependency.id,
11
11
  projection_id: dependency.projection_id,
12
12
  kind: dependency.kind,
13
- platform: dependency.platform,
13
+ projection_type: dependency.projection_type,
14
14
  reason: dependency.reason,
15
15
  items: []
16
16
  });
@@ -47,7 +47,7 @@ export function buildUiReviewGroups(items) {
47
47
  id: dependency.id,
48
48
  projection_id: dependency.projection_id,
49
49
  kind: dependency.kind,
50
- platform: dependency.platform,
50
+ projection_type: dependency.projection_type,
51
51
  reason: dependency.reason,
52
52
  items: []
53
53
  });
@@ -172,7 +172,7 @@ export function buildBundleAdoptionPriorities(report, confidenceRank) {
172
172
  enums: bundle.enums.length,
173
173
  capabilities: bundle.capabilities.length,
174
174
  shapes: bundle.shapes.length,
175
- components: bundle.components?.length || 0,
175
+ widgets: bundle.widgets?.length || 0,
176
176
  screens: bundle.screens.length,
177
177
  workflows: bundle.workflows.length,
178
178
  docs: bundle.docs.length
@@ -2,16 +2,16 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
4
  import {
5
- componentById,
6
5
  getJourneyDoc,
7
6
  getStatement,
8
7
  summarizeById,
9
8
  getWorkflowDoc,
10
- relatedComponentsForProjection,
11
9
  relatedProjectionsForCapability,
12
- relatedProjectionsForComponent,
13
10
  relatedProjectionsForEntity,
14
- relatedShapesForProjection
11
+ relatedProjectionsForWidget,
12
+ relatedShapesForProjection,
13
+ relatedWidgetsForProjection,
14
+ widgetById
15
15
  } from "../generator/context/shared.js";
16
16
  import { generatorDefaultsMap } from "../generator/surfaces/shared.js";
17
17
 
@@ -1337,7 +1337,7 @@ function buildMaintainedOutputGroups(outputs = [], seams = [], {
1337
1337
 
1338
1338
  function projectionKindForImpact(projection) {
1339
1339
  if (!projection) return "unknown";
1340
- if ((projection.http || []).length > 0 || projection.platform === "api" || projection.platform === "backend") {
1340
+ if ((projection.http || []).length > 0 || projection.type === "api" || projection.type === "backend") {
1341
1341
  return "api";
1342
1342
  }
1343
1343
  if (
@@ -1345,9 +1345,9 @@ function projectionKindForImpact(projection) {
1345
1345
  (projection.uiWeb || []).length > 0 ||
1346
1346
  (projection.uiIos || []).length > 0 ||
1347
1347
  (projection.uiScreens || []).length > 0 ||
1348
- projection.platform === "web_surface" ||
1349
- projection.platform === "ios_surface" ||
1350
- projection.platform === "ui_contract"
1348
+ projection.type === "web_surface" ||
1349
+ projection.type === "ios_surface" ||
1350
+ projection.type === "ui_contract"
1351
1351
  ) {
1352
1352
  return "ui";
1353
1353
  }
@@ -1355,7 +1355,7 @@ function projectionKindForImpact(projection) {
1355
1355
  (projection.dbTables || []).length > 0 ||
1356
1356
  (projection.dbColumns || []).length > 0 ||
1357
1357
  (projection.dbRelations || []).length > 0 ||
1358
- String(projection.platform || "").startsWith("db_")
1358
+ String(projection.type || "").startsWith("db_")
1359
1359
  ) {
1360
1360
  return "db";
1361
1361
  }
@@ -1367,7 +1367,7 @@ function projectionSummaryForImpact(projection) {
1367
1367
  return {
1368
1368
  projection_id: projection.id,
1369
1369
  kind: projectionKindForImpact(projection),
1370
- platform: projection.platform || null,
1370
+ type: projection.type || null,
1371
1371
  outputs: stableSortedStrings(projection.outputs || [])
1372
1372
  };
1373
1373
  }
@@ -1408,7 +1408,7 @@ function addImpact(map, impact) {
1408
1408
  "changed_capability",
1409
1409
  "changed_entity",
1410
1410
  "changed_shape",
1411
- "changed_component",
1411
+ "changed_widget",
1412
1412
  "changed_rule",
1413
1413
  "changed_workflow",
1414
1414
  "changed_journey",
@@ -1551,20 +1551,20 @@ function projectionImpactsFromShape(graph, shapeId) {
1551
1551
  ];
1552
1552
  }
1553
1553
 
1554
- function projectionImpactsFromComponent(graph, componentId) {
1555
- const component = componentById(graph, componentId);
1556
- if (!component) {
1554
+ function projectionImpactsFromWidget(graph, widgetId) {
1555
+ const widget = widgetById(graph, widgetId);
1556
+ if (!widget) {
1557
1557
  return [];
1558
1558
  }
1559
1559
 
1560
1560
  return impactsFromProjectionIds(
1561
1561
  graph,
1562
- relatedProjectionsForComponent(graph, componentId),
1563
- "changed_component",
1564
- (projection) => `Projection ${projection.id} is affected because component ${componentId} is used by that UI surface.`
1562
+ relatedProjectionsForWidget(graph, widgetId),
1563
+ "changed_widget",
1564
+ (projection) => `Projection ${projection.id} is affected because widget ${widgetId} is used by that UI surface.`
1565
1565
  ).map((impact) => ({
1566
1566
  ...impact,
1567
- component_ids: stableSortedStrings([componentId])
1567
+ widget_ids: stableSortedStrings([widgetId])
1568
1568
  }));
1569
1569
  }
1570
1570
 
@@ -1602,8 +1602,8 @@ function buildProjectionImpacts(graph, { sliceArtifact, diffArtifact }) {
1602
1602
  for (const impact of projectionImpactsFromShape(graph, entry.id)) addImpact(impactMap, impact);
1603
1603
  }
1604
1604
 
1605
- for (const entry of diffArtifact.components || []) {
1606
- for (const impact of projectionImpactsFromComponent(graph, entry.id)) addImpact(impactMap, impact);
1605
+ for (const entry of diffArtifact.widgets || diffArtifact.components || []) {
1606
+ for (const impact of projectionImpactsFromWidget(graph, entry.id)) addImpact(impactMap, impact);
1607
1607
  }
1608
1608
 
1609
1609
  for (const entry of diffArtifact.rules || []) {
@@ -1640,7 +1640,7 @@ function buildProjectionImpacts(graph, { sliceArtifact, diffArtifact }) {
1640
1640
  (projection) => `Projection ${projection.id} is in scope because selected entity ${id} participates in that projection.`
1641
1641
  )) addImpact(impactMap, impact);
1642
1642
  } else if (kind === "widget") {
1643
- for (const impact of projectionImpactsFromComponent(graph, id)) addImpact(impactMap, impact);
1643
+ for (const impact of projectionImpactsFromWidget(graph, id)) addImpact(impactMap, impact);
1644
1644
  } else if (kind === "workflow") {
1645
1645
  for (const impact of projectionImpactsFromWorkflow(graph, id, true)) addImpact(impactMap, impact);
1646
1646
  } else if (kind === "journey") {
@@ -1675,7 +1675,7 @@ function buildGeneratorTargets(graph, projectionImpacts = [], diffArtifact = nul
1675
1675
  }
1676
1676
  };
1677
1677
 
1678
- for (const entry of diffArtifact?.components || []) {
1678
+ for (const entry of diffArtifact?.widgets || diffArtifact?.components || []) {
1679
1679
  addTarget({
1680
1680
  target: "ui-widget-contract",
1681
1681
  widget_id: entry.id,
@@ -1717,7 +1717,7 @@ function buildGeneratorTargets(graph, projectionImpacts = [], diffArtifact = nul
1717
1717
  });
1718
1718
  }
1719
1719
 
1720
- if (projection.platform === "ui_contract") {
1720
+ if (projection.type === "ui_contract") {
1721
1721
  addTarget({
1722
1722
  target: "ui-contract-graph",
1723
1723
  projection_id: impact.projection_id,
@@ -1732,29 +1732,29 @@ function buildGeneratorTargets(graph, projectionImpacts = [], diffArtifact = nul
1732
1732
  });
1733
1733
  }
1734
1734
 
1735
- const componentIds = stableSortedStrings([
1736
- ...(impact.component_ids || []),
1737
- ...(impact.kind === "ui" ? relatedComponentsForProjection(graph, projection) : [])
1735
+ const widgetIds = stableSortedStrings([
1736
+ ...(impact.widget_ids || []),
1737
+ ...(impact.kind === "ui" ? relatedWidgetsForProjection(graph, projection) : [])
1738
1738
  ]);
1739
1739
 
1740
- for (const componentId of componentIds) {
1740
+ for (const widgetId of widgetIds) {
1741
1741
  addTarget({
1742
1742
  target: "ui-widget-contract",
1743
- widget_id: componentId,
1743
+ widget_id: widgetId,
1744
1744
  projection_id: impact.projection_id,
1745
1745
  required: true,
1746
- reason: `Projection ${impact.projection_id} is affected by widget ${componentId}, so the widget contract should be refreshed.`
1746
+ reason: `Projection ${impact.projection_id} is affected by widget ${widgetId}, so the widget contract should be refreshed.`
1747
1747
  });
1748
1748
  addTarget({
1749
1749
  target: "widget-behavior-report",
1750
- widget_id: componentId,
1750
+ widget_id: widgetId,
1751
1751
  projection_id: impact.projection_id,
1752
1752
  required: true,
1753
- reason: `Projection ${impact.projection_id} is affected by widget ${componentId}, so behavior data/event/action wiring should be reviewed.`
1753
+ reason: `Projection ${impact.projection_id} is affected by widget ${widgetId}, so behavior data/event/action wiring should be reviewed.`
1754
1754
  });
1755
1755
  }
1756
1756
 
1757
- if (projection.platform === "web_surface") {
1757
+ if (projection.type === "web_surface") {
1758
1758
  addTarget({
1759
1759
  target: "ui-surface-contract",
1760
1760
  projection_id: impact.projection_id,
@@ -1777,7 +1777,7 @@ function buildGeneratorTargets(graph, projectionImpacts = [], diffArtifact = nul
1777
1777
  }
1778
1778
  }
1779
1779
 
1780
- if (projection.platform === "ios_surface") {
1780
+ if (projection.type === "ios_surface") {
1781
1781
  addTarget({
1782
1782
  target: "swiftui-app",
1783
1783
  projection_id: impact.projection_id,
@@ -1786,7 +1786,7 @@ function buildGeneratorTargets(graph, projectionImpacts = [], diffArtifact = nul
1786
1786
  });
1787
1787
  }
1788
1788
 
1789
- if (String(projection.platform || "").startsWith("db_")) {
1789
+ if (String(projection.type || "").startsWith("db_")) {
1790
1790
  addTarget({
1791
1791
  target: "db-contract-graph",
1792
1792
  projection_id: impact.projection_id,
@@ -53,7 +53,7 @@ function serializeFields(statement) {
53
53
  "plan",
54
54
  "monitoring",
55
55
  "vocabulary",
56
- "componentContract",
56
+ "widgetContract",
57
57
  "record",
58
58
  "flow",
59
59
  "affectedByPitches",
package/src/cli.js CHANGED
@@ -771,7 +771,7 @@ function printReleaseHelp() {
771
771
  console.log(" topogram release status");
772
772
  console.log(" topogram release status --json");
773
773
  console.log(" topogram release status --strict");
774
- console.log(" topogram release status --strict --write-report ./release-baseline.md");
774
+ console.log(" topogram release status --strict --write-report ./docs/release-matrix.md");
775
775
  console.log(" topogram release roll-consumers 0.3.46 --watch");
776
776
  console.log(" topogram release roll-consumers --latest --watch");
777
777
  console.log("");
@@ -1037,8 +1037,8 @@ function targetRequiresImplementationProvider(target) {
1037
1037
 
1038
1038
  function topologyComponentReferences(component) {
1039
1039
  return {
1040
- uses_api: component.uses_api || component.api || null,
1041
- uses_database: component.uses_database || component.database || null
1040
+ uses_api: component.uses_api || null,
1041
+ uses_database: component.uses_database || null
1042
1042
  };
1043
1043
  }
1044
1044
 
@@ -1054,10 +1054,10 @@ function summarizeProjectTopology(config) {
1054
1054
  ownership: output?.ownership || null
1055
1055
  }))
1056
1056
  .sort((left, right) => left.name.localeCompare(right.name));
1057
- const runtimes = (config?.topology?.runtimes || config?.topology?.components || [])
1057
+ const runtimes = (config?.topology?.runtimes || [])
1058
1058
  .map((component) => ({
1059
1059
  id: component.id,
1060
- kind: component.kind || component.type,
1060
+ kind: component.kind,
1061
1061
  projection: component.projection,
1062
1062
  generator: {
1063
1063
  id: component.generator?.id || null,
@@ -1097,7 +1097,7 @@ function publicProjectTopology(topology) {
1097
1097
  return topology || null;
1098
1098
  }
1099
1099
  return {
1100
- ...Object.fromEntries(Object.entries(topology).filter(([key]) => !["components", "__normalizedRuntimeAliases"].includes(key))),
1100
+ ...Object.fromEntries(Object.entries(topology).filter(([key]) => key !== "components")),
1101
1101
  runtimes: topology.runtimes || []
1102
1102
  };
1103
1103
  }
@@ -1818,7 +1818,7 @@ function annotateGeneratorPolicyDiagnostics(diagnostics, bindings) {
1818
1818
  return diagnostics.map((diagnostic) => {
1819
1819
  const binding = bindings.find((item) => (
1820
1820
  item.packageName === diagnostic.packageName &&
1821
- (!diagnostic.componentId || item.componentId === diagnostic.componentId)
1821
+ (!diagnostic.runtimeId || item.runtimeId === diagnostic.runtimeId)
1822
1822
  ));
1823
1823
  if (!binding) {
1824
1824
  return diagnostic;
@@ -1846,11 +1846,11 @@ function generatorPolicyPackageMetadataDiagnostics(bindings) {
1846
1846
  diagnostics.push({
1847
1847
  code: "generator_package_dependency_missing",
1848
1848
  severity: "warning",
1849
- message: `Component '${binding.componentId}' generator package '${binding.packageName}' is not declared in package.json dependencies.`,
1849
+ message: `Runtime '${binding.runtimeId}' generator package '${binding.packageName}' is not declared in package.json dependencies.`,
1850
1850
  path: binding.packageInfo.installedPackageJsonPath,
1851
1851
  suggestedFix: `Declare '${binding.packageName}' in package.json devDependencies so generator adoption is visible in package review.`,
1852
1852
  step: "generator-policy",
1853
- componentId: binding.componentId,
1853
+ runtimeId: binding.runtimeId,
1854
1854
  generatorId: binding.generatorId,
1855
1855
  packageName: binding.packageName,
1856
1856
  version: binding.version,
@@ -1870,11 +1870,11 @@ function generatorPolicyPackageMetadataDiagnostics(bindings) {
1870
1870
  diagnostics.push({
1871
1871
  code: "generator_package_version_drift",
1872
1872
  severity: "warning",
1873
- message: `Component '${binding.componentId}' generator package '${binding.packageName}' is installed at '${binding.packageInfo.installedVersion}', but package-lock records '${binding.packageInfo.lockfileVersion}'.`,
1873
+ message: `Runtime '${binding.runtimeId}' generator package '${binding.packageName}' is installed at '${binding.packageInfo.installedVersion}', but package-lock records '${binding.packageInfo.lockfileVersion}'.`,
1874
1874
  path: binding.packageInfo.lockfilePath,
1875
1875
  suggestedFix: "Run the package manager install command and review the resulting lockfile before pinning generator policy.",
1876
1876
  step: "generator-policy",
1877
- componentId: binding.componentId,
1877
+ runtimeId: binding.runtimeId,
1878
1878
  generatorId: binding.generatorId,
1879
1879
  packageName: binding.packageName,
1880
1880
  version: binding.version,
@@ -1975,7 +1975,7 @@ function buildGeneratorPolicyExplainPayload(projectPath) {
1975
1975
  `scopes=${policy.allowedPackageScopes.join(", ") || "(none)"}`,
1976
1976
  `packages=${policy.allowedPackages.join(", ") || "(none)"}`
1977
1977
  ].join("; "),
1978
- `Component '${binding.componentId}' package-backed generator must be from an allowed package or scope.`,
1978
+ `Runtime '${binding.runtimeId}' package-backed generator must be from an allowed package or scope.`,
1979
1979
  `Run \`topogram generator policy pin ${binding.packageName}@${binding.version}\` after reviewing the generator package.`
1980
1980
  ));
1981
1981
  const pinnedVersion = policy.pinnedVersions[binding.packageName] || policy.pinnedVersions[binding.generatorId] || null;
@@ -1984,7 +1984,7 @@ function buildGeneratorPolicyExplainPayload(projectPath) {
1984
1984
  !pinnedVersion || pinnedVersion === binding.version,
1985
1985
  binding.version,
1986
1986
  pinnedVersion || "(unpinned)",
1987
- `Component '${binding.componentId}' generator version must match its policy pin when one exists.`,
1987
+ `Runtime '${binding.runtimeId}' generator version must match its policy pin when one exists.`,
1988
1988
  `Run \`topogram generator policy pin ${binding.packageName}@${binding.version}\` after review.`
1989
1989
  ));
1990
1990
  }
@@ -2024,7 +2024,7 @@ function printGeneratorPolicyCheckPayload(payload) {
2024
2024
  console.log(`Defaulted: ${payload.defaulted ? "yes" : "no"}`);
2025
2025
  console.log(`Package-backed generators: ${payload.bindings.length}`);
2026
2026
  for (const binding of payload.bindings) {
2027
- console.log(`- ${binding.componentId}: ${binding.generatorId}@${binding.version} via ${binding.packageName}`);
2027
+ console.log(`- ${binding.runtimeId}: ${binding.generatorId}@${binding.version} via ${binding.packageName}`);
2028
2028
  console.log(` npm package: ${binding.packageInfo.installedVersion || "(not installed)"}`);
2029
2029
  if (binding.packageInfo.dependencySpec) {
2030
2030
  console.log(` dependency: ${binding.packageInfo.dependencyField} ${binding.packageInfo.dependencySpec}`);
@@ -2066,7 +2066,7 @@ function printGeneratorPolicyStatusPayload(payload) {
2066
2066
  console.log("Generator packages:");
2067
2067
  }
2068
2068
  for (const binding of payload.bindings) {
2069
- console.log(`- ${binding.componentId}: ${binding.generatorId}@${binding.version} via ${binding.packageName}`);
2069
+ console.log(`- ${binding.runtimeId}: ${binding.generatorId}@${binding.version} via ${binding.packageName}`);
2070
2070
  console.log(` allowed: ${binding.allowed ? "yes" : "no"}`);
2071
2071
  console.log(` npm package: ${binding.packageInfo.installedVersion || "(not installed)"}`);
2072
2072
  console.log(` dependency: ${binding.packageInfo.dependencyField && binding.packageInfo.dependencySpec ? `${binding.packageInfo.dependencyField} ${binding.packageInfo.dependencySpec}` : "(not declared)"}`);
@@ -2107,7 +2107,7 @@ function printGeneratorPolicyExplainPayload(payload) {
2107
2107
  console.log("");
2108
2108
  console.log("Package-backed generators:");
2109
2109
  for (const binding of payload.bindings) {
2110
- console.log(`- ${binding.componentId}: ${binding.generatorId}@${binding.version} via ${binding.packageName}`);
2110
+ console.log(`- ${binding.runtimeId}: ${binding.generatorId}@${binding.version} via ${binding.packageName}`);
2111
2111
  console.log(` npm package: ${binding.packageInfo.installedVersion || "(not installed)"}`);
2112
2112
  if (binding.packageInfo.dependencySpec) {
2113
2113
  console.log(` dependency: ${binding.packageInfo.dependencyField} ${binding.packageInfo.dependencySpec}`);
@@ -2944,10 +2944,15 @@ function printReleaseStatus(payload) {
2944
2944
  * @returns {string}
2945
2945
  */
2946
2946
  function renderReleaseStatusMarkdown(payload) {
2947
+ const matrix = buildReleaseMatrixCatalogPayload();
2948
+ const catalogConsumer = payload.consumers.find((consumer) => consumer.name === "topograms");
2949
+ const demoConsumer = payload.consumers.find((consumer) => consumer.name === "topogram-demo-todo");
2947
2950
  const lines = [
2948
- `# Topogram CLI release ${payload.localVersion}`,
2951
+ "# Known-Good Release Matrix",
2949
2952
  "",
2950
- `Date checked: ${new Date().toISOString().slice(0, 10)}`,
2953
+ "This matrix is generated by `topogram release status --strict --write-report`.",
2954
+ `Date checked: ${new Date().toISOString().slice(0, 10)}.`,
2955
+ "Treat it as a dated release audit, not a floating compatibility promise.",
2951
2956
  "",
2952
2957
  "## Summary",
2953
2958
  "",
@@ -2957,12 +2962,48 @@ function renderReleaseStatusMarkdown(payload) {
2957
2962
  `- Consumer pins: ${payload.consumerPins.matching}/${payload.consumerPins.known} matching`,
2958
2963
  `- Consumer CI: ${payload.consumerCi.passing}/${payload.consumerCi.checked} passing`,
2959
2964
  `- Strict status: ${payload.ok ? "passed" : "failed"}`,
2965
+ "",
2966
+ "## Core",
2967
+ "",
2968
+ "| Package or Repo | Version or Commit | Verification |",
2969
+ "| --- | --- | --- |",
2970
+ `| \`${payload.packageName}\` | \`${payload.localVersion}\` | Publish CLI Package, strict release status, fresh npmjs smoke, and installed CLI smoke passed |`,
2971
+ `| \`attebury/topograms\` catalog | \`${releaseMatrixConsumerCommit(catalogConsumer)}\` | ${releaseMatrixConsumerVerification(catalogConsumer, "Catalog Verification", payload.localVersion)} |`,
2972
+ `| \`topogram-demo-todo\` | \`${releaseMatrixConsumerCommit(demoConsumer)}\` | ${releaseMatrixConsumerVerification(demoConsumer, "Demo Verification", payload.localVersion)} |`,
2973
+ "",
2974
+ "## Catalog Entries",
2975
+ "",
2976
+ "| Catalog ID | Kind | Package | Version | Stack |",
2977
+ "| --- | --- | --- | --- | --- |"
2978
+ ];
2979
+ if (matrix.entries.length > 0) {
2980
+ for (const entry of matrix.entries) {
2981
+ lines.push(`| \`${entry.id}\` | ${entry.kind} | \`${entry.package}\` | \`${entry.defaultVersion}\` | ${escapeMarkdownTableCell(entry.stack || "not declared")} |`);
2982
+ }
2983
+ } else {
2984
+ lines.push("| unavailable | unavailable | unavailable | unavailable | Catalog could not be loaded for this report |");
2985
+ }
2986
+ lines.push(
2987
+ "",
2988
+ "## Generator Packages",
2989
+ "",
2990
+ "| Generator Package | Surface | Catalog usage |",
2991
+ "| --- | --- | --- |"
2992
+ );
2993
+ if (matrix.generators.length > 0) {
2994
+ for (const generator of matrix.generators) {
2995
+ lines.push(`| \`${generator.package}\` | ${escapeMarkdownTableCell(generator.surface)} | ${escapeMarkdownTableCell(generator.catalogIds.join(", "))} |`);
2996
+ }
2997
+ } else {
2998
+ lines.push("| unavailable | unavailable | Catalog generator metadata could not be loaded for this report |");
2999
+ }
3000
+ lines.push(
2960
3001
  "",
2961
3002
  "## Consumers",
2962
3003
  "",
2963
3004
  "| Repo | Pin | Workflow | Status | Run |",
2964
3005
  "| --- | --- | --- | --- | --- |"
2965
- ];
3006
+ );
2966
3007
  for (const consumer of payload.consumers) {
2967
3008
  const workflow = consumer.workflow || consumer.ci?.expectedWorkflow || "";
2968
3009
  const run = consumer.ci?.run;
@@ -2970,6 +3011,32 @@ function renderReleaseStatusMarkdown(payload) {
2970
3011
  const url = run?.url ? `[${run.databaseId || "run"}](${run.url})` : "";
2971
3012
  lines.push(`| \`${consumer.name}\` | \`${consumer.version || "missing"}\` | ${escapeMarkdownTableCell(workflow)} | ${escapeMarkdownTableCell(status)} | ${url} |`);
2972
3013
  }
3014
+ lines.push(
3015
+ "",
3016
+ "## Consumer Proofs",
3017
+ "",
3018
+ "The external Todo demo is the canonical end-to-end consumer proof for the current catalog-backed workflow:",
3019
+ "",
3020
+ "```bash",
3021
+ "topogram new ./todo-demo --template todo",
3022
+ "cd ./todo-demo",
3023
+ "npm install",
3024
+ "npm run check",
3025
+ "npm run generate",
3026
+ "npm run app:compile",
3027
+ "npm run verify",
3028
+ "npm run app:runtime",
3029
+ "```",
3030
+ "",
3031
+ "The demo CI also verifies `topogram new` from the default public catalog and from the repo-local catalog fixture. That prevents local fixtures from masking a broken published catalog alias."
3032
+ );
3033
+ const reportDiagnostics = [...matrix.diagnostics];
3034
+ if (reportDiagnostics.length > 0) {
3035
+ lines.push("", "## Report Diagnostics", "");
3036
+ for (const diagnostic of reportDiagnostics) {
3037
+ lines.push(`- **${diagnostic.severity || "warning"}** \`${diagnostic.code || "release_report_catalog_unavailable"}\`: ${diagnostic.message}`);
3038
+ }
3039
+ }
2973
3040
  if (payload.diagnostics.length > 0) {
2974
3041
  lines.push("", "## Diagnostics", "");
2975
3042
  for (const diagnostic of payload.diagnostics) {
@@ -2987,6 +3054,92 @@ function renderReleaseStatusMarkdown(payload) {
2987
3054
  return `${lines.join("\n")}\n`;
2988
3055
  }
2989
3056
 
3057
+ /**
3058
+ * @returns {{ entries: any[], generators: Array<{ package: string, surface: string, catalogIds: string[] }>, diagnostics: Array<Record<string, any>> }}
3059
+ */
3060
+ function buildReleaseMatrixCatalogPayload() {
3061
+ try {
3062
+ const loaded = loadCatalog(null);
3063
+ const entries = [...loaded.catalog.entries].sort((left, right) => {
3064
+ if (left.kind !== right.kind) {
3065
+ return left.kind === "template" ? -1 : 1;
3066
+ }
3067
+ return left.id.localeCompare(right.id);
3068
+ });
3069
+ const generatorMap = new Map();
3070
+ for (const entry of entries) {
3071
+ for (const packageName of Array.isArray(entry.generators) ? entry.generators : []) {
3072
+ if (!generatorMap.has(packageName)) {
3073
+ generatorMap.set(packageName, {
3074
+ package: packageName,
3075
+ surface: releaseMatrixGeneratorSurface(packageName),
3076
+ catalogIds: []
3077
+ });
3078
+ }
3079
+ generatorMap.get(packageName).catalogIds.push(entry.id);
3080
+ }
3081
+ }
3082
+ const generators = [...generatorMap.values()].sort((left, right) => left.package.localeCompare(right.package));
3083
+ return {
3084
+ entries,
3085
+ generators,
3086
+ diagnostics: loaded.diagnostics || []
3087
+ };
3088
+ } catch (error) {
3089
+ return {
3090
+ entries: [],
3091
+ generators: [],
3092
+ diagnostics: [{
3093
+ code: "release_report_catalog_unavailable",
3094
+ severity: "warning",
3095
+ message: messageFromError(error)
3096
+ }]
3097
+ };
3098
+ }
3099
+ }
3100
+
3101
+ /**
3102
+ * @param {string} packageName
3103
+ * @returns {string}
3104
+ */
3105
+ function releaseMatrixGeneratorSurface(packageName) {
3106
+ if (packageName.includes("-web")) return "web";
3107
+ if (packageName.includes("-api")) return "api";
3108
+ if (packageName.includes("-db")) return "database";
3109
+ if (packageName.includes("-native")) return "native";
3110
+ return "not declared";
3111
+ }
3112
+
3113
+ /**
3114
+ * @param {any} consumer
3115
+ * @returns {string}
3116
+ */
3117
+ function releaseMatrixConsumerCommit(consumer) {
3118
+ return shortSha(consumer?.ci?.headSha || consumer?.ci?.run?.headSha || null) || "unknown";
3119
+ }
3120
+
3121
+ /**
3122
+ * @param {any} consumer
3123
+ * @param {string} workflowName
3124
+ * @param {string} version
3125
+ * @returns {string}
3126
+ */
3127
+ function releaseMatrixConsumerVerification(consumer, workflowName, version) {
3128
+ const status = consumer?.ci?.run
3129
+ ? `${consumer.ci.run.status || "unknown"}/${consumer.ci.run.conclusion || "unknown"}`
3130
+ : "not checked";
3131
+ return `${workflowName}: ${status}; pinned ${CLI_PACKAGE_NAME}@${version}`;
3132
+ }
3133
+
3134
+ /**
3135
+ * @param {string|null|undefined} value
3136
+ * @returns {string|null}
3137
+ */
3138
+ function shortSha(value) {
3139
+ const text = String(value || "").trim();
3140
+ return text ? text.slice(0, 7) : null;
3141
+ }
3142
+
2990
3143
  /**
2991
3144
  * @param {string|null|undefined} value
2992
3145
  * @returns {string}
@@ -5533,7 +5686,7 @@ function importCandidateCounts(summary) {
5533
5686
  apiRoutes: candidates.api?.routes?.length || 0,
5534
5687
  uiScreens: candidates.ui?.screens?.length || 0,
5535
5688
  uiRoutes: candidates.ui?.routes?.length || 0,
5536
- uiComponents: candidates.ui?.components?.length || 0,
5689
+ uiWidgets: candidates.ui?.widgets?.length || candidates.ui?.components?.length || 0,
5537
5690
  workflows: candidates.workflows?.workflows?.length || 0,
5538
5691
  verifications: candidates.verification?.verifications?.length || 0
5539
5692
  };
@@ -41,7 +41,7 @@ import { generateVanillaWebApp } from "./surfaces/web/vanilla.js";
41
41
  * @property {Record<string, any>} graph
42
42
  * @property {Record<string, any>} projection
43
43
  * @property {Record<string, any>} runtime
44
- * @property {Record<string, any>} component Internal runtime alias.
44
+ * @property {Record<string, any>} [component] Legacy runtime alias for existing generator packages.
45
45
  * @property {Record<string, any>|null} [topology]
46
46
  * @property {Record<string, any>} [contracts]
47
47
  * @property {Record<string, any>|null} [implementation]
@@ -88,7 +88,23 @@ function requiredManifest(generatorId) {
88
88
  * @returns {string}
89
89
  */
90
90
  function runtimeFor(context) {
91
- return context.runtime || context.component || {};
91
+ return normalizeRuntimeForGenerator(context.runtime || context.component || {});
92
+ }
93
+
94
+ /**
95
+ * Keep package-backed generator context canonical while preserving aliases for already-published adapters.
96
+ *
97
+ * @param {Record<string, any>} runtime
98
+ * @returns {Record<string, any>}
99
+ */
100
+ function normalizeRuntimeForGenerator(runtime) {
101
+ const apiRuntime = runtime.apiRuntime || runtime.apiComponent || null;
102
+ const databaseRuntime = runtime.databaseRuntime || runtime.databaseComponent || null;
103
+ return {
104
+ ...runtime,
105
+ ...(apiRuntime ? { apiRuntime, apiComponent: apiRuntime } : {}),
106
+ ...(databaseRuntime ? { databaseRuntime, databaseComponent: databaseRuntime } : {})
107
+ };
92
108
  }
93
109
 
94
110
  /**
@@ -108,7 +124,7 @@ function projectionIdFor(context) {
108
124
  function serverOptions(context, profile) {
109
125
  const projectionId = projectionIdFor(context);
110
126
  const runtime = runtimeFor(context);
111
- const dbProjectionId = runtime.databaseComponent?.projection?.id || context.options?.dbProjectionId;
127
+ const dbProjectionId = runtime.databaseRuntime?.projection?.id || runtime.databaseComponent?.projection?.id || context.options?.dbProjectionId;
112
128
  return {
113
129
  ...(context.options || {}),
114
130
  projectionId,
@@ -178,7 +194,7 @@ export const BUNDLED_GENERATOR_ADAPTERS = [
178
194
  manifest: requiredManifest("topogram/hono"),
179
195
  generate(context) {
180
196
  const runtime = runtimeFor(context);
181
- if (runtime && !runtime.databaseComponent) {
197
+ if (runtime && !runtime.databaseRuntime) {
182
198
  return fileResult(generateStatelessServer(context.graph, serverOptions(context, "hono")));
183
199
  }
184
200
  return fileResult(generateHonoServer(context.graph, serverOptions(context, "hono")));
@@ -188,7 +204,7 @@ export const BUNDLED_GENERATOR_ADAPTERS = [
188
204
  manifest: requiredManifest("topogram/express"),
189
205
  generate(context) {
190
206
  const runtime = runtimeFor(context);
191
- if (runtime && !runtime.databaseComponent) {
207
+ if (runtime && !runtime.databaseRuntime) {
192
208
  return fileResult(generateStatelessServer(context.graph, serverOptions(context, "express")));
193
209
  }
194
210
  return fileResult(generateExpressServer(context.graph, serverOptions(context, "express")));
@@ -277,8 +293,8 @@ function loadPackageGeneratorAdapter(manifest, runtime, options = {}) {
277
293
  const diagnostics = generatorPolicyDiagnosticsForBindings(
278
294
  loadGeneratorPolicy(rootDir),
279
295
  [{
280
- componentId: String(runtime?.id || "unknown"),
281
- componentType: String(runtime?.kind || runtime?.type || manifest.surface || "unknown"),
296
+ runtimeId: String(runtime?.id || "unknown"),
297
+ runtimeKind: String(runtime?.kind || runtime?.type || manifest.surface || "unknown"),
282
298
  projection: String(runtime?.projection?.id || runtime?.projection || "unknown"),
283
299
  generatorId: String(runtime?.generator?.id || manifest.id),
284
300
  version: String(runtime?.generator?.version || manifest.version),