@onlineapps/service-wrapper 4.1.1 → 4.1.3

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/CHANGELOG.md CHANGED
@@ -4,11 +4,50 @@ All notable changes to this package. Follows [Keep a Changelog](https://keepacha
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
- **Patch.** No exported API moves: `BUSINESS_ERROR_BRAND`, `isBusinessError`, the
7
+ **Minor.** No exported API moves: `BUSINESS_ERROR_BRAND`, `isBusinessError`, the
8
8
  error classes, `ServiceWrapper` and `bootstrap` are untouched. What changes is
9
- startup behaviour and one field inside `conn-runtime/validation-failure.json`,
10
- a runtime file this package writes and reads alone — not a contract any consumer
11
- compiles against.
9
+ startup behaviour, one field inside `conn-runtime/validation-failure.json` — a
10
+ runtime file this package writes and reads alone — and one field that disappears
11
+ from the loaded config (below).
12
+
13
+ ### Removed — the `|| ''` fallback on `specificationEndpoint` (DÁVKA 79 B)
14
+
15
+ `_initializeRegistry` read `this.config.service?.specificationEndpoint || ''`.
16
+ The branch could never fire: `ConfigLoader.js:360-365` refuses a config that does
17
+ not declare the field, and all eight biz services build their config through
18
+ `ConfigLoader.loadAll` (`src/config/index.js` in converter, emailer,
19
+ hello-service, ingest, invoicing, meta, pdfgen, property), with `bootstrap()`
20
+ passing it straight through (`src/index.js:81`).
21
+
22
+ A fallback that cannot fire is not harmless — it tells the reader that an empty
23
+ endpoint is an expected state, while the registry warns about exactly that case
24
+ (`api/infra/api_services_registry/src/listeners/registry.listener.js:474-477`).
25
+ `architecture-principles.md` §3.
26
+
27
+ Behaviour is unchanged, which is the point: the new control suite
28
+ `tests/unit/ServiceWrapper.specificationEndpoint.test.js` is green before and
29
+ after, and asserts the value reaching the registry client verbatim.
30
+
31
+ ### Removed — `config.service.port` is no longer parsed (DÁVKA 77 B)
32
+
33
+ ADR 0005 removed the HTTP surface from biz containers. `ConfigLoader` parsed
34
+ `service.port` into the loaded config anyway, and nothing read it back: measured
35
+ 2026-08-30, the only `.port` reads in `src/` are the Redis/state URL ports at
36
+ `ServiceWrapper.js:1495`, `:1525`, `:1530`. A declaration nothing reads tells the
37
+ reader something depends on it — `change-discipline.md` § "Removing something
38
+ removes its declaration".
39
+
40
+ - `cfg.service.port` is gone. A config that still declares one loads fine; the
41
+ field is simply not carried forward. `conn-orch-validator` is the component
42
+ that tells the author to delete it (`ORPHANED_CONFIG_FIELD`, warning).
43
+ - `service.specificationEndpoint` stays REQUIRED and is UNCHANGED. It looked like
44
+ the same class of orphan — `ServiceWrapper.js:1295` even says ADR 0005 left it
45
+ without a consumer — but it has one: `api_services_registry` reads it off the
46
+ register payload (`listeners/registry.listener.js:354`, `:474-477`, `:542`,
47
+ `:626`, `:730`, `:813`, `:1108`) and stores it in the Redis summary entry
48
+ (`models/microservice.model.js:166-177`); the heartbeat listener reads it too
49
+ (`listeners/heartbeat.listener.js:30`, `:49`). The comment at `:1295` is wrong
50
+ about that, and the requirement stands.
12
51
 
13
52
  ### Fixed — the restart cooldown is keyed on the CAUSE, not on time alone
14
53
 
@@ -83,8 +122,44 @@ failure — measured by the lead: five minutes, no output. The test now records
83
122
  the scheduling instead of arming it, and asserts the success path never reaches
84
123
  the scheduler at all. Under the mutation it fails in 2 ms.
85
124
 
125
+ ### Fixed — one owner for "who am I"
126
+
127
+ `this.config.service?.name || 'unnamed-service'` stood in **thirteen** places.
128
+ Each looked harmless; together they were a policy nobody decided — a wrapper with
129
+ no configured name kept running and told the registry, the MQ layer, the
130
+ heartbeat and every log line that it was `unnamed-service`. Not a missing name: a
131
+ WRONG one, and the same defect the heartbeat fix above removed, twelve more
132
+ times (`architecture-principles.md` §3).
133
+
134
+ `_serviceName()` and `_serviceVersion()` are now the only readers and they fail
135
+ fast with `[ServiceWrapper] Missing configuration - service.<key> is required` —
136
+ a message that is defined exactly once. Three duplicate guards that said the same
137
+ thing in their own words (`_initializeOrchestrator`, `startHealthHeartbeat`,
138
+ `_ensureValidationProof`'s "Service name is required for validation") now defer
139
+ to it.
140
+
141
+ Two readers stay direct, and each is named in a test:
142
+
143
+ - `_logPhase` — **the only path that may not throw.**
144
+ `_handleInitializationError` reports a failed phase through it, and a phase can
145
+ fail precisely because the configuration never loaded; throwing there would
146
+ replace the error being reported with a second one and the operator would never
147
+ see the first. It uses `this.config?.service?.name ?? '<config not loaded>'` —
148
+ not an invented identity but a statement ABOUT the configuration, in a message
149
+ whose subject is the configuration failing.
150
+ - `_initializeState` — `state.serviceName || service.name` is a different
151
+ contract; its error names both keys.
152
+
153
+ `service.url || ''` is unchanged: ADR 0005 says a biz service has no URL.
154
+
86
155
  ### Tests
87
156
 
157
+ `tests/unit/ServiceWrapper.serviceIdentity.test.js` — 14 cases: the getters
158
+ return and throw by value; the initialization-error path keeps BOTH facts (the
159
+ original cause and that the name was unknown); `unnamed-service` appears nowhere
160
+ in the code; exactly three direct readers, each identified; each message defined
161
+ once; and a control that the ADR 0005 url default survives.
162
+
88
163
  `tests/unit/ServiceWrapper.validationFailureFile.test.js` — 18 cases: the
89
164
  fingerprint moves for a cookbook, for `operations.json` and for an added
90
165
  cookbook and NOT for an unrelated file; the three cooldown branches; the
@@ -101,7 +176,7 @@ made-up identity, a complete identity starting it under its real name, the older
101
176
  `mqClient` guard still firing first, and the retired default absent from the
102
177
  source.
103
178
 
104
- Suite: 16 suites, 214 passed (was 189).
179
+ Suite: 17 suites, 228 passed (was 189).
105
180
 
106
181
  ## [3.4.3] — 2026-08-17
107
182
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/service-wrapper",
3
- "version": "4.1.1",
3
+ "version": "4.1.3",
4
4
  "description": "Thin orchestration layer for microservices - delegates all infrastructure concerns to specialized connectors",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -25,24 +25,25 @@
25
25
  "license": "MIT",
26
26
  "dependencies": {
27
27
  "@onlineapps/conn-base-cache": "1.0.10",
28
- "@onlineapps/conn-base-monitoring": "1.0.16",
28
+ "@onlineapps/conn-base-monitoring": "1.0.17",
29
29
  "@onlineapps/conn-base-state": "1.0.2",
30
30
  "@onlineapps/conn-infra-error-handler": "1.0.15",
31
- "@onlineapps/conn-infra-mq": "2.0.0",
31
+ "@onlineapps/conn-infra-mq": "2.0.1",
32
32
  "@onlineapps/conn-infra-secrets": "1.1.0",
33
33
  "@onlineapps/conn-orch-cookbook": "2.1.5",
34
- "@onlineapps/conn-orch-orchestrator": "2.1.8",
35
- "@onlineapps/conn-orch-registry": "2.0.0",
34
+ "@onlineapps/conn-orch-orchestrator": "2.1.9",
35
+ "@onlineapps/conn-orch-registry": "2.0.1",
36
36
  "@onlineapps/conn-orch-validator": "4.0.1",
37
- "@onlineapps/infrastructure-tools": "1.2.7",
37
+ "@onlineapps/infrastructure-tools": "1.2.9",
38
38
  "@onlineapps/runtime-config": "1.0.3",
39
- "@onlineapps/service-common": "2.0.0",
39
+ "@onlineapps/service-common": "2.0.1",
40
40
  "@onlineapps/service-validator-core": "1.0.15",
41
41
  "ajv": "8.17.1",
42
42
  "ajv-formats": "3.0.1"
43
43
  },
44
44
  "devDependencies": {
45
45
  "express": "^5.1.0",
46
+ "ioredis": "^5.3.2",
46
47
  "jest": "^29.7.0",
47
48
  "jsdoc": "^4.0.2",
48
49
  "jsdoc-to-markdown": "^8.0.0"
@@ -368,7 +368,13 @@ class ConfigLoader {
368
368
  service: {
369
369
  name: serviceConfig.service.name,
370
370
  version: serviceConfig.service.version,
371
- port: parseInt(serviceConfig.service.port, 10),
371
+ // No `port`. ADR 0005 removed the HTTP surface from biz containers, so
372
+ // nothing listens and nothing read this value back — measured
373
+ // 2026-08-30. Parsing it into the loaded config only told the reader
374
+ // that something depends on it (`change-discipline.md` § Removing
375
+ // something removes its declaration). A config that still declares one
376
+ // loads fine; `conn-orch-validator` is what tells the author to delete
377
+ // the field.
372
378
  url: serviceConfig.service.url,
373
379
  specificationEndpoint: serviceConfig.service.specificationEndpoint,
374
380
  description: serviceConfig.service.description,
@@ -343,7 +343,7 @@ class ServiceWrapper {
343
343
  * @private
344
344
  */
345
345
  _validateNamingAndWorkspaceScoped() {
346
- const serviceName = this.config?.service?.name || '<unnamed-service>';
346
+ const serviceName = this._serviceName();
347
347
  const kebabRegex = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
348
348
 
349
349
  // Rule 1: every operation key must be kebab-case
@@ -606,12 +606,55 @@ class ServiceWrapper {
606
606
  * Initialize all wrapper components
607
607
  * @async
608
608
  */
609
+ /**
610
+ * Who this wrapper is. THE reader of `config.service.name`.
611
+ *
612
+ * Thirteen places used to write `this.config.service?.name ||
613
+ * 'unnamed-service'`. Each looked harmless; together they were a policy
614
+ * nobody decided — a wrapper with no configured name kept running and told
615
+ * the registry, the MQ layer, the heartbeat and every log line that it was
616
+ * `unnamed-service`. That is not a missing name, it is a WRONG one, and it is
617
+ * the same defect dávka 62 removed from `startHealthHeartbeat`
618
+ * (architecture-principles.md §3).
619
+ *
620
+ * @private
621
+ * @returns {string}
622
+ */
623
+ _serviceName() {
624
+ const name = this.config?.service?.name;
625
+ if (!name) {
626
+ throw new Error('[ServiceWrapper] Missing configuration - service.name is required');
627
+ }
628
+ return name;
629
+ }
630
+
631
+ /**
632
+ * The version this wrapper reports. THE reader of `config.service.version`.
633
+ *
634
+ * @private
635
+ * @returns {string}
636
+ */
637
+ _serviceVersion() {
638
+ const version = this.config?.service?.version;
639
+ if (!version) {
640
+ throw new Error('[ServiceWrapper] Missing configuration - service.version is required');
641
+ }
642
+ return version;
643
+ }
644
+
609
645
  /**
610
646
  * Log phase with transparent format
611
647
  * @private
612
648
  */
613
649
  _logPhase(phase, phaseName, status, error = null, duration = null) {
614
- const serviceName = this.config.service?.name || 'unnamed-service';
650
+ // THE ONE PLACE that does not use _serviceName(), and the only one that may
651
+ // not. `_handleInitializationError` reports a failed phase through here, and
652
+ // a phase can fail precisely because the configuration never loaded — so a
653
+ // throw would replace the error being reported with a second one and the
654
+ // operator would never see the first. `<config not loaded>` is not an
655
+ // invented identity: it is a statement ABOUT the configuration, in a message
656
+ // whose subject is the configuration failing.
657
+ const serviceName = this.config?.service?.name ?? '<config not loaded>';
615
658
  const logMsg = `[FÁZE ${phase}] ${phaseName} - ${status}`;
616
659
  const logData = {
617
660
  phase,
@@ -651,7 +694,7 @@ class ServiceWrapper {
651
694
  * @private
652
695
  */
653
696
  async _cleanupBeforeRestart() {
654
- const serviceName = this.config.service?.name || 'unnamed-service';
697
+ const serviceName = this._serviceName();
655
698
  console.log(`[CLEANUP] Starting cleanup for ${serviceName} before restart...`);
656
699
 
657
700
  try {
@@ -800,7 +843,7 @@ class ServiceWrapper {
800
843
  return;
801
844
  }
802
845
 
803
- const serviceName = this.config.service?.name || 'unnamed-service';
846
+ const serviceName = this._serviceName();
804
847
  const startTime = Date.now();
805
848
 
806
849
  try {
@@ -971,7 +1014,7 @@ class ServiceWrapper {
971
1014
  * @private
972
1015
  */
973
1016
  async _initializeMonitoring() {
974
- const serviceName = this.config.service?.name || 'unnamed-service';
1017
+ const serviceName = this._serviceName();
975
1018
  const fs = require('fs');
976
1019
  const path = require('path');
977
1020
 
@@ -1128,7 +1171,7 @@ class ServiceWrapper {
1128
1171
  throw new Error('MQ URL is required when MQ is enabled. Set wrapper.mq.url in config');
1129
1172
  }
1130
1173
 
1131
- const serviceName = this.config.service?.name || 'unnamed-service';
1174
+ const serviceName = this._serviceName();
1132
1175
 
1133
1176
  // Health monitoring and channel close hooks configuration
1134
1177
  // NOTE: Health monitoring is NOT run periodically - it's triggered manually before workflow steps in debug mode
@@ -1232,10 +1275,7 @@ class ServiceWrapper {
1232
1275
  throw new Error('Registry URL is required when registry is enabled. Set wrapper.registry.url in config');
1233
1276
  }
1234
1277
 
1235
- const serviceName = this.config.service?.name;
1236
- if (!serviceName) {
1237
- throw new Error('[ServiceWrapper] Missing configuration - service.name is required');
1238
- }
1278
+ const serviceName = this._serviceName();
1239
1279
 
1240
1280
  // ADR 0005: biz has no HTTP — no port, no URL. Kept in serviceInfo
1241
1281
  // payload as empty string for Registry-side backward compat with
@@ -1252,17 +1292,26 @@ class ServiceWrapper {
1252
1292
  // FÁZE 0.5 a 0.6: Vytvoření front a spuštění konzumerů se provádí v registryClient.init()
1253
1293
  // FÁZE 0.7: Registrace u Registry
1254
1294
 
1255
- // ADR 0005: no HTTP surface specificationEndpoint has no consumer.
1256
- // Registry receives full operations spec in the register payload
1257
- // itself. Field kept optional for pre-3.4.1 config files that may
1258
- // still declare it; ignored if absent.
1259
- const specificationEndpoint = this.config.service?.specificationEndpoint || '';
1295
+ // specificationEndpoint HAS a consumer, despite ADR 0005 leaving no HTTP
1296
+ // surface behind it. The registry destructures it off this register payload
1297
+ // (api/infra/api_services_registry/src/listeners/registry.listener.js:354),
1298
+ // warns when it is empty (:474-477), passes it into every registerFull call
1299
+ // (:542, :626, :730, :813, :1108), and the model writes it into the Redis
1300
+ // summary entry (src/models/microservice.model.js:166-177). The heartbeat
1301
+ // listener reads it too (src/listeners/heartbeat.listener.js:30, :49).
1302
+ // This comment claimed the opposite until 2026-08-30; measured, it was wrong.
1303
+ // ConfigLoader.js:360-365 therefore keeps the field REQUIRED, and every biz
1304
+ // service builds its config through it (measured 2026-08-30: all eight call
1305
+ // ConfigLoader.loadAll from src/config/index.js). A `|| ''` used to sit here;
1306
+ // it could never fire, and a fallback that cannot fire still tells the
1307
+ // reader an empty endpoint is an expected state (architecture-principles §3).
1308
+ const specificationEndpoint = this.config.service.specificationEndpoint;
1260
1309
 
1261
1310
  // Logger may not be initialized yet at this point - RegistryClient handles null gracefully
1262
1311
  this.registryClient = new RegistryConnector.ServiceRegistryClient({
1263
1312
  amqpUrl: mqUrl,
1264
1313
  serviceName: serviceName,
1265
- version: this.config.service?.version,
1314
+ version: this._serviceVersion(),
1266
1315
  specificationEndpoint: specificationEndpoint,
1267
1316
  registryQueue: 'registry.register', // Use correct queue name
1268
1317
  registryUrl: registryUrl,
@@ -1285,7 +1334,7 @@ class ServiceWrapper {
1285
1334
  operations: this.operations?.operations || {},
1286
1335
  workspaceScoped: this.config.service?.workspaceScoped,
1287
1336
  metadata: {
1288
- version: this.config.service?.version,
1337
+ version: this._serviceVersion(),
1289
1338
  description: this.config.service?.description || ''
1290
1339
  }
1291
1340
  };
@@ -1445,7 +1494,7 @@ class ServiceWrapper {
1445
1494
  return;
1446
1495
  }
1447
1496
 
1448
- const serviceName = this.config.service?.name || 'unnamed-service';
1497
+ const serviceName = this._serviceName();
1449
1498
 
1450
1499
  let host = cacheUrl;
1451
1500
  let port = 6379;
@@ -1599,14 +1648,8 @@ class ServiceWrapper {
1599
1648
  // (below), but it runs only when serviceRoot is set and
1600
1649
  // wrapper.validation.enabled !== false — a service with validation off used
1601
1650
  // to arrive here with neither key ever checked.
1602
- const serviceName = this.config.service?.name;
1603
- if (!serviceName) {
1604
- throw new Error('[ServiceWrapper] Missing configuration - service.name is required');
1605
- }
1606
- const serviceVersion = this.config.service?.version;
1607
- if (!serviceVersion) {
1608
- throw new Error('[ServiceWrapper] Missing configuration - service.version is required');
1609
- }
1651
+ const serviceName = this._serviceName();
1652
+ const serviceVersion = this._serviceVersion();
1610
1653
 
1611
1654
  const { createBaseClientAdapter } = require('@onlineapps/infrastructure-tools');
1612
1655
 
@@ -1724,17 +1767,13 @@ class ServiceWrapper {
1724
1767
  async _initializeOrchestrator() {
1725
1768
  assertConnOrchOrchestratorV2Contract();
1726
1769
 
1727
- const serviceName = this.config.service?.name;
1728
- const serviceVersion = this.config.service?.version;
1770
+ // Both guards moved into the getters — one owner for the contract and for
1771
+ // its wording (change-discipline.md § one fact, one owner).
1772
+ const serviceName = this._serviceName();
1773
+ const serviceVersion = this._serviceVersion();
1729
1774
  const environment = this.config.service?.env;
1730
1775
  const serviceUrl = this.config.service?.url;
1731
1776
 
1732
- if (!serviceName) {
1733
- throw new Error('[ServiceWrapper] Missing configuration - service.name is required');
1734
- }
1735
- if (!serviceVersion) {
1736
- throw new Error('[ServiceWrapper] Missing configuration - service.version is required');
1737
- }
1738
1777
  if (!environment) {
1739
1778
  throw new Error('[ServiceWrapper] Missing configuration - service.env (NODE_ENV) is required');
1740
1779
  }
@@ -1815,7 +1854,7 @@ class ServiceWrapper {
1815
1854
 
1816
1855
  const queueName = 'workflow.init';
1817
1856
  this.logger?.info(`[ServiceWrapper] [CONSUMER] Starting workflow.init listener for queue: ${queueName}`);
1818
- this.logger?.info(`[ServiceWrapper] [CONSUMER] Service: ${this.config.service?.name || 'unnamed-service'}`);
1857
+ this.logger?.info(`[ServiceWrapper] [CONSUMER] Service: ${this._serviceName()}`);
1819
1858
  this.logger?.info(`[ServiceWrapper] [CONSUMER] Timestamp: ${new Date().toISOString()}`);
1820
1859
 
1821
1860
  try {
@@ -1847,7 +1886,7 @@ class ServiceWrapper {
1847
1886
 
1848
1887
  const queueName = 'workflow.control';
1849
1888
  this.logger?.info(`[ServiceWrapper] [CONSUMER] Starting workflow.control listener for queue: ${queueName}`);
1850
- this.logger?.info(`[ServiceWrapper] [CONSUMER] Service: ${this.config.service?.name || 'unnamed-service'}`);
1889
+ this.logger?.info(`[ServiceWrapper] [CONSUMER] Service: ${this._serviceName()}`);
1851
1890
  this.logger?.info(`[ServiceWrapper] [CONSUMER] Timestamp: ${new Date().toISOString()}`);
1852
1891
 
1853
1892
  try {
@@ -1876,7 +1915,7 @@ class ServiceWrapper {
1876
1915
  throw new Error('[ServiceWrapper] Cannot start service workflow listener — MQ client not initialized. Queue infrastructure is required.');
1877
1916
  }
1878
1917
 
1879
- const serviceName = this.config.service?.name || 'unnamed-service';
1918
+ const serviceName = this._serviceName();
1880
1919
  const serviceQueue = `${serviceName}.workflow`;
1881
1920
 
1882
1921
  this.logger?.info(`[ServiceWrapper] [CONSUMER] Starting service-specific workflow listener for queue: ${serviceQueue}`);
@@ -1984,7 +2023,7 @@ class ServiceWrapper {
1984
2023
  });
1985
2024
 
1986
2025
  // Check if this message is for our service
1987
- const serviceName = this.config.service?.name || 'unnamed-service';
2026
+ const serviceName = this._serviceName();
1988
2027
  if (message.step?.service && message.step.service !== serviceName) {
1989
2028
  this.logger?.debug(`Message not for this service (target: ${message.step.service})`);
1990
2029
  return;
@@ -2406,8 +2445,8 @@ class ServiceWrapper {
2406
2445
  });
2407
2446
 
2408
2447
  try {
2409
- const serviceName = this.config.service?.name;
2410
- const serviceVersion = this.config.service?.version;
2448
+ const serviceName = this._serviceName();
2449
+ const serviceVersion = this._serviceVersion();
2411
2450
  const serviceUrl = this.config.service?.url;
2412
2451
 
2413
2452
  const orchestrator = this._createValidationOrchestrator();
@@ -2599,8 +2638,8 @@ class ServiceWrapper {
2599
2638
  // INTENTIONAL FALLBACK: bootstrap logger — see docs/standards/FALLBACKS_INVENTORY.md §5.1
2600
2639
  return new ValidationOrchestrator({
2601
2640
  serviceRoot: this.serviceRoot,
2602
- serviceName: this.config.service?.name,
2603
- serviceVersion: this.config.service?.version,
2641
+ serviceName: this._serviceName(),
2642
+ serviceVersion: this._serviceVersion(),
2604
2643
  serviceUrl: this.config.service?.url || '',
2605
2644
  logger: this.logger || console
2606
2645
  });
@@ -2684,7 +2723,7 @@ class ServiceWrapper {
2684
2723
  * @async
2685
2724
  */
2686
2725
  async shutdown() {
2687
- const serviceName = this.config.service?.name || 'unnamed-service';
2726
+ const serviceName = this._serviceName();
2688
2727
  this.logger?.info(`Shutting down ServiceWrapper for ${serviceName}`);
2689
2728
 
2690
2729
  try {
@@ -2774,12 +2813,11 @@ class ServiceWrapper {
2774
2813
  * @private
2775
2814
  */
2776
2815
  async _ensureValidationProof() {
2777
- if (!this.config.service?.name) {
2778
- throw new Error('Service name is required for validation');
2779
- }
2780
- if (!this.config.service?.version) {
2781
- throw new Error('Service version is required for validation');
2782
- }
2816
+ // The two guards this method used to carry said the same thing in different
2817
+ // words ("Service name is required for validation"). One contract, one
2818
+ // wording — the getters own both.
2819
+ this._serviceName();
2820
+ this._serviceVersion();
2783
2821
 
2784
2822
  // ADR 0005: biz services have no HTTP surface — no port to bind, no URL to
2785
2823
  // advertise. The two guards above are the contract; the values themselves
@@ -2848,7 +2886,7 @@ class ServiceWrapper {
2848
2886
  getStatus() {
2849
2887
  return {
2850
2888
  initialized: this.isInitialized,
2851
- service: this.config.service?.name || 'unnamed-service',
2889
+ service: this._serviceName(),
2852
2890
  components: {
2853
2891
  mq: this.mqClient?.isConnected() || false,
2854
2892
  registry: !!this.registryClient,