@topolo/sdk 0.4.6 → 0.4.7

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/index.cjs CHANGED
@@ -143,7 +143,7 @@ var SERVICES = {
143
143
  nexus: "https://nexus-api.topolo.app",
144
144
  notify: "https://notify.topolo.app",
145
145
  observability_watch: "https://observability-watch.topolo.app",
146
- one: "https://one.topolo.app",
146
+ one: "https://www.topolo.app",
147
147
  p2p: "https://p2p.topolo.app",
148
148
  pay: "https://pay.topolo.app",
149
149
  people: "https://people.topolo.app",
@@ -1150,20 +1150,23 @@ var APPLICATIONS = {
1150
1150
  {
1151
1151
  "name": "topolo-one",
1152
1152
  "kind": "worker",
1153
- "apiUrl": null,
1154
- "domains": [],
1155
- "routes": [],
1153
+ "apiUrl": "https://one.topolo.app",
1154
+ "domains": [
1155
+ "one.topolo.app"
1156
+ ],
1157
+ "routes": [
1158
+ "one.topolo.app/*"
1159
+ ],
1156
1160
  "serviceId": null
1157
1161
  },
1158
1162
  {
1159
1163
  "name": "topolo-one-api",
1160
1164
  "kind": "worker",
1161
- "apiUrl": "https://one.topolo.app",
1165
+ "apiUrl": "https://www.topolo.app",
1162
1166
  "domains": [
1163
- "one.topolo.app"
1167
+ "www.topolo.app"
1164
1168
  ],
1165
1169
  "routes": [
1166
- "one.topolo.app/*",
1167
1170
  "www.topolo.app/*"
1168
1171
  ],
1169
1172
  "serviceId": "one"
@@ -1716,7 +1719,7 @@ var APPLICATIONS = {
1716
1719
  };
1717
1720
 
1718
1721
  // src/application-requirements.ts
1719
- var APPLICATION_REQUIREMENTS_VERSION = "2026-05-01.1";
1722
+ var APPLICATION_REQUIREMENTS_VERSION = "2026-06-07.1";
1720
1723
  var APPLICATION_REQUIREMENTS = [
1721
1724
  {
1722
1725
  id: "platform-metadata",
@@ -1860,11 +1863,13 @@ var APPLICATION_REQUIREMENTS = [
1860
1863
  evidence: [
1861
1864
  "The app-owned API exposes GET /api/widget",
1862
1865
  "The endpoint returns TopoloWidgetApiResponse from @topolo/sdk",
1866
+ "Every returned widget includes snapshot.summary with app-owned operational prose",
1863
1867
  "The endpoint derives organization/user context from the app auth boundary"
1864
1868
  ],
1865
1869
  implementation: [
1866
1870
  "Import createTopoloWidgetResponse from @topolo/sdk in the app backend",
1867
1871
  "Return native app metrics or action widgets instead of generic launch-card fallbacks",
1872
+ "Populate snapshot.summary in the producing app for every widget instead of synthesizing summaries in TopoloOne",
1868
1873
  "Add a route-level test that validates the response with validateTopoloWidgetResponse"
1869
1874
  ]
1870
1875
  },
@@ -3192,12 +3197,14 @@ function validateWidget(widget, path, errors) {
3192
3197
  }
3193
3198
  function validateSnapshotMetadata(snapshot, path, errors) {
3194
3199
  if (snapshot === void 0) {
3200
+ errors.push(`${path} must be an object`);
3195
3201
  return;
3196
3202
  }
3197
3203
  if (!isRecord(snapshot)) {
3198
- errors.push(`${path} must be an object when provided`);
3204
+ errors.push(`${path} must be an object`);
3199
3205
  return;
3200
3206
  }
3207
+ validateNonEmptyString(snapshot.summary, `${path}.summary`, errors);
3201
3208
  if (snapshot.defaultMetricKeys !== void 0 && !Array.isArray(snapshot.defaultMetricKeys)) {
3202
3209
  errors.push(`${path}.defaultMetricKeys must be an array when provided`);
3203
3210
  }
package/dist/index.d.cts CHANGED
@@ -264,7 +264,7 @@ declare const SERVICES: {
264
264
  readonly nexus: "https://nexus-api.topolo.app";
265
265
  readonly notify: "https://notify.topolo.app";
266
266
  readonly observability_watch: "https://observability-watch.topolo.app";
267
- readonly one: "https://one.topolo.app";
267
+ readonly one: "https://www.topolo.app";
268
268
  readonly p2p: "https://p2p.topolo.app";
269
269
  readonly pay: "https://pay.topolo.app";
270
270
  readonly people: "https://people.topolo.app";
@@ -1018,16 +1018,16 @@ declare const APPLICATIONS: {
1018
1018
  readonly deployTargets: readonly [{
1019
1019
  readonly name: "topolo-one";
1020
1020
  readonly kind: "worker";
1021
- readonly apiUrl: null;
1022
- readonly domains: readonly [];
1023
- readonly routes: readonly [];
1021
+ readonly apiUrl: "https://one.topolo.app";
1022
+ readonly domains: readonly ["one.topolo.app"];
1023
+ readonly routes: readonly ["one.topolo.app/*"];
1024
1024
  readonly serviceId: null;
1025
1025
  }, {
1026
1026
  readonly name: "topolo-one-api";
1027
1027
  readonly kind: "worker";
1028
- readonly apiUrl: "https://one.topolo.app";
1029
- readonly domains: readonly ["one.topolo.app"];
1030
- readonly routes: readonly ["one.topolo.app/*", "www.topolo.app/*"];
1028
+ readonly apiUrl: "https://www.topolo.app";
1029
+ readonly domains: readonly ["www.topolo.app"];
1030
+ readonly routes: readonly ["www.topolo.app/*"];
1031
1031
  readonly serviceId: "one";
1032
1032
  }, {
1033
1033
  readonly name: "topolo-one-website";
@@ -1432,7 +1432,7 @@ type ApplicationId = keyof typeof APPLICATIONS;
1432
1432
  type ApplicationCatalogEntry = (typeof APPLICATIONS)[ApplicationId];
1433
1433
  type ApplicationDeployTarget = ApplicationCatalogEntry['deployTargets'][number];
1434
1434
 
1435
- declare const APPLICATION_REQUIREMENTS_VERSION = "2026-05-01.1";
1435
+ declare const APPLICATION_REQUIREMENTS_VERSION = "2026-06-07.1";
1436
1436
  type ApplicationRequirementScope = 'all' | 'browser' | 'api' | 'tooling' | 'agent_surface';
1437
1437
  interface ApplicationRequirement {
1438
1438
  id: string;
@@ -1541,8 +1541,8 @@ declare const APPLICATION_REQUIREMENTS: readonly [{
1541
1541
  readonly title: "Expose a native dashboard widget contract";
1542
1542
  readonly summary: "Launchable first-party applications must populate TopoloOne live workspace through an app-owned /api/widget endpoint using the SDK widget response contract.";
1543
1543
  readonly appliesTo: readonly ["api"];
1544
- readonly evidence: readonly ["The app-owned API exposes GET /api/widget", "The endpoint returns TopoloWidgetApiResponse from @topolo/sdk", "The endpoint derives organization/user context from the app auth boundary"];
1545
- readonly implementation: readonly ["Import createTopoloWidgetResponse from @topolo/sdk in the app backend", "Return native app metrics or action widgets instead of generic launch-card fallbacks", "Add a route-level test that validates the response with validateTopoloWidgetResponse"];
1544
+ readonly evidence: readonly ["The app-owned API exposes GET /api/widget", "The endpoint returns TopoloWidgetApiResponse from @topolo/sdk", "Every returned widget includes snapshot.summary with app-owned operational prose", "The endpoint derives organization/user context from the app auth boundary"];
1545
+ readonly implementation: readonly ["Import createTopoloWidgetResponse from @topolo/sdk in the app backend", "Return native app metrics or action widgets instead of generic launch-card fallbacks", "Populate snapshot.summary in the producing app for every widget instead of synthesizing summaries in TopoloOne", "Add a route-level test that validates the response with validateTopoloWidgetResponse"];
1546
1546
  }, {
1547
1547
  readonly id: "observability-and-audit";
1548
1548
  readonly title: "Emit platform audit and debugging signals";
@@ -1971,7 +1971,7 @@ interface TopoloWidgetBase {
1971
1971
  serviceIcon?: string;
1972
1972
  type: TopoloWidgetType;
1973
1973
  lastUpdated: string;
1974
- snapshot?: TopoloWidgetSnapshotMetadata;
1974
+ snapshot: TopoloWidgetSnapshotMetadata;
1975
1975
  }
1976
1976
  interface TopoloWidgetMetric {
1977
1977
  key?: string;
@@ -1995,7 +1995,7 @@ interface TopoloWidgetSnapshotMetadata {
1995
1995
  scope?: string;
1996
1996
  scopeLabel?: string;
1997
1997
  generatedAt?: string;
1998
- summary?: string;
1998
+ summary: string;
1999
1999
  defaultMetricKeys?: string[];
2000
2000
  availableMetrics?: TopoloWidgetSnapshotMetric[];
2001
2001
  highlights?: string[];
package/dist/index.d.ts CHANGED
@@ -264,7 +264,7 @@ declare const SERVICES: {
264
264
  readonly nexus: "https://nexus-api.topolo.app";
265
265
  readonly notify: "https://notify.topolo.app";
266
266
  readonly observability_watch: "https://observability-watch.topolo.app";
267
- readonly one: "https://one.topolo.app";
267
+ readonly one: "https://www.topolo.app";
268
268
  readonly p2p: "https://p2p.topolo.app";
269
269
  readonly pay: "https://pay.topolo.app";
270
270
  readonly people: "https://people.topolo.app";
@@ -1018,16 +1018,16 @@ declare const APPLICATIONS: {
1018
1018
  readonly deployTargets: readonly [{
1019
1019
  readonly name: "topolo-one";
1020
1020
  readonly kind: "worker";
1021
- readonly apiUrl: null;
1022
- readonly domains: readonly [];
1023
- readonly routes: readonly [];
1021
+ readonly apiUrl: "https://one.topolo.app";
1022
+ readonly domains: readonly ["one.topolo.app"];
1023
+ readonly routes: readonly ["one.topolo.app/*"];
1024
1024
  readonly serviceId: null;
1025
1025
  }, {
1026
1026
  readonly name: "topolo-one-api";
1027
1027
  readonly kind: "worker";
1028
- readonly apiUrl: "https://one.topolo.app";
1029
- readonly domains: readonly ["one.topolo.app"];
1030
- readonly routes: readonly ["one.topolo.app/*", "www.topolo.app/*"];
1028
+ readonly apiUrl: "https://www.topolo.app";
1029
+ readonly domains: readonly ["www.topolo.app"];
1030
+ readonly routes: readonly ["www.topolo.app/*"];
1031
1031
  readonly serviceId: "one";
1032
1032
  }, {
1033
1033
  readonly name: "topolo-one-website";
@@ -1432,7 +1432,7 @@ type ApplicationId = keyof typeof APPLICATIONS;
1432
1432
  type ApplicationCatalogEntry = (typeof APPLICATIONS)[ApplicationId];
1433
1433
  type ApplicationDeployTarget = ApplicationCatalogEntry['deployTargets'][number];
1434
1434
 
1435
- declare const APPLICATION_REQUIREMENTS_VERSION = "2026-05-01.1";
1435
+ declare const APPLICATION_REQUIREMENTS_VERSION = "2026-06-07.1";
1436
1436
  type ApplicationRequirementScope = 'all' | 'browser' | 'api' | 'tooling' | 'agent_surface';
1437
1437
  interface ApplicationRequirement {
1438
1438
  id: string;
@@ -1541,8 +1541,8 @@ declare const APPLICATION_REQUIREMENTS: readonly [{
1541
1541
  readonly title: "Expose a native dashboard widget contract";
1542
1542
  readonly summary: "Launchable first-party applications must populate TopoloOne live workspace through an app-owned /api/widget endpoint using the SDK widget response contract.";
1543
1543
  readonly appliesTo: readonly ["api"];
1544
- readonly evidence: readonly ["The app-owned API exposes GET /api/widget", "The endpoint returns TopoloWidgetApiResponse from @topolo/sdk", "The endpoint derives organization/user context from the app auth boundary"];
1545
- readonly implementation: readonly ["Import createTopoloWidgetResponse from @topolo/sdk in the app backend", "Return native app metrics or action widgets instead of generic launch-card fallbacks", "Add a route-level test that validates the response with validateTopoloWidgetResponse"];
1544
+ readonly evidence: readonly ["The app-owned API exposes GET /api/widget", "The endpoint returns TopoloWidgetApiResponse from @topolo/sdk", "Every returned widget includes snapshot.summary with app-owned operational prose", "The endpoint derives organization/user context from the app auth boundary"];
1545
+ readonly implementation: readonly ["Import createTopoloWidgetResponse from @topolo/sdk in the app backend", "Return native app metrics or action widgets instead of generic launch-card fallbacks", "Populate snapshot.summary in the producing app for every widget instead of synthesizing summaries in TopoloOne", "Add a route-level test that validates the response with validateTopoloWidgetResponse"];
1546
1546
  }, {
1547
1547
  readonly id: "observability-and-audit";
1548
1548
  readonly title: "Emit platform audit and debugging signals";
@@ -1971,7 +1971,7 @@ interface TopoloWidgetBase {
1971
1971
  serviceIcon?: string;
1972
1972
  type: TopoloWidgetType;
1973
1973
  lastUpdated: string;
1974
- snapshot?: TopoloWidgetSnapshotMetadata;
1974
+ snapshot: TopoloWidgetSnapshotMetadata;
1975
1975
  }
1976
1976
  interface TopoloWidgetMetric {
1977
1977
  key?: string;
@@ -1995,7 +1995,7 @@ interface TopoloWidgetSnapshotMetadata {
1995
1995
  scope?: string;
1996
1996
  scopeLabel?: string;
1997
1997
  generatedAt?: string;
1998
- summary?: string;
1998
+ summary: string;
1999
1999
  defaultMetricKeys?: string[];
2000
2000
  availableMetrics?: TopoloWidgetSnapshotMetric[];
2001
2001
  highlights?: string[];
package/dist/index.js CHANGED
@@ -88,7 +88,7 @@ var SERVICES = {
88
88
  nexus: "https://nexus-api.topolo.app",
89
89
  notify: "https://notify.topolo.app",
90
90
  observability_watch: "https://observability-watch.topolo.app",
91
- one: "https://one.topolo.app",
91
+ one: "https://www.topolo.app",
92
92
  p2p: "https://p2p.topolo.app",
93
93
  pay: "https://pay.topolo.app",
94
94
  people: "https://people.topolo.app",
@@ -1095,20 +1095,23 @@ var APPLICATIONS = {
1095
1095
  {
1096
1096
  "name": "topolo-one",
1097
1097
  "kind": "worker",
1098
- "apiUrl": null,
1099
- "domains": [],
1100
- "routes": [],
1098
+ "apiUrl": "https://one.topolo.app",
1099
+ "domains": [
1100
+ "one.topolo.app"
1101
+ ],
1102
+ "routes": [
1103
+ "one.topolo.app/*"
1104
+ ],
1101
1105
  "serviceId": null
1102
1106
  },
1103
1107
  {
1104
1108
  "name": "topolo-one-api",
1105
1109
  "kind": "worker",
1106
- "apiUrl": "https://one.topolo.app",
1110
+ "apiUrl": "https://www.topolo.app",
1107
1111
  "domains": [
1108
- "one.topolo.app"
1112
+ "www.topolo.app"
1109
1113
  ],
1110
1114
  "routes": [
1111
- "one.topolo.app/*",
1112
1115
  "www.topolo.app/*"
1113
1116
  ],
1114
1117
  "serviceId": "one"
@@ -1661,7 +1664,7 @@ var APPLICATIONS = {
1661
1664
  };
1662
1665
 
1663
1666
  // src/application-requirements.ts
1664
- var APPLICATION_REQUIREMENTS_VERSION = "2026-05-01.1";
1667
+ var APPLICATION_REQUIREMENTS_VERSION = "2026-06-07.1";
1665
1668
  var APPLICATION_REQUIREMENTS = [
1666
1669
  {
1667
1670
  id: "platform-metadata",
@@ -1805,11 +1808,13 @@ var APPLICATION_REQUIREMENTS = [
1805
1808
  evidence: [
1806
1809
  "The app-owned API exposes GET /api/widget",
1807
1810
  "The endpoint returns TopoloWidgetApiResponse from @topolo/sdk",
1811
+ "Every returned widget includes snapshot.summary with app-owned operational prose",
1808
1812
  "The endpoint derives organization/user context from the app auth boundary"
1809
1813
  ],
1810
1814
  implementation: [
1811
1815
  "Import createTopoloWidgetResponse from @topolo/sdk in the app backend",
1812
1816
  "Return native app metrics or action widgets instead of generic launch-card fallbacks",
1817
+ "Populate snapshot.summary in the producing app for every widget instead of synthesizing summaries in TopoloOne",
1813
1818
  "Add a route-level test that validates the response with validateTopoloWidgetResponse"
1814
1819
  ]
1815
1820
  },
@@ -3137,12 +3142,14 @@ function validateWidget(widget, path, errors) {
3137
3142
  }
3138
3143
  function validateSnapshotMetadata(snapshot, path, errors) {
3139
3144
  if (snapshot === void 0) {
3145
+ errors.push(`${path} must be an object`);
3140
3146
  return;
3141
3147
  }
3142
3148
  if (!isRecord(snapshot)) {
3143
- errors.push(`${path} must be an object when provided`);
3149
+ errors.push(`${path} must be an object`);
3144
3150
  return;
3145
3151
  }
3152
+ validateNonEmptyString(snapshot.summary, `${path}.summary`, errors);
3146
3153
  if (snapshot.defaultMetricKeys !== void 0 && !Array.isArray(snapshot.defaultMetricKeys)) {
3147
3154
  errors.push(`${path}.defaultMetricKeys must be an array when provided`);
3148
3155
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topolo/sdk",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "description": "Typed client SDK for the Topolo platform. Used by TopoloCli, TopoloMCP, and third-party agents.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -4,7 +4,7 @@ import {
4
4
  type ApplicationId,
5
5
  } from './applications.generated.js';
6
6
 
7
- export const APPLICATION_REQUIREMENTS_VERSION = '2026-05-01.1';
7
+ export const APPLICATION_REQUIREMENTS_VERSION = '2026-06-07.1';
8
8
 
9
9
  export type ApplicationRequirementScope =
10
10
  | 'all'
@@ -218,11 +218,13 @@ export const APPLICATION_REQUIREMENTS = [
218
218
  evidence: [
219
219
  'The app-owned API exposes GET /api/widget',
220
220
  'The endpoint returns TopoloWidgetApiResponse from @topolo/sdk',
221
+ 'Every returned widget includes snapshot.summary with app-owned operational prose',
221
222
  'The endpoint derives organization/user context from the app auth boundary',
222
223
  ],
223
224
  implementation: [
224
225
  'Import createTopoloWidgetResponse from @topolo/sdk in the app backend',
225
226
  'Return native app metrics or action widgets instead of generic launch-card fallbacks',
227
+ 'Populate snapshot.summary in the producing app for every widget instead of synthesizing summaries in TopoloOne',
226
228
  'Add a route-level test that validates the response with validateTopoloWidgetResponse',
227
229
  ],
228
230
  },
@@ -988,20 +988,23 @@ export const APPLICATIONS = {
988
988
  {
989
989
  "name": "topolo-one",
990
990
  "kind": "worker",
991
- "apiUrl": null,
992
- "domains": [],
993
- "routes": [],
991
+ "apiUrl": "https://one.topolo.app",
992
+ "domains": [
993
+ "one.topolo.app"
994
+ ],
995
+ "routes": [
996
+ "one.topolo.app/*"
997
+ ],
994
998
  "serviceId": null
995
999
  },
996
1000
  {
997
1001
  "name": "topolo-one-api",
998
1002
  "kind": "worker",
999
- "apiUrl": "https://one.topolo.app",
1003
+ "apiUrl": "https://www.topolo.app",
1000
1004
  "domains": [
1001
- "one.topolo.app"
1005
+ "www.topolo.app"
1002
1006
  ],
1003
1007
  "routes": [
1004
- "one.topolo.app/*",
1005
1008
  "www.topolo.app/*"
1006
1009
  ],
1007
1010
  "serviceId": "one"
@@ -33,7 +33,7 @@ export const SERVICES = {
33
33
  nexus: "https://nexus-api.topolo.app",
34
34
  notify: "https://notify.topolo.app",
35
35
  observability_watch: "https://observability-watch.topolo.app",
36
- one: "https://one.topolo.app",
36
+ one: "https://www.topolo.app",
37
37
  p2p: "https://p2p.topolo.app",
38
38
  pay: "https://pay.topolo.app",
39
39
  people: "https://people.topolo.app",
@@ -71,7 +71,7 @@ describe('service registry', () => {
71
71
  consent: 'https://consent.topolo.app',
72
72
  feed: 'https://feed-api.topolo.app',
73
73
  mail: 'https://mail-api.topolo.app',
74
- one: 'https://one.topolo.app',
74
+ one: 'https://www.topolo.app',
75
75
  web: 'https://web-api.topolo.app',
76
76
  });
77
77
  expect(DEFAULT_SERVICE_URLS).toMatchObject({
@@ -21,6 +21,9 @@ describe("Topolo widget contract", () => {
21
21
  widgets: [
22
22
  {
23
23
  type: "stats",
24
+ snapshot: {
25
+ summary: "Bytes is reporting 12 files in the current workspace.",
26
+ },
24
27
  stats: [{ key: "files", label: "Files", value: 12 }],
25
28
  },
26
29
  ],
@@ -34,6 +37,9 @@ describe("Topolo widget contract", () => {
34
37
  serviceName: "Bytes",
35
38
  lastUpdated: "2026-04-25T00:00:00.000Z",
36
39
  type: "stats",
40
+ snapshot: {
41
+ summary: "Bytes is reporting 12 files in the current workspace.",
42
+ },
37
43
  stats: [{ key: "files", label: "Files", value: 12 }],
38
44
  },
39
45
  ],
@@ -51,6 +57,9 @@ describe("Topolo widget contract", () => {
51
57
  serviceName: "CRM",
52
58
  lastUpdated: "2026-04-24T23:00:00.000Z",
53
59
  type: "stats",
60
+ snapshot: {
61
+ summary: "CRM is tracking 3 contacts for this workspace.",
62
+ },
54
63
  stats: [{ label: "Contacts", value: 3 }],
55
64
  },
56
65
  ],
@@ -79,6 +88,9 @@ describe("Topolo widget contract", () => {
79
88
  widgets: [
80
89
  {
81
90
  type: "notifications",
91
+ snapshot: {
92
+ summary: "CRM has one unread lead notification for review.",
93
+ },
82
94
  unreadCount: 1,
83
95
  notifications: [
84
96
  {
@@ -100,7 +112,7 @@ describe("Topolo widget contract", () => {
100
112
  });
101
113
  });
102
114
 
103
- it("keeps optional snapshot metadata for configurable TopoloOne datapoints", () => {
115
+ it("keeps required snapshot metadata for configurable TopoloOne datapoints", () => {
104
116
  const response = createTopoloWidgetResponse({
105
117
  serviceId: "service_admin",
106
118
  serviceName: "Admin",
@@ -111,6 +123,8 @@ describe("Topolo widget contract", () => {
111
123
  stats: [{ label: "Team Members", value: 5 }],
112
124
  snapshot: {
113
125
  presetKey: "admin_morning_snapshot",
126
+ summary:
127
+ "Admin has 5 team members visible in the current workspace snapshot.",
114
128
  defaultMetricKeys: ["team_members"],
115
129
  availableMetrics: [
116
130
  {
@@ -131,6 +145,8 @@ describe("Topolo widget contract", () => {
131
145
  });
132
146
  expect(response.widgets[0]).toMatchObject({
133
147
  snapshot: {
148
+ summary:
149
+ "Admin has 5 team members visible in the current workspace snapshot.",
134
150
  defaultMetricKeys: ["team_members"],
135
151
  availableMetrics: [
136
152
  { key: "team_members", label: "Team Members", value: 5 },
@@ -156,7 +172,29 @@ describe("Topolo widget contract", () => {
156
172
  expect(result.errors).toEqual([
157
173
  "widgets[0].serviceId must be a non-empty string",
158
174
  "widgets[0].lastUpdated must be a non-empty string",
175
+ "widgets[0].snapshot must be an object",
159
176
  "widgets[0].type must be a supported Topolo widget type",
160
177
  ]);
161
178
  });
179
+
180
+ it("reports missing snapshot summaries", () => {
181
+ const result = validateTopoloWidgetResponse({
182
+ success: true,
183
+ widgets: [
184
+ {
185
+ serviceId: "service_bad",
186
+ serviceName: "Bad App",
187
+ lastUpdated: "2026-04-25T00:00:00.000Z",
188
+ type: "stats",
189
+ snapshot: { summary: "" },
190
+ stats: [],
191
+ },
192
+ ],
193
+ });
194
+
195
+ expect(result.ok).toBe(false);
196
+ expect(result.errors).toEqual([
197
+ "widgets[0].snapshot.summary must be a non-empty string",
198
+ ]);
199
+ });
162
200
  });
package/src/widgets.ts CHANGED
@@ -14,7 +14,7 @@ export interface TopoloWidgetBase {
14
14
  serviceIcon?: string;
15
15
  type: TopoloWidgetType;
16
16
  lastUpdated: string;
17
- snapshot?: TopoloWidgetSnapshotMetadata;
17
+ snapshot: TopoloWidgetSnapshotMetadata;
18
18
  }
19
19
 
20
20
  export interface TopoloWidgetMetric {
@@ -41,7 +41,7 @@ export interface TopoloWidgetSnapshotMetadata {
41
41
  scope?: string;
42
42
  scopeLabel?: string;
43
43
  generatedAt?: string;
44
- summary?: string;
44
+ summary: string;
45
45
  defaultMetricKeys?: string[];
46
46
  availableMetrics?: TopoloWidgetSnapshotMetric[];
47
47
  highlights?: string[];
@@ -291,12 +291,14 @@ function validateSnapshotMetadata(
291
291
  errors: string[],
292
292
  ) {
293
293
  if (snapshot === undefined) {
294
+ errors.push(`${path} must be an object`);
294
295
  return;
295
296
  }
296
297
  if (!isRecord(snapshot)) {
297
- errors.push(`${path} must be an object when provided`);
298
+ errors.push(`${path} must be an object`);
298
299
  return;
299
300
  }
301
+ validateNonEmptyString(snapshot.summary, `${path}.summary`, errors);
300
302
  if (
301
303
  snapshot.defaultMetricKeys !== undefined &&
302
304
  !Array.isArray(snapshot.defaultMetricKeys)