@topogram/cli 0.3.53 → 0.3.55

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 (40) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/review-groups.js +3 -3
  3. package/src/agent-ops/query-builders.js +34 -34
  4. package/src/archive/schema.js +1 -1
  5. package/src/cli.js +177 -22
  6. package/src/generator/adapters.js +2 -2
  7. package/src/generator/context/domain-coverage.js +2 -2
  8. package/src/generator/context/shared.js +6 -22
  9. package/src/generator/context/slice.js +10 -10
  10. package/src/generator/docs.js +1 -1
  11. package/src/generator/registry.js +1 -1
  12. package/src/generator/runtime/app-bundle.js +7 -5
  13. package/src/generator/runtime/compile-check.js +3 -1
  14. package/src/generator/runtime/deployment.js +3 -1
  15. package/src/generator/runtime/environment.js +22 -19
  16. package/src/generator/runtime/shared.js +7 -7
  17. package/src/generator/surfaces/contracts.js +1 -1
  18. package/src/generator/surfaces/databases/lifecycle-shared.js +1 -1
  19. package/src/generator/surfaces/databases/postgres/drizzle.js +1 -1
  20. package/src/generator/surfaces/databases/postgres/prisma.js +1 -1
  21. package/src/generator/surfaces/databases/shared.js +3 -3
  22. package/src/generator/surfaces/databases/sqlite/prisma.js +1 -1
  23. package/src/generator/surfaces/services/persistence-wiring.js +1 -1
  24. package/src/generator/surfaces/services/server-contract.js +1 -1
  25. package/src/generator/surfaces/shared.js +1 -1
  26. package/src/generator/surfaces/web/ui-surface-contract.js +1 -1
  27. package/src/generator/widget-conformance.js +6 -6
  28. package/src/generator/widgets.js +1 -1
  29. package/src/generator-policy.js +10 -10
  30. package/src/import/core/runner.js +60 -50
  31. package/src/project-config.js +5 -42
  32. package/src/proofs/contract-audit.js +1 -1
  33. package/src/realization/backend/build-backend-runtime-realization.js +3 -3
  34. package/src/realization/ui/build-ui-shared-realization.js +9 -9
  35. package/src/realization/ui/build-web-realization.js +3 -3
  36. package/src/reconcile/journeys.js +1 -1
  37. package/src/resolver/enrich/widget.js +2 -2
  38. package/src/resolver/index.js +4 -23
  39. package/src/validator/index.js +10 -10
  40. package/src/workflows.js +49 -49
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topogram/cli",
3
- "version": "0.3.53",
3
+ "version": "0.3.55",
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
@@ -135,7 +135,8 @@ const KNOWN_CLI_CONSUMER_REPOS = [
135
135
  "topogram-starters",
136
136
  "topogram-template-todo",
137
137
  "topogram-demo-todo",
138
- "topogram-hello"
138
+ "topogram-hello",
139
+ "topograms"
139
140
  ];
