@topolo/sdk 0.2.12 → 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/index.js CHANGED
@@ -91,41 +91,20 @@ var SERVICES = {
91
91
  p2p: "https://p2p.topolo.app",
92
92
  pay: "https://pay.topolo.app",
93
93
  people: "https://people.topolo.app",
94
+ pulse: "https://pulse.topolo.app",
94
95
  "quro.api": "https://api.ol0.me",
95
96
  "quro.redirect": "https://ol0.me",
96
97
  roadmapper: "https://roadmapper-api.topolo.app",
97
98
  sign: "https://sign.topolo.app",
98
99
  social_studio: "https://studio-api.topolo.app",
99
100
  socialize: "https://socialize-api.topolo.app",
101
+ spaces: "https://spaces.topolo.app",
100
102
  status: "https://status.topolo.app",
101
103
  support: "https://support.topolo.app",
102
104
  survey: "https://survey.topolo.app",
103
105
  voice: "https://voice.topolo.app",
104
106
  web: "https://web-api.topolo.app"
105
107
  };
106
- var PLATFORM_SERVICE_IDS = {
107
- books: "srv_0g260hbN8oqv",
108
- bugfix: "srv_kQ1t8qUSDgNs",
109
- compose: "srv_aR3ETYmgygmd",
110
- consent: "srv_8FIGR7Q7zP9r",
111
- crm: "srv_iCwM4jGXcwlj",
112
- customer_success: "srv_RWpOxkgZdZbH",
113
- developers: "srv_NjDq9G9qyyjr",
114
- director: "srv_wRCnLLbtvmVT",
115
- flow: "srv_GBCuKkHhnRXv",
116
- forms: "srv_UE7IcGe80J5o",
117
- home: "srv_hE6HBldVzjth",
118
- insights: "srv_DgbBMwWSIhmC",
119
- inventory: "srv_hmvjbv3ZxDnh",
120
- localize: "srv_Ze1g9OmXuZjF",
121
- notify: "srv_9pRdkIjYyKX0",
122
- p2p: "srv_4BzeLqH0wCve",
123
- people: "srv_VDMBAonA5fKb",
124
- sign: "srv_Bd8pRw5ZqRBT",
125
- status: "srv_topolo_status",
126
- survey: "srv_RCOi7CJkwf7R",
127
- voice: "srv_BZANHkshGAK5"
128
- };
129
108
 
130
109
  // src/applications.generated.ts
131
110
  var APPLICATIONS = {
@@ -205,6 +184,24 @@ var APPLICATIONS = {
205
184
  }
206
185
  ]
207
186
  },
187
+ "backup": {
188
+ "id": "backup",
189
+ "appDir": "TopoloBackup",
190
+ "name": "topolo-backup",
191
+ "packageName": "topolo-backup",
192
+ "productionUrl": null,
193
+ "services": [],
194
+ "deployTargets": [
195
+ {
196
+ "name": "topolo-backup",
197
+ "kind": "worker",
198
+ "apiUrl": null,
199
+ "domains": [],
200
+ "routes": [],
201
+ "serviceId": null
202
+ }
203
+ ]
204
+ },
208
205
  "books": {
209
206
  "id": "books",
210
207
  "appDir": "TopoloBooks",
@@ -355,15 +352,6 @@ var APPLICATIONS = {
355
352
  }
356
353
  ]
357
354
  },
358
- "cli": {
359
- "id": "cli",
360
- "appDir": "TopoloCli",
361
- "name": "@topolo/cli",
362
- "packageName": "@topolo/cli",
363
- "productionUrl": null,
364
- "services": [],
365
- "deployTargets": []
366
- },
367
355
  "cloudcontrol": {
368
356
  "id": "cloudcontrol",
369
357
  "appDir": "TopoloCloudControl",
@@ -529,24 +517,6 @@ var APPLICATIONS = {
529
517
  }
530
518
  ]