140
141
  const KNOWN_CLI_CONSUMER_WORKFLOWS = {
141
142
  "topogram-generator-express-api": "Generator Verification",
@@ -149,7 +150,8 @@ const KNOWN_CLI_CONSUMER_WORKFLOWS = {
149
150
  "topogram-starters": "Starter Verification",
150
151
  "topogram-template-todo": "Template Verification",
151
152
  "topogram-demo-todo": "Demo Verification",
152
- "topogram-hello": "Topogram Package Verification"
153
+ "topogram-hello": "Topogram Package Verification",
154
+ "topograms": "Catalog Verification"
153
155
  };
154
156
  const PACKAGE_UPDATE_CLI_CHECK_SCRIPTS = [
155
157
  "cli:surface",
@@ -769,7 +771,7 @@ function printReleaseHelp() {
769
771
  console.log(" topogram release status");
770
772
  console.log(" topogram release status --json");
771
773
  console.log(" topogram release status --strict");
772
- console.log(" topogram release status --strict --write-report ./release-baseline.md");
774
+ console.log(" topogram release status --strict --write-report ./docs/release-matrix.md");
773
775
  console.log(" topogram release roll-consumers 0.3.46 --watch");
774
776
  console.log(" topogram release roll-consumers --latest --watch");
775
777
  console.log("");
@@ -1035,8 +1037,8 @@ function targetRequiresImplementationProvider(target) {
1035
1037
 
1036
1038
  function topologyComponentReferences(component) {
1037
1039
  return {
1038
- uses_api: component.uses_api || component.api || null,
1039
- uses_database: component.uses_database || component.database || null
1040
+ uses_api: component.uses_api || null,
1041
+ uses_database: component.uses_database || null
1040
1042
  };
1041
1043
  }
1042
1044
 
@@ -1052,10 +1054,10 @@ function summarizeProjectTopology(config) {
1052
1054
  ownership: output?.ownership || null
1053
1055
  }))
1054
1056
  .sort((left, right) => left.name.localeCompare(right.name));
1055
- const runtimes = (config?.topology?.runtimes || config?.topology?.components || [])
1057
+ const runtimes = (config?.topology?.runtimes || [])
1056
1058
  .map((component) => ({
1057
1059
  id: component.id,
1058
- kind: component.kind || component.type,
1060
+ kind: component.kind,
1059
1061
  projection: component.projection,
1060
1062
  generator: {
1061
1063
  id: component.generator?.id || null,
@@ -1095,7 +1097,7 @@ function publicProjectTopology(topology) {
1095
1097
  return topology || null;
1096
1098
  }
1097
1099
  return {
1098
- ...Object.fromEntries(Object.entries(topology).filter(([key]) => !["components", "__normalizedRuntimeAliases"].includes(key))),
1100
+ ...Object.fromEntries(Object.entries(topology).filter(([key]) => key !== "components")),
1099
1101
  runtimes: topology.runtimes || []
1100
1102
  };
1101
1103
  }
@@ -1816,7 +1818,7 @@ function annotateGeneratorPolicyDiagnostics(diagnostics, bindings) {
1816
1818
  return diagnostics.map((diagnostic) => {
1817
1819
  const binding = bindings.find((item) => (
1818
1820
  item.packageName === diagnostic.packageName &&
1819
- (!diagnostic.componentId || item.componentId === diagnostic.componentId)
1821
+ (!diagnostic.runtimeId || item.runtimeId === diagnostic.runtimeId)
1820
1822
  ));
1821
1823
  if (!binding) {
1822
1824
  return diagnostic;
@@ -1844,11 +1846,11 @@ function generatorPolicyPackageMetadataDiagnostics(bindings) {
1844
1846
  diagnostics.push({
1845
1847
  code: "generator_package_dependency_missing",
1846
1848
  severity: "warning",
1847
- 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.`,
1848
1850
  path: binding.packageInfo.installedPackageJsonPath,
1849
1851
  suggestedFix: `Declare '${binding.packageName}' in package.json devDependencies so generator adoption is visible in package review.`,
1850
1852
  step: "generator-policy",
1851
- componentId: binding.componentId,
1853
+ runtimeId: binding.runtimeId,
1852
1854
  generatorId: binding.generatorId,
1853
1855
  packageName: binding.packageName,
1854
1856
  version: binding.version,
@@ -1868,11 +1870,11 @@ function generatorPolicyPackageMetadataDiagnostics(bindings) {
1868
1870
  diagnostics.push({
1869
1871
  code: "generator_package_version_drift",
1870
1872
  severity: "warning",
1871
- 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}'.`,
1872
1874
  path: binding.packageInfo.lockfilePath,
1873
1875
  suggestedFix: "Run the package manager install command and review the resulting lockfile before pinning generator policy.",
1874
1876
  step: "generator-policy",
1875
- componentId: binding.componentId,
1877
+ runtimeId: binding.runtimeId,
1876
1878
  generatorId: binding.generatorId,
1877
1879
  packageName: binding.packageName,
1878
1880
  version: binding.version,
@@ -1973,7 +1975,7 @@ function buildGeneratorPolicyExplainPayload(projectPath) {
1973
1975
  `scopes=${policy.allowedPackageScopes.join(", ") || "(none)"}`,
1974
1976
  `packages=${policy.allowedPackages.join(", ") || "(none)"}`
1975
1977
  ].join("; "),
1976
- `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.`,
1977
1979
  `Run \`topogram generator policy pin ${binding.packageName}@${binding.version}\` after reviewing the generator package.`
1978
1980
  ));
1979
1981
  const pinnedVersion = policy.pinnedVersions[binding.packageName] || policy.pinnedVersions[binding.generatorId] || null;
@@ -1982,7 +1984,7 @@ function buildGeneratorPolicyExplainPayload(projectPath) {
1982
1984
  !pinnedVersion || pinnedVersion === binding.version,
1983
1985
  binding.version,
1984
1986
  pinnedVersion || "(unpinned)",
1985
- `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.`,
1986
1988
  `Run \`topogram generator policy pin ${binding.packageName}@${binding.version}\` after review.`
1987
1989
  ));
1988
1990
  }
@@ -2022,7 +2024,7 @@ function printGeneratorPolicyCheckPayload(payload) {
2022
2024
  console.log(`Defaulted: ${payload.defaulted ? "yes" : "no"}`);
2023
2025
  console.log(`Package-backed generators: ${payload.bindings.length}`);
2024
2026
  for (const binding of payload.bindings) {
2025
- console.log(`- ${binding.componentId}: ${binding.generatorId}@${binding.version} via ${binding.packageName}`);
2027
+ console.log(`- ${binding.runtimeId}: ${binding.generatorId}@${binding.version} via ${binding.packageName}`);
2026
2028
  console.log(` npm package: ${binding.packageInfo.installedVersion || "(not installed)"}`);
2027
2029
  if (binding.packageInfo.dependencySpec) {
2028
2030
  console.log(` dependency: ${binding.packageInfo.dependencyField} ${binding.packageInfo.dependencySpec}`);
@@ -2064,7 +2066,7 @@ function printGeneratorPolicyStatusPayload(payload) {
2064
2066
  console.log("Generator packages:");
2065
2067
  }
2066
2068
  for (const binding of payload.bindings) {
2067
- console.log(`- ${binding.componentId}: ${binding.generatorId}@${binding.version} via ${binding.packageName}`);
2069
+ console.log(`- ${binding.runtimeId}: ${binding.generatorId}@${binding.version} via ${binding.packageName}`);
2068
2070
  console.log(` allowed: ${binding.allowed ? "yes" : "no"}`);
2069
2071
  console.log(` npm package: ${binding.packageInfo.installedVersion || "(not installed)"}`);
2070
2072
  console.log(` dependency: ${binding.packageInfo.dependencyField && binding.packageInfo.dependencySpec ? `${binding.packageInfo.dependencyField} ${binding.packageInfo.dependencySpec}` : "(not declared)"}`);
@@ -2105,7 +2107,7 @@ function printGeneratorPolicyExplainPayload(payload) {
2105
2107
  console.log("");
2106
2108
  console.log("Package-backed generators:");
2107
2109
  for (const binding of payload.bindings) {
2108
- console.log(`- ${binding.componentId}: ${binding.generatorId}@${binding.version} via ${binding.packageName}`);
2110
+ console.log(`- ${binding.runtimeId}: ${binding.generatorId}@${binding.version} via ${binding.packageName}`);
2109
2111
  console.log(` npm package: ${binding.packageInfo.installedVersion || "(not installed)"}`);
2110
2112
  if (binding.packageInfo.dependencySpec) {
2111
2113
  console.log(` dependency: ${binding.packageInfo.dependencyField} ${binding.packageInfo.dependencySpec}`);
@@ -2942,10 +2944,15 @@ function printReleaseStatus(payload) {
2942
2944
  * @returns {string}
2943
2945
  */
2944
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");
2945
2950
  const lines = [
2946
- `# Topogram CLI release ${payload.localVersion}`,
2951
+ "# Known-Good Release Matrix",
2947
2952
  "",
2948
- `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.",
2949
2956
  "",
2950
2957
  "## Summary",
2951
2958
  "",
@@ -2955,12 +2962,48 @@ function renderReleaseStatusMarkdown(payload) {
2955
2962
  `- Consumer pins: ${payload.consumerPins.matching}/${payload.consumerPins.known} matching`,
2956
2963
  `- Consumer CI: ${payload.consumerCi.passing}/${payload.consumerCi.checked} passing`,
2957
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(
2958
3001
  "",
2959
3002
  "## Consumers",
2960
3003
  "",
2961
3004
  "| Repo | Pin | Workflow | Status | Run |",
2962
3005
  "| --- | --- | --- | --- | --- |"
2963
- ];
3006
+ );
2964
3007
  for (const consumer of payload.consumers) {
2965
3008
  const workflow = consumer.workflow || consumer.ci?.expectedWorkflow || "";
2966
3009
  const run = consumer.ci?.run;
@@ -2968,6 +3011,32 @@ function renderReleaseStatusMarkdown(payload) {
2968
3011
  const url = run?.url ? `[${run.databaseId || "run"}](${run.url})` : "";
2969
3012
  lines.push(`| \`${consumer.name}\` | \`${consumer.version || "missing"}\` | ${escapeMarkdownTableCell(workflow)} | ${escapeMarkdownTableCell(status)} | ${url} |`);
2970
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
+ }
2971
3040
  if (payload.diagnostics.length > 0) {
2972
3041
  lines.push("", "## Diagnostics", "");
2973
3042
  for (const diagnostic of payload.diagnostics) {
@@ -2985,6 +3054,92 @@ function renderReleaseStatusMarkdown(payload) {
2985
3054
  return `${lines.join("\n")}\n`;
2986
3055
  }
2987
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
+
2988
3143
  /**
2989
3144
  * @param {string|null|undefined} value
2990
3145
  * @returns {string}
@@ -5531,7 +5686,7 @@ function importCandidateCounts(summary) {
5531
5686
  apiRoutes: candidates.api?.routes?.length || 0,
5532
5687
  uiScreens: candidates.ui?.screens?.length || 0,
5533
5688
  uiRoutes: candidates.ui?.routes?.length || 0,
5534
- uiComponents: candidates.ui?.components?.length || 0,
5689
+ uiWidgets: candidates.ui?.widgets?.length || candidates.ui?.components?.length || 0,
5535
5690
  workflows: candidates.workflows?.workflows?.length || 0,
5536
5691
  verifications: candidates.verification?.verifications?.length || 0
5537
5692
  };
@@ -277,8 +277,8 @@ function loadPackageGeneratorAdapter(manifest, runtime, options = {}) {
277
277
  const diagnostics = generatorPolicyDiagnosticsForBindings(
278
278
  loadGeneratorPolicy(rootDir),
279
279
  [{
280
- componentId: String(runtime?.id || "unknown"),
281
- componentType: String(runtime?.kind || runtime?.type || manifest.surface || "unknown"),
280
+ runtimeId: String(runtime?.id || "unknown"),
281
+ runtimeKind: String(runtime?.kind || runtime?.type || manifest.surface || "unknown"),
282
282
  projection: String(runtime?.projection?.id || runtime?.projection || "unknown"),
283
283
  generatorId: String(runtime?.generator?.id || manifest.id),
284
284
  version: String(runtime?.generator?.version || manifest.version),
@@ -12,7 +12,7 @@ import {
12
12
  function projectionTypesFromProjections(projections) {
13
13
  const projectionTypes = new Set();
14
14
  for (const projection of projections) {
15
- const projectionType = projection?.type || projection?.platform;
15
+ const projectionType = projection?.type || projection?.type;
16
16
  if (projectionType) {
17
17
  projectionTypes.add(projectionType);
18
18
  }
@@ -53,7 +53,7 @@ export function generateDomainCoverage(graph, options = {}) {
53
53
  for (const projectionType of projectionTypes) {
54
54
  const realized = projectionStatements.some(
55
55
  (projection) =>
56
- (projection.type || projection.platform) === projectionType &&
56
+ (projection.type || projection.type) === projectionType &&
57
57
  (projection.realizes || []).some((entry) => entry.id === capabilityId)
58
58
  );
59
59
  matrix[capabilityId][projectionType] = realized;