531
519
  },
532
- "d1_backup": {
533
- "id": "d1_backup",
534
- "appDir": "TopoloD1Backup",
535
- "name": "topolo-d1-backup",
536
- "packageName": "topolo-d1-backup",
537
- "productionUrl": null,
538
- "services": [],
539
- "deployTargets": [
540
- {
541
- "name": "topolo-d1-backup",
542
- "kind": "worker",
543
- "apiUrl": null,
544
- "domains": [],
545
- "routes": [],
546
- "serviceId": null
547
- }
548
- ]
549
- },
550
520
  "developers": {
551
521
  "id": "developers",
552
522
  "appDir": "TopoloDevelopers",
@@ -889,15 +859,6 @@ var APPLICATIONS = {
889
859
  }
890
860
  ]
891
861
  },
892
- "mcp": {
893
- "id": "mcp",
894
- "appDir": "TopoloMCP",
895
- "name": "@topolo/mcp",
896
- "packageName": "@topolo/mcp",
897
- "productionUrl": null,
898
- "services": [],
899
- "deployTargets": []
900
- },
901
862
  "mdm": {
902
863
  "id": "mdm",
903
864
  "appDir": "TopoloMDM",
@@ -1032,7 +993,7 @@ var APPLICATIONS = {
1032
993
  "one": {
1033
994
  "id": "one",
1034
995
  "appDir": "TopoloOne",
1035
- "name": "topolo-oneclick-sso",
996
+ "name": "TopoloOne",
1036
997
  "packageName": "topolo-oneclick-sso",
1037
998
  "productionUrl": "https://one.topolo.app",
1038
999
  "services": [
@@ -1153,9 +1114,24 @@ var APPLICATIONS = {
1153
1114
  "appDir": "TopoloPulse",
1154
1115
  "name": "topolo-pulse",
1155
1116
  "packageName": "topolo-pulse",
1156
- "productionUrl": null,
1157
- "services": [],
1158
- "deployTargets": []
1117
+ "productionUrl": "https://pulse.topolo.app",
1118
+ "services": [
1119
+ "pulse"
1120
+ ],
1121
+ "deployTargets": [
1122
+ {
1123
+ "name": "topolo-pulse",
1124
+ "kind": "worker",
1125
+ "apiUrl": "https://pulse.topolo.app",
1126
+ "domains": [
1127
+ "pulse.topolo.app"
1128
+ ],
1129
+ "routes": [
1130
+ "pulse.topolo.app"
1131
+ ],
1132
+ "serviceId": "pulse"
1133
+ }
1134
+ ]
1159
1135
  },
1160
1136
  "quro": {
1161
1137
  "id": "quro",
@@ -1338,6 +1314,30 @@ var APPLICATIONS = {
1338
1314
  }
1339
1315
  ]
1340
1316
  },
1317
+ "spaces": {
1318
+ "id": "spaces",
1319
+ "appDir": "TopoloSpaces",
1320
+ "name": "topolo-spaces",
1321
+ "packageName": "@topolo/spaces",
1322
+ "productionUrl": "https://spaces.topolo.app",
1323
+ "services": [
1324
+ "spaces"
1325
+ ],
1326
+ "deployTargets": [
1327
+ {
1328
+ "name": "topolo-spaces",
1329
+ "kind": "worker",
1330
+ "apiUrl": "https://spaces.topolo.app",
1331
+ "domains": [
1332
+ "spaces.topolo.app"
1333
+ ],
1334
+ "routes": [
1335
+ "spaces.topolo.app"
1336
+ ],
1337
+ "serviceId": "spaces"
1338
+ }
1339
+ ]
1340
+ },
1341
1341
  "status": {
1342
1342
  "id": "status",
1343
1343
  "appDir": "TopoloStatus",
@@ -1519,12 +1519,12 @@ var APPLICATION_REQUIREMENTS = [
1519
1519
  summary: "Applications must derive identity, organization, scopes, and service entitlement from TopoloAuth rather than inventing parallel auth state.",
1520
1520
  appliesTo: ["browser", "api", "tooling", "agent_surface"],
1521
1521
  evidence: [
1522
- "@topolo/auth-client or @topolo/auth-middleware is used where applicable",
1522
+ "@topolo/auth-client or @topolo-io/worker-runtime is used where applicable",
1523
1523
  "No public command, tool, or SDK method accepts orgId",
1524
1524
  "Service slug and Auth service registration are documented for protected services"
1525
1525
  ],
1526
1526
  implementation: [
1527
- "Use shared auth packages instead of copying token parsing or browser storage flows",
1527
+ "Use shared auth/runtime packages instead of copying token parsing, gateway, or browser storage flows",
1528
1528
  "Derive organization from the credential on every request",
1529
1529
  "Route OAuth, refresh, logout, and SSO paths according to the shared Auth contract"
1530
1530
  ]
@@ -1664,14 +1664,15 @@ var APPLICATION_REQUIREMENTS = [
1664
1664
  ];
1665
1665
  function applicationRequirementScopes(application) {
1666
1666
  const scopes = /* @__PURE__ */ new Set(["all"]);
1667
+ const appId = application.id;
1667
1668
  const hasBrowserSurface = Boolean(application.productionUrl) || application.deployTargets.some((target) => target.kind === "pages");
1668
1669
  const hasApiSurface = application.services.length > 0 || application.deployTargets.some((target) => Boolean(target.apiUrl));
1669
1670
  if (hasBrowserSurface) scopes.add("browser");
1670
1671
  if (hasApiSurface) scopes.add("api");
1671
- if (application.id === "cli" || application.id === "mcp" || application.id === "cloudcontrol") {
1672
+ if (appId === "cli" || appId === "mcp" || appId === "cloudcontrol") {
1672
1673
  scopes.add("tooling");
1673
1674
  }
1674
- if (application.id === "cli" || application.id === "mcp" || hasApiSurface) {
1675
+ if (appId === "cli" || appId === "mcp" || hasApiSurface) {
1675
1676
  scopes.add("agent_surface");
1676
1677
  }
1677
1678
  return Array.from(scopes);
@@ -1790,12 +1791,13 @@ function evaluateRequirement(application, requirement) {
1790
1791
  return finding(requirement, "met", "CloudControl deploy target metadata is present.", evidence);
1791
1792
  }
1792
1793
  case "agent-and-operator-surfaces": {
1794
+ const appId = application.id;
1793
1795
  if (application.services.length > 0) {
1794
1796
  evidence.push(`services=${application.services.join(",")}`);
1795
1797
  return finding(requirement, "partial", "SDK service discovery exists; typed SDK/CLI/MCP workflow coverage should be checked as APIs stabilize.", evidence);
1796
1798
  }
1797
- if (application.id === "cli" || application.id === "mcp" || application.id === "cloudcontrol") {
1798
- evidence.push(`tooling=${application.id}`);
1799
+ if (appId === "cli" || appId === "mcp" || appId === "cloudcontrol") {
1800
+ evidence.push(`tooling=${appId}`);
1799
1801
  return finding(requirement, "met", "This application is itself an agent/operator surface.", evidence);
1800
1802
  }
1801
1803
  return finding(requirement, "missing", "No SDK service or tooling surface is registered.", evidence);
@@ -1870,20 +1872,38 @@ function resolveServiceUrl(service, overrides) {
1870
1872
 
1871
1873
  // src/client.ts
1872
1874
  var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
1873
- var TopoloClient = class {
1875
+ var POSITIVE_CACHE_TTL_MS = 5 * 60 * 1e3;
1876
+ var NEGATIVE_CACHE_TTL_MS = 60 * 1e3;
1877
+ var TopoloClient = class _TopoloClient {
1874
1878
  credential;
1875
1879
  agent;
1876
1880
  serviceUrls;
1881
+ serviceIds;
1877
1882
  requireConfirmForWrites;
1878
1883
  timeoutMs;
1879
1884
  fetchImpl;
1880
1885
  debug;
1886
+ /**
1887
+ * Process-wide cache for slug → service-id lookups. Keyed by
1888
+ * `${authBaseUrl}|${slug}` so multiple `TopoloClient` instances pointing at
1889
+ * the same Auth share, but staging vs prod don't poison each other.
1890
+ * Cleared via `TopoloClient.clearServiceIdCache()` if a test needs it.
1891
+ */
1892
+ static serviceIdCache = /* @__PURE__ */ new Map();
1893
+ /**
1894
+ * Drop the slug→service-id cache. Test-only — production callers should
1895
+ * let the 5-minute TTL expire naturally.
1896
+ */
1897
+ static clearServiceIdCache() {
1898
+ _TopoloClient.serviceIdCache.clear();
1899
+ }
1881
1900
  constructor(options) {
1882
1901
  if (!options.credential) throw new TopoloAuthError("credential is required");
1883
1902
  if (!options.agent?.clientName) throw new TopoloAuthError("agent.clientName is required");
1884
1903
  this.credential = options.credential;
1885
1904
  this.agent = options.agent;
1886
1905
  this.serviceUrls = options.serviceUrls;
1906
+ this.serviceIds = options.serviceIds;
1887
1907
  this.requireConfirmForWrites = options.requireConfirmForWrites !== false;
1888
1908
  this.timeoutMs = options.timeoutMs ?? 3e4;
1889
1909
  this.fetchImpl = options.fetch ?? fetch;
@@ -1918,7 +1938,7 @@ var TopoloClient = class {
1918
1938
  const headers = new Headers();
1919
1939
  headers.set("Accept", "application/json");
1920
1940
  if (opts.body !== void 0) headers.set("Content-Type", "application/json");
1921
- const platformServiceId = PLATFORM_SERVICE_IDS[opts.service];
1941
+ const platformServiceId = resolveConfiguredServiceId(opts.service, this.serviceIds);
1922
1942
  if (platformServiceId) headers.set("X-Service-ID", platformServiceId);
1923
1943
  applyAuthHeaders(headers, this.credential);
1924
1944
  const requestId = generateRequestId();
@@ -1992,6 +2012,115 @@ var TopoloClient = class {
1992
2012
  });
1993
2013
  return parsed;
1994
2014
  }
2015
+ /**
2016
+ * Resolve a service-slug → opaque service-id via caller config or TopoloAuth.
2017
+ *
2018
+ * Cache: 5 minutes per process per `(slug, auth_base_url)`. Negative
2019
+ * results (404) are cached for 60s so a typo doesn't hot-loop the
2020
+ * resolver. Returns null when the slug isn't registered — callers decide
2021
+ * whether to throw.
2022
+ *
2023
+ * No credential is sent: the endpoint is public (slugs aren't secret).
2024
+ * This keeps the resolver usable during early bootstrap before a caller
2025
+ * has acquired an access token.
2026
+ */
2027
+ async getServiceIdBySlug(slug) {
2028
+ if (typeof slug !== "string") return null;
2029
+ const normalized = slug.trim().toLowerCase();
2030
+ if (!normalized) return null;
2031
+ const configuredServiceId = resolveConfiguredServiceId(normalized, this.serviceIds);
2032
+ if (configuredServiceId) return configuredServiceId;
2033
+ const authBase = resolveServiceUrl("auth", this.serviceUrls);
2034
+ const cacheKey = `${authBase}|${normalized}`;
2035
+ const now = Date.now();
2036
+ const cached = _TopoloClient.serviceIdCache.get(cacheKey);
2037
+ if (cached && cached.expiresAt > now) {
2038
+ return cached.value;
2039
+ }
2040
+ const url = new URL(
2041
+ `/api/services/by-slug/${encodeURIComponent(normalized)}`,
2042
+ authBase.endsWith("/") ? authBase : `${authBase}/`
2043
+ ).toString();
2044
+ let res;
2045
+ try {
2046
+ res = await this.fetchImpl(url, {
2047
+ method: "GET",
2048
+ headers: { Accept: "application/json" }
2049
+ });
2050
+ } catch {
2051
+ return null;
2052
+ }
2053
+ if (res.status === 404) {
2054
+ _TopoloClient.serviceIdCache.set(cacheKey, {
2055
+ value: null,
2056
+ expiresAt: now + NEGATIVE_CACHE_TTL_MS
2057
+ });
2058
+ return null;
2059
+ }
2060
+ if (!res.ok) {
2061
+ return null;
2062
+ }
2063
+ const body = await res.json().catch(() => null);
2064
+ const serviceId = body?.data?.service_id;
2065
+ const value = typeof serviceId === "string" && serviceId ? serviceId : null;
2066
+ _TopoloClient.serviceIdCache.set(cacheKey, {
2067
+ value,
2068
+ expiresAt: now + POSITIVE_CACHE_TTL_MS
2069
+ });
2070
+ return value;
2071
+ }
2072
+ /**
2073
+ * List the platform service catalog from the live registry
2074
+ * (`GET /api/services/catalog` on TopoloAuth). Credential-aware: the
2075
+ * caller's credential is attached, so an authenticated caller in an
2076
+ * entitled org also receives the `developer_only` services its role
2077
+ * permits; an unauthenticated/agent caller receives `active` only.
2078
+ *
2079
+ * Resilient: on any network/HTTP failure this falls back to the
2080
+ * build-time generated `APPLICATIONS` snapshot so offline/degraded
2081
+ * callers still get a usable (if static) catalog. The live result is
2082
+ * authoritative; the snapshot is a floor, not a cap.
2083
+ */
2084
+ async listServices() {
2085
+ try {
2086
+ const res = await this.request({ service: "auth", method: "GET", path: "/api/services/catalog" });
2087
+ const raw = res.data?.services ?? res.services;
2088
+ if (Array.isArray(raw) && raw.length > 0) {
2089
+ const entries = [];
2090
+ for (const item of raw) {
2091
+ const s = item;
2092
+ const serviceId = typeof s.service_id === "string" ? s.service_id : typeof s.id === "string" ? s.id : null;
2093
+ if (!serviceId) continue;
2094
+ entries.push({
2095
+ serviceId,
2096
+ slug: typeof s.app_slug === "string" ? s.app_slug : null,
2097
+ name: typeof s.name === "string" ? s.name : null,
2098
+ description: typeof s.description === "string" ? s.description : null,
2099
+ baseUrl: typeof s.base_url === "string" ? s.base_url : null,
2100
+ status: typeof s.status === "string" ? s.status : null,
2101
+ surfaceKind: typeof s.surface_kind === "string" ? s.surface_kind : null,
2102
+ launchable: s.launchable === true
2103
+ });
2104
+ }
2105
+ if (entries.length > 0) return entries;
2106
+ }
2107
+ } catch {
2108
+ }
2109
+ return this.generatedCatalogFallback();
2110
+ }
2111
+ generatedCatalogFallback() {
2112
+ const apps = Object.values(APPLICATIONS);
2113
+ return apps.map((app) => ({
2114
+ serviceId: app.id,
2115
+ slug: app.id,
2116
+ name: app.name ?? app.id,
2117
+ description: null,
2118
+ baseUrl: app.productionUrl ?? null,
2119
+ status: "active",
2120
+ surfaceKind: "application",
2121
+ launchable: Boolean(app.productionUrl)
2122
+ }));
2123
+ }
1995
2124
  /**
1996
2125
  * Introspect the current credential. Returns the caller's resolved identity,
1997
2126
  * organization, and permission set. Used by CLI `whoami` and by MCP to gate
@@ -2028,6 +2157,26 @@ var TopoloClient = class {
2028
2157
  };
2029
2158
  }
2030
2159
  };
2160
+ function resolveConfiguredServiceId(slug, serviceIds) {
2161
+ for (const serviceKey of serviceKeyCandidates(slug)) {
2162
+ const configured = serviceIds?.[serviceKey];
2163
+ if (typeof configured === "string" && configured.trim()) return configured.trim();
2164
+ const envKey = `TOPOLO_SERVICE_ID_${serviceKey.replace(/\./g, "_").toUpperCase()}`;
2165
+ const envValue = typeof process !== "undefined" ? process.env?.[envKey] : void 0;
2166
+ if (typeof envValue === "string" && envValue.trim()) return envValue.trim();
2167
+ }
2168
+ return null;
2169
+ }
2170
+ function serviceKeyCandidates(slug) {
2171
+ const normalized = slug.trim().toLowerCase();
2172
+ const withoutTopolo = normalized.startsWith("topolo-") ? normalized.slice("topolo-".length) : normalized;
2173
+ return [.../* @__PURE__ */ new Set([
2174
+ normalized,
2175
+ normalized.replace(/-/g, "_"),
2176
+ withoutTopolo,
2177
+ withoutTopolo.replace(/-/g, "_")
2178
+ ])];
2179
+ }
2031
2180
  function describeError(body, fallback) {
2032
2181
  if (body && typeof body === "object") {
2033
2182
  const maybe = body;
@@ -2593,7 +2742,6 @@ export {
2593
2742
  DEFAULT_SERVICE_URLS,
2594
2743
  IdentityModule,
2595
2744
  P2PModule,
2596
- PLATFORM_SERVICE_IDS,
2597
2745
  TOPOLO_WIDGET_ENDPOINT,
2598
2746
  TOPOLO_WIDGET_REFRESH_INTERVAL_MS,
2599
2747
  TopoloAuthError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topolo/sdk",
3
- "version": "0.2.12",
3
+ "version": "0.4.0",
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",
@@ -25,10 +25,13 @@
25
25
  "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
26
26
  "typecheck": "node scripts/generate-services.mjs && tsc --noEmit",
27
27
  "test": "vitest run",
28
- "test:watch": "vitest"
28
+ "test:watch": "vitest",
29
+ "prepublishOnly": "npm run build",
30
+ "coverage": "vitest run --coverage"
29
31
  },
30
32
  "devDependencies": {
31
33
  "@types/node": "^20.19.39",
34
+ "@vitest/coverage-v8": "^2.1.0",
32
35
  "tsup": "^8.0.0",
33
36
  "typescript": "^5.4.0",
34
37
  "vitest": "^2.1.0"
@@ -108,12 +108,12 @@ export const APPLICATION_REQUIREMENTS = [
108
108
  'Applications must derive identity, organization, scopes, and service entitlement from TopoloAuth rather than inventing parallel auth state.',
109
109
  appliesTo: ['browser', 'api', 'tooling', 'agent_surface'],
110
110
  evidence: [
111
- '@topolo/auth-client or @topolo/auth-middleware is used where applicable',
111
+ '@topolo/auth-client or @topolo-io/worker-runtime is used where applicable',
112
112
  'No public command, tool, or SDK method accepts orgId',
113
113
  'Service slug and Auth service registration are documented for protected services',
114
114
  ],
115
115
  implementation: [
116
- 'Use shared auth packages instead of copying token parsing or browser storage flows',
116
+ 'Use shared auth/runtime packages instead of copying token parsing, gateway, or browser storage flows',
117
117
  'Derive organization from the credential on every request',
118
118
  'Route OAuth, refresh, logout, and SSO paths according to the shared Auth contract',
119
119
  ],
@@ -264,6 +264,9 @@ export function applicationRequirementScopes(
264
264
  application: ApplicationCatalogEntry,
265
265
  ): ApplicationRequirementScope[] {
266
266
  const scopes = new Set<ApplicationRequirementScope>(['all']);
267
+ // id is checked by value: tooling ids (cli/mcp) are intentionally not in
268
+ // the deploy catalog union, so compare as string rather than assert overlap.
269
+ const appId: string = application.id;
267
270
  const hasBrowserSurface =
268
271
  Boolean(application.productionUrl) || application.deployTargets.some((target) => target.kind === 'pages');
269
272
  const hasApiSurface =
@@ -272,10 +275,10 @@ export function applicationRequirementScopes(
272
275
 
273
276
  if (hasBrowserSurface) scopes.add('browser');
274
277
  if (hasApiSurface) scopes.add('api');
275
- if (application.id === 'cli' || application.id === 'mcp' || application.id === 'cloudcontrol') {
278
+ if (appId === 'cli' || appId === 'mcp' || appId === 'cloudcontrol') {
276
279
  scopes.add('tooling');
277
280
  }
278
- if (application.id === 'cli' || application.id === 'mcp' || hasApiSurface) {
281
+ if (appId === 'cli' || appId === 'mcp' || hasApiSurface) {
279
282
  scopes.add('agent_surface');
280
283
  }
281
284
 
@@ -420,12 +423,13 @@ function evaluateRequirement(
420
423
  return finding(requirement, 'met', 'CloudControl deploy target metadata is present.', evidence);
421
424
  }
422
425
  case 'agent-and-operator-surfaces': {
426
+ const appId: string = application.id;
423
427
  if (application.services.length > 0) {
424
428
  evidence.push(`services=${application.services.join(',')}`);
425
429
  return finding(requirement, 'partial', 'SDK service discovery exists; typed SDK/CLI/MCP workflow coverage should be checked as APIs stabilize.', evidence);
426
430
  }
427
- if (application.id === 'cli' || application.id === 'mcp' || application.id === 'cloudcontrol') {
428
- evidence.push(`tooling=${application.id}`);
431
+ if (appId === 'cli' || appId === 'mcp' || appId === 'cloudcontrol') {
432
+ evidence.push(`tooling=${appId}`);
429
433
  return finding(requirement, 'met', 'This application is itself an agent/operator surface.', evidence);
430
434
  }
431
435
  return finding(requirement, 'missing', 'No SDK service or tooling surface is registered.', evidence);
@@ -79,6 +79,24 @@ export const APPLICATIONS = {
79
79
  }
80
80
  ]
81
81
  },
82
+ "backup": {
83
+ "id": "backup",
84
+ "appDir": "TopoloBackup",
85
+ "name": "topolo-backup",
86
+ "packageName": "topolo-backup",
87
+ "productionUrl": null,
88
+ "services": [],
89
+ "deployTargets": [
90
+ {
91
+ "name": "topolo-backup",
92
+ "kind": "worker",
93
+ "apiUrl": null,
94
+ "domains": [],
95
+ "routes": [],
96
+ "serviceId": null
97
+ }
98
+ ]
99
+ },
82
100
  "books": {
83
101
  "id": "books",
84
102
  "appDir": "TopoloBooks",
@@ -229,15 +247,6 @@ export const APPLICATIONS = {
229
247
  }
230
248
  ]
231
249
  },
232
- "cli": {
233
- "id": "cli",
234
- "appDir": "TopoloCli",
235
- "name": "@topolo/cli",
236
- "packageName": "@topolo/cli",
237
- "productionUrl": null,
238
- "services": [],
239
- "deployTargets": []
240
- },
241
250
  "cloudcontrol": {
242
251
  "id": "cloudcontrol",
243
252
  "appDir": "TopoloCloudControl",
@@ -403,24 +412,6 @@ export const APPLICATIONS = {
403
412
  }
404
413
  ]
405
414
  },
406
- "d1_backup": {
407
- "id": "d1_backup",
408
- "appDir": "TopoloD1Backup",
409
- "name": "topolo-d1-backup",
410
- "packageName": "topolo-d1-backup",
411
- "productionUrl": null,
412
- "services": [],
413
- "deployTargets": [
414
- {
415
- "name": "topolo-d1-backup",
416
- "kind": "worker",
417
- "apiUrl": null,
418
- "domains": [],
419
- "routes": [],
420
- "serviceId": null
421
- }
422
- ]
423
- },
424
415
  "developers": {
425
416
  "id": "developers",
426
417
  "appDir": "TopoloDevelopers",
@@ -763,15 +754,6 @@ export const APPLICATIONS = {
763
754
  }
764
755
  ]
765
756
  },
766
- "mcp": {
767
- "id": "mcp",
768
- "appDir": "TopoloMCP",
769
- "name": "@topolo/mcp",
770
- "packageName": "@topolo/mcp",
771
- "productionUrl": null,
772
- "services": [],
773
- "deployTargets": []
774
- },
775
757
  "mdm": {
776
758
  "id": "mdm",
777
759
  "appDir": "TopoloMDM",
@@ -906,7 +888,7 @@ export const APPLICATIONS = {
906
888
  "one": {
907
889
  "id": "one",
908
890
  "appDir": "TopoloOne",
909
- "name": "topolo-oneclick-sso",
891
+ "name": "TopoloOne",
910
892
  "packageName": "topolo-oneclick-sso",
911
893
  "productionUrl": "https://one.topolo.app",
912
894
  "services": [
@@ -1027,9 +1009,24 @@ export const APPLICATIONS = {
1027
1009
  "appDir": "TopoloPulse",
1028
1010
  "name": "topolo-pulse",
1029
1011
  "packageName": "topolo-pulse",
1030
- "productionUrl": null,
1031
- "services": [],
1032
- "deployTargets": []
1012
+ "productionUrl": "https://pulse.topolo.app",
1013
+ "services": [
1014
+ "pulse"
1015
+ ],
1016
+ "deployTargets": [
1017
+ {
1018
+ "name": "topolo-pulse",
1019
+ "kind": "worker",
1020
+ "apiUrl": "https://pulse.topolo.app",
1021
+ "domains": [
1022
+ "pulse.topolo.app"
1023
+ ],
1024
+ "routes": [
1025
+ "pulse.topolo.app"
1026
+ ],
1027
+ "serviceId": "pulse"
1028
+ }
1029
+ ]
1033
1030
  },
1034
1031
  "quro": {
1035
1032
  "id": "quro",
@@ -1212,6 +1209,30 @@ export const APPLICATIONS = {
1212
1209
  }
1213
1210
  ]
1214
1211
  },
1212
+ "spaces": {
1213
+ "id": "spaces",
1214
+ "appDir": "TopoloSpaces",
1215
+ "name": "topolo-spaces",
1216
+ "packageName": "@topolo/spaces",
1217
+ "productionUrl": "https://spaces.topolo.app",
1218
+ "services": [
1219
+ "spaces"
1220
+ ],
1221
+ "deployTargets": [
1222
+ {
1223
+ "name": "topolo-spaces",
1224
+ "kind": "worker",
1225
+ "apiUrl": "https://spaces.topolo.app",
1226
+ "domains": [
1227
+ "spaces.topolo.app"
1228
+ ],
1229
+ "routes": [
1230
+ "spaces.topolo.app"
1231
+ ],
1232
+ "serviceId": "spaces"
1233
+ }
1234
+ ]
1235
+ },
1215
1236
  "status": {
1216
1237
  "id": "status",
1217
1238
  "appDir": "TopoloStatus",