@onlineapps/conn-orch-registry 6.0.2 → 7.0.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/API.md +831 -0
- package/CHANGELOG.md +538 -0
- package/README.md +35 -7
- package/docs/REGISTRY_CLIENT_GUIDE.md +2 -2
- package/package.json +5 -3
- package/src/registryClient.js +183 -27
- package/src/registryEventConsumer.js +14 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
# Changelog — @onlineapps/conn-orch-registry
|
|
2
|
+
|
|
3
|
+
All notable changes to this package. Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [7.0.0] — 2026-09-27
|
|
8
|
+
|
|
9
|
+
pin: `@onlineapps/mq-client-core` 4.2.0 → 5.0.0
|
|
10
|
+
|
|
11
|
+
### Documentation — `API.md` je součástí tarballu (d.1039)
|
|
12
|
+
|
|
13
|
+
`files` teď jmenuje `API.md`; dokument ležel v balíčku, ale do vydaného artefaktu se nedostal.
|
|
14
|
+
|
|
15
|
+
### Changed — BREAKING — odpověď na registraci chodí na frontu instance `<service>.registry.<instance_id>` (d.817c)
|
|
16
|
+
|
|
17
|
+
Konfirmace `registry-instance-identity` 001 (c): odpověď na registraci jde na frontu TÉ kopie, která
|
|
18
|
+
se registrovala, přes existující `responseQueue`. Sdílenou `<service>.registry` dostávají od registru
|
|
19
|
+
jen podněty. Klient teď:
|
|
20
|
+
- ve zprávě `register` posílá `responseQueue: "<service>.registry.<instance_id>"` (dřív
|
|
21
|
+
`"<service>.registry"`); nová vlastnost `instanceResponseQueue`;
|
|
22
|
+
- v `init()` konzumuje DVĚ fronty, obě s `requeueOnError: false`: sdílenou `<service>.registry`
|
|
23
|
+
(podněty registru, např. `revalidate`, které `api_services_registry` posílá na
|
|
24
|
+
`<serviceName>.registry`) a frontu instance (odpovědi);
|
|
25
|
+
- hlášku o vypršení registrace jmenuje frontou instance a log `Initialized` nese `promptQueue` a
|
|
26
|
+
`responseQueue`.
|
|
27
|
+
|
|
28
|
+
Heartbeat a `deregister` se nemění. Pořadí z konfirmace 002 platí: šablona `registry.{instance_id}`
|
|
29
|
+
s dead-letter cestou musí být v pinovaném `@onlineapps/mq-client-core` (d.817b), jinak `consume()`
|
|
30
|
+
frontu instance odmítne. Tato změna proto vyžaduje mq-client-core s d.817b. Server registru
|
|
31
|
+
(`registry.listener.js`, `validationResponse.listener.js`) odpovídá na `msg.responseQueue` ze
|
|
32
|
+
zprávy a změnu nepotřebuje.
|
|
33
|
+
|
|
34
|
+
Proč BREAKING: mění se veřejný tvar zprávy `register` (`responseQueue`), topologie, kterou klient
|
|
35
|
+
deklaruje (nová trvalá fronta na instanci), a minimální `@onlineapps/mq-client-core` (musí nést d.817b).
|
|
36
|
+
|
|
37
|
+
Životnost fronty instance (`auto-delete`, `x-expires`, trvalá) rozhodnutá není (konfirmace 002,
|
|
38
|
+
Conditions; šablona d.817b ji záměrně nenese). Fronta je trvalá a po skončení kopie zůstává.
|
|
39
|
+
|
|
40
|
+
### Documentation — README, průvodce a komentáře testů ukazují na vlastníka drátu jeho jménem (d.999b, d.999d)
|
|
41
|
+
|
|
42
|
+
Balíček citoval kontrakt registrace starým jménem dokumentu, který v `docs/biz/30-operations/` není.
|
|
43
|
+
Odkazy přitom vedly na `registration-wire.md`. Všechna místa teď jmenují existující uzel a jeho
|
|
44
|
+
sekci:
|
|
45
|
+
- úvod README: `registration-wire.md §2` (MQ wire format) (d.999b);
|
|
46
|
+
- komentář v příkladu `register()` v README a parametr `serviceInfo.operations` v
|
|
47
|
+
`docs/REGISTRY_CLIENT_GUIDE.md`: `§3` (Operation schema);
|
|
48
|
+
- úvod průvodce a komentáře ve třech unit testech: `§2.1` (`register`) (d.999d).
|
|
49
|
+
|
|
50
|
+
### Changed — BREAKING — konstruktor `ServiceRegistryClient` vyžaduje `instanceId`; `instance_id` jde v `register`, `heartbeat` i `deregister` (d.817)
|
|
51
|
+
|
|
52
|
+
Každá zpráva na drát teď říká nejen, KTERÁ služba, ale i KTERÁ běžící kopie (konfirmace
|
|
53
|
+
`registry-instance-identity` 001): `register`, `heartbeat` a `deregister` nesou `instance_id`.
|
|
54
|
+
Registr podle něj maže jen při shodě, takže krátce žijící cizí kopie už neodhlásí tu živou.
|
|
55
|
+
Konstruktor bez `instanceId` (nebo s prázdným řetězcem) skončí chybou:
|
|
56
|
+
`[RegistryClient] <service>: Missing instanceId - got undefined. Expected: a non-empty string naming
|
|
57
|
+
THIS running copy of the service, the same value for the whole process; … Fix: pass the instance id
|
|
58
|
+
the wrapper made once at boot (SERVICE_INSTANCE_ID when the deployment gives one, otherwise a UUID v4)
|
|
59
|
+
- this client never makes one, because the same value also reaches monitoring-core as serviceInstanceId.`
|
|
60
|
+
|
|
61
|
+
Migrace volajícího: předat `instanceId`, jednu hodnotu na proces. `@onlineapps/service-wrapper` ji
|
|
62
|
+
předává (`this.instanceId`, stejná hodnota jde do monitoringu jako `serviceInstanceId`). Kdo staví
|
|
63
|
+
klienta sám, musí ji doplnit. Registrační odpověď dál chodí do sdílené fronty `<service>.registry`,
|
|
64
|
+
viz komentář u `serviceRegistryQueue` v `src/registryClient.js`.
|
|
65
|
+
|
|
66
|
+
### Changed — BREAKING — `register()` přijímá jen klíče registračního kontraktu (d.702)
|
|
67
|
+
|
|
68
|
+
`register(serviceInfo)` odmítne klíč, který kontrakt nezná (`registration-wire.md` §2.1). Dřív ho
|
|
69
|
+
tiše zahodil. Povolené klíče: `endpoints`, `operations`, `metadata`, `health`, `workspaceScoped`,
|
|
70
|
+
`timeout`. Hláška:
|
|
71
|
+
`[RegistryClient] <service>: serviceInfo key "foo" is not part of the registration contract - Expected
|
|
72
|
+
one of: endpoints, operations, metadata, health, workspaceScoped, timeout (api/docs/biz/30-operations/
|
|
73
|
+
registration-wire.md §2.1). Fix: delete "foo" from the object passed to register(), or send the value
|
|
74
|
+
under the key the contract names for it.`
|
|
75
|
+
Zaniklé klíče si dál nesou vlastní hlášku (např. `token`: `serviceInfo carries the retired key "token" …`).
|
|
76
|
+
Migrace: z objektu pro `register()` odstranit vše mimo šest klíčů výše.
|
|
77
|
+
|
|
78
|
+
### Changed — druhý souběžný `register()` pod týmž request id a druhé `startHeartbeat()` se odmítnou, místo tichého přepisu (d.760b, d.760c)
|
|
79
|
+
|
|
80
|
+
- `register()`, dokud čeká registrace se stejným `id`, skončí chybou `[RegistryClient] <service>: Duplicate
|
|
81
|
+
request id "<id>" - a registration with this id is still pending. … Fix: await the register() already in
|
|
82
|
+
flight before starting another one …`. Dřív druhé volání přepsalo záznamy prvního a handle jeho timeru
|
|
83
|
+
zmizel, takže starší timer doběhl celý timeout bez možnosti ho zrušit. S `uuid.v4` se id neopakují,
|
|
84
|
+
takže běžný volající to nepozná.
|
|
85
|
+
- `startHeartbeat()` nad klientem, jehož smyčka běží, skončí chybou `[RegistryClient] <service>: A heartbeat
|
|
86
|
+
loop is already running - … Fix: call client.stopHeartbeat() before starting a new loop, or keep the loop
|
|
87
|
+
that is already running.`. Dřív přepsal handle a první smyčka tepala dál bez možnosti ji zastavit.
|
|
88
|
+
|
|
89
|
+
### Fixed — `RegistryEventConsumer` bere jméno výměny `registry.changes` z `queueConfig` (d.809); `stopHeartbeat()` ruší jen `setTimeout` (d.752c)
|
|
90
|
+
|
|
91
|
+
- Jméno výměny, na kterou se odběratel změn váže, vlastní `@onlineapps/mq-client-core`
|
|
92
|
+
(`queueConfig.queueName('registry', 'changes')`), ne literál v balíčku. Hodnota se nemění:
|
|
93
|
+
`registry.changes`.
|
|
94
|
+
- `stopHeartbeat()` volal nad týmž handlem `clearTimeout` i `clearInterval`, přestože smyčka je
|
|
95
|
+
rekurzivní `setTimeout`. Druhé volání je pryč, chování se nemění.
|
|
96
|
+
|
|
97
|
+
### Changed — `CHANGELOG.md` je součástí tarballu (d.997)
|
|
98
|
+
|
|
99
|
+
`package.json` `files` nese `CHANGELOG.md`. Vydaná 6.0.3 ho nenesla.
|
|
100
|
+
|
|
101
|
+
## [6.0.3] — 2026-09-25
|
|
102
|
+
|
|
103
|
+
pin: `@onlineapps/mq-client-core` 4.1.0 → 4.2.0
|
|
104
|
+
|
|
105
|
+
## [6.0.2] — 2026-09-24
|
|
106
|
+
|
|
107
|
+
pin: `@onlineapps/mq-client-core` 4.0.1 → 4.1.0
|
|
108
|
+
|
|
109
|
+
## [6.0.1] — 2026-09-17
|
|
110
|
+
|
|
111
|
+
pin: `@onlineapps/mq-client-core` 4.0.0 → 4.0.1
|
|
112
|
+
|
|
113
|
+
## [6.0.0] — 2026-09-17
|
|
114
|
+
|
|
115
|
+
### Changed (testy) — odmítnutá deklarace se tvrdí hodnotou značky (d.579b)
|
|
116
|
+
|
|
117
|
+
`tests/integration/ensureQueuesRefusal.integration.test.js` čekal `cause.code === 406` z doby pinu jádra 3.2.0; pinované
|
|
118
|
+
`@onlineapps/mq-client-core` 4.x značí odmítnutou deklaraci u zdroje (d.582), takže případ teď tvrdí `code: MQ_CONNECTION_FATAL`,
|
|
119
|
+
`reason: broker-refused` a 406 brokeru čte o patro níž (`cause.cause.code`). Sada byla od re-pinu červená (W592 měření).
|
|
120
|
+
|
|
121
|
+
### Removed — BREAKING: `deployable` odchází z registrační zprávy (d.592)
|
|
122
|
+
|
|
123
|
+
Verdikt shody s uniformou jezdil na registraci od d.221 a **nikdo ho nečetl**:
|
|
124
|
+
`processRegistration` (`api/infra/api_services_registry/src/listeners/registry.listener.js`)
|
|
125
|
+
zprávu destrukturuje bez něj a jiný čtenář na platformě není (`grep -rn deployable`
|
|
126
|
+
nad `api/infra/**` a `fe_adminui/src` vrací prázdno). Slib konfirmace
|
|
127
|
+
`biz-service-manifest` 001 §4 — „registr ho promítne do
|
|
128
|
+
`infrastructure:health:<service>` vedle `status`" — zůstal bez implementace.
|
|
129
|
+
|
|
130
|
+
Čtyři otázky `change-discipline.md` § Removing something removes its declaration:
|
|
131
|
+
|
|
132
|
+
- **Vznik.** d.221 klíč zavedla, aby nasaditelnost dojela z běhu služby k registru.
|
|
133
|
+
- **Role v koncepci.** 001 §4: dvě závažnosti, dva důsledky — `boot` shodí start,
|
|
134
|
+
`deploy` nechá službu běžet a označí ji jako nenasaditelnou.
|
|
135
|
+
- **Proč nikdo nečte.** Projekce nikdy nevznikla, a mezitím se ukázalo, že
|
|
136
|
+
verdikt z běžícího kontejneru nevypovídá o repozitáři: produkční obraz není git
|
|
137
|
+
checkout a nenese compose ani README, takže krok 7 nad ním hlásí 12 nálezů
|
|
138
|
+
nepřítomnosti — na drát by u každé zdravé služby jel `false`.
|
|
139
|
+
- **Náhrada je koncepčnější.** Nasaditelnost dokládá z každého commitu CI job
|
|
140
|
+
`validate-uniform` nad checkoutem (konfirmace `biz-service-manifest` 010), tedy
|
|
141
|
+
jediné místo, které vidí, co dostane klon. Rozhodnutí vlastníka 2026-09-17,
|
|
142
|
+
konfirmace `biz-service-manifest` 011.
|
|
143
|
+
|
|
144
|
+
S klíčem odešel i jeho `@param` v JSDoc a blok `if ('deployable' in serviceInfo)`;
|
|
145
|
+
`API.md` je zregenerovaná ze zdrojů (`npm run docs`).
|
|
146
|
+
|
|
147
|
+
### Changed — BREAKING: `register()` odmítá zaniklý klíč jménem (d.592)
|
|
148
|
+
|
|
149
|
+
`deployable` je šestý řádek tabulky `RETIRED_SERVICE_INFO_KEYS` (vedle `spec`,
|
|
150
|
+
`token`, `validationToken`, `secret`, `tokenSecret` z d.562). Volání, které ho
|
|
151
|
+
ještě pošle, **skončí výjimkou před publikací**, ne tiše bez účinku:
|
|
152
|
+
|
|
153
|
+
[RegistryClient] biz-invoicing: serviceInfo carries the retired key
|
|
154
|
+
"deployable" - the manifest-conformance verdict of a boot-time validation run
|
|
155
|
+
(owner decision 2026-09-17, confirmation
|
|
156
|
+
`api/docs/governance/confirmations/biz-service-manifest.md` 011). … Fix:
|
|
157
|
+
delete "deployable" from the object passed to register().
|
|
158
|
+
|
|
159
|
+
Odmítnutí se ptá na **přítomnost klíče, ne na jeho hodnotu** — `false` i `null`
|
|
160
|
+
byly stavy, které wrapper posílal nejčastěji, a kontrola podle pravdivosti by je
|
|
161
|
+
propustila.
|
|
162
|
+
|
|
163
|
+
Dopad: `ServiceWrapper._buildRegistrationPayload()` klíč dnes posílá, takže
|
|
164
|
+
wrapperová půlka změny (d.591) musí přistát ve stejné vlně; bez ní boot skončí
|
|
165
|
+
touto výjimkou. Pro ostatní přímé volající `register()` platí `Fix:` z hlášky.
|
|
166
|
+
|
|
167
|
+
Sady: unit 19 suites / 205 testů (o jednu sadu méně — `registryClient.deployable`
|
|
168
|
+
i `deployable-on-the-wire` odešly s klíčem, který tvrdily); integrace proti
|
|
169
|
+
živému brokeru včetně případu `deployable: false` odmítnutého dřív, než něco
|
|
170
|
+
odjede.
|
|
171
|
+
|
|
172
|
+
## [5.0.0] — 2026-09-16
|
|
173
|
+
|
|
174
|
+
### Fixed — klasifikace chyby přežije i rewrap ve FÁZI 0.8 (d.579)
|
|
175
|
+
|
|
176
|
+
`ensureQueues()` byla **třetí** cesta, která obalovala pád do
|
|
177
|
+
`new Error(msg, { cause })` — a ta konstrukce nenese žádnou vlastní vlastnost,
|
|
178
|
+
takže `code` i `reason`, které na chybu napsal
|
|
179
|
+
`@onlineapps/mq-client-core`, tady umíraly. d.532 opravila dvě cesty FÁZE 0.7
|
|
180
|
+
(`QueueManager.init()`, `RegistryClient.init()`); deklarace business front ve
|
|
181
|
+
FÁZI 0.8 zůstala. `@onlineapps/service-wrapper` rozhoduje podle `error.code`,
|
|
182
|
+
takže odmítnutí z této cesty dostal jako neklasifikované a ukončil ho jako
|
|
183
|
+
přechodné.
|
|
184
|
+
|
|
185
|
+
Teď se klasifikace přenáší týmž vzorem jako v `init()`: `refusal.code =
|
|
186
|
+
assertErr.code`, `refusal.reason = assertErr.reason`, původní chyba zůstává
|
|
187
|
+
v `cause` a věta, kterou tato vrstva přidává (která fronta, co to spraví), je
|
|
188
|
+
beze změny.
|
|
189
|
+
|
|
190
|
+
**Nic se tu neklasifikuje**: co zdroj nechal neoznačené, zůstává neoznačené.
|
|
191
|
+
Tenhle balíček nečte AMQP reply kódy ani nematchuje text hlášky — otázku „odpověděl
|
|
192
|
+
broker a odmítl?" vlastní `@onlineapps/mq-client-core` (`REFUSAL_REPLY_CODES`,
|
|
193
|
+
`_isBrokerRefusal()`) a druhá kopie toho seznamu by se od něj mohla rozejít.
|
|
194
|
+
|
|
195
|
+
Změřeno a nezamlčeno: proti pinu `@onlineapps/mq-client-core` 3.2.0 předává
|
|
196
|
+
`assertQueue()` syrovou chybu z amqplib, takže dnes touto cestou cestuje **číslo
|
|
197
|
+
406**, ne značka `code: 'MQ_CONNECTION_FATAL'` / `reason: 'broker-refused'`,
|
|
198
|
+
podle které wrapper končí boot natrvalo. d.577 dala značku rail `consume()`
|
|
199
|
+
v jádře; deklarační rail ji zatím nemá. Tento přenos je tedy nutný, ne
|
|
200
|
+
dostačující — a integrační test to říká nahlas místo toho, aby tvrdil značku,
|
|
201
|
+
kterou žádný zdroj nepíše.
|
|
202
|
+
|
|
203
|
+
### Documentation — komentář v `registryClient.js` ukazoval na zrušený dokument (d.574)
|
|
204
|
+
|
|
205
|
+
Komentář nad propagací `workspaceScoped` posílal čtenáře na
|
|
206
|
+
`biz-service-onboarding.md` §4 — dokument, který 2026-08-16 přešel na
|
|
207
|
+
`api/docs/biz/60-templates/onboarding-checklist.md` (čísla sekcí se přenesla,
|
|
208
|
+
`api/docs/biz/RETIRED-VOCABULARY.md` §4). Nová podoba je `@see` s živou cestou.
|
|
209
|
+
Ostatních 16 citací `api/docs/…` v tomto souboru bylo změřeno (`ls`) a všechny
|
|
210
|
+
cíle existují. Chování ani API se nemění.
|
|
211
|
+
|
|
212
|
+
### Removed — BREAKING: `spec`, `validationToken` a `tokenSecret` odcházejí z registrační zprávy (d.562)
|
|
213
|
+
|
|
214
|
+
Tři klíče jezdily na každé registraci a nikdo je nečetl. Změřeno 2026-09-16 nad
|
|
215
|
+
`api/infra/api_services_registry/src/**`: `processRegistration` destrukturuje
|
|
216
|
+
`id, serviceName, version, endpoints, operations, metadata, health,
|
|
217
|
+
specificationEndpoint, responseQueue, timestamp, validationProof,
|
|
218
|
+
validationData, workspaceScoped` — a `grep -rn 'validationToken|tokenSecret'`
|
|
219
|
+
nad tím stromem nevrací nic. Kontrakt to už popisoval slovy „emitted, unread"
|
|
220
|
+
(`api/docs/biz/30-operations/registration-wire.md` §2.1).
|
|
221
|
+
|
|
222
|
+
Čtyři otázky `change-discipline.md` § Removing something removes its declaration:
|
|
223
|
+
|
|
224
|
+
- `spec` nesl OpenAPI dokument z doby, kdy registr dereferencoval HTTP cestu
|
|
225
|
+
`/specification`. ADR 0005 vzalo biz kontejnerům HTTP; co registr dnes
|
|
226
|
+
**validuje**, je `operations` podle schema-v3 (§3 téhož kontraktu). Schéma,
|
|
227
|
+
které protistrana kontroluje klíč po klíči, je koncepčnější než dokument,
|
|
228
|
+
který se jen uložil.
|
|
229
|
+
- `validationToken` a `tokenSecret` patřily k JWT token API, které odplulo
|
|
230
|
+
z `@onlineapps/service-validator-core` v d.344; poslední čtenář (HTTP
|
|
231
|
+
`POST /validate`) byl smazán 2026-08-22 a validator si zrušení zapsal do
|
|
232
|
+
vlastní konfigurace (`api/infra/api_services_validator/src/config/config.js`
|
|
233
|
+
§ security). Běh dnes dokládá `validationProof` / `validationData` a podepsaný
|
|
234
|
+
certifikát — důkaz, který si příjemce ověří, místo sdíleného tajemství, které
|
|
235
|
+
tahle zpráva vozila po brokeru v čistém tvaru.
|
|
236
|
+
|
|
237
|
+
S klíči padly i dvě jejich záložky: `spec: serviceInfo.spec || null`
|
|
238
|
+
(výchozí hodnota pod `architecture-principles.md` §3) a dva pravopisy jednoho
|
|
239
|
+
pole — `serviceInfo.token || serviceInfo.validationToken`, `serviceInfo.secret
|
|
240
|
+
|| serviceInfo.tokenSecret`.
|
|
241
|
+
|
|
242
|
+
### Changed — BREAKING: `register()` odmítá zaniklý klíč jménem (d.562)
|
|
243
|
+
|
|
244
|
+
Volání, které některý z pěti zaniklých klíčů (`spec`, `token`,
|
|
245
|
+
`validationToken`, `secret`, `tokenSecret`) ještě posílá, **skončí výjimkou
|
|
246
|
+
ještě před publikací** — nikoli tiše bez účinku, protože tiše zahozený klíč
|
|
247
|
+
nechává volajícího věřit, že něco odjelo:
|
|
248
|
+
|
|
249
|
+
[RegistryClient] biz-invoicing: serviceInfo carries the retired key
|
|
250
|
+
"tokenSecret" - the shared secret of that same token API, whose last reader
|
|
251
|
+
(HTTP POST /validate) was deleted 2026-08-22; nothing verifies a token with
|
|
252
|
+
it, and a broker message is no place for a secret. Fix: delete "tokenSecret"
|
|
253
|
+
from the object passed to register().
|
|
254
|
+
|
|
255
|
+
Hláška **nikdy neopakuje předanou hodnotu** — dva z těch klíčů nesly tajemství.
|
|
256
|
+
|
|
257
|
+
`ServiceWrapper._buildRegistrationPayload()` žádný z nich neodesílá, takže pro
|
|
258
|
+
běžící biz služby se nemění nic; dopad je na přímého volajícího `register()`.
|
|
259
|
+
|
|
260
|
+
Sady: unit 20 suites / 199 testů; integrace 6 suites / 38 testů proti živému
|
|
261
|
+
brokeru, včetně nové `registrationPayloadKeys.integration.test.js`, která čte
|
|
262
|
+
zprávu zpět z fronty — `undefined` klíč zmizí až v serializaci, takže to, co
|
|
263
|
+
brokerem opravdu jede, neukáže žádný unit tier.
|
|
264
|
+
|
|
265
|
+
### Fixed — značka odmítnutí brokerem přežije přebalení chyby (d.532)
|
|
266
|
+
|
|
267
|
+
Obě selhávající cesty tohohle balíčku chybu z jádra **přebalují**, aby dodaly
|
|
268
|
+
fakt, který jádru chybí: který broker (`QueueManager.init()`) a která fronta
|
|
269
|
+
které služby (`RegistryClient.init()`). Dělaly to holým `new Error(msg, { cause })`,
|
|
270
|
+
které nenese žádnou vlastní vlastnost — takže `code: 'MQ_CONNECTION_FATAL'`
|
|
271
|
+
a `reason: 'broker-refused'`, které na chybu napsal její vlastník
|
|
272
|
+
(`@onlineapps/mq-client-core`, `REFUSAL_REPLY_CODES` + `_isBrokerRefusal()`),
|
|
273
|
+
tady umřely.
|
|
274
|
+
|
|
275
|
+
Volající, který podle nich jedná, je `@onlineapps/service-wrapper`: FÁZE 0.3
|
|
276
|
+
ukončí boot natrvalo právě podle `error.code`, a od d.532 to dělá i FÁZE 0.7.
|
|
277
|
+
Bez značky viděl neklasifikované selhání a odmítnutí ohlásil jako přechodné —
|
|
278
|
+
změřeno na biz-meta (dev, wrapper 8.1.1): FÁZE 0.7 FAILED pro 406 na
|
|
279
|
+
`biz-meta.registry`, hlášeno jako „a restart against a recovered dependency can
|
|
280
|
+
succeed", a služba pak v crash loopu.
|
|
281
|
+
|
|
282
|
+
`code` i `reason` se teď přenášejí **bezpodmínečně** — táž kolej, jakou drží
|
|
283
|
+
`@onlineapps/conn-infra-mq` v `ConnectorMQClient` (d.498b). Nic se tu
|
|
284
|
+
neklasifikuje: co zdroj neoznačil, zůstává neoznačené, takže se do balíčku
|
|
285
|
+
nedostane druhá kopie seznamu AMQP reply kódů ani čtení textu hlášky.
|
|
286
|
+
|
|
287
|
+
Sonda `tests/unit/error-message-contract.test.js` navíc umí tvar
|
|
288
|
+
`const <jméno> = new …Error(…); … throw <jméno>;` — chyba, která nese vlastní
|
|
289
|
+
vlastnosti, se jinak postavit nedá. Bez toho by obě hlášky tiše vypadly z jejího
|
|
290
|
+
dosahu (změřeno: kontrolní počet spadl 28 → 26), což je přesně ta falešná záruka,
|
|
291
|
+
kvůli které kontrolní případ existuje (`automation-gates.md` §5).
|
|
292
|
+
|
|
293
|
+
### Breaking — `startHeartbeat({ maxFailures, onFatal })`: smyčka heartbeatu má mez selhání (d.496)
|
|
294
|
+
|
|
295
|
+
Do téhle dávky `startHeartbeat()` selhání pouze logovala a plánovala další beat
|
|
296
|
+
donekonečna (`src/registryClient.js`, větev `heartbeatLoop`/`catch`). Služba, jejíž
|
|
297
|
+
beaty registr nedostával, tedy běžela dál a nikdo v tomhle balíčku neuměl říct, že
|
|
298
|
+
je „živá" jen naoko. Mez měl jen `@onlineapps/service-wrapper`, ve **vlastní druhé
|
|
299
|
+
smyčce** kolem `sendHeartbeat()` — dvě smyčky nad jednou kadencí, což
|
|
300
|
+
`.claude/rules/change-discipline.md` § One rail per concern označuje za vadu
|
|
301
|
+
a co si CHANGELOG wrapperu sám poznamenal do „Co tahle dávka NEudělala"
|
|
302
|
+
(*„Ta metoda selhání jen loguje — nemá počítadlo ani mez"*).
|
|
303
|
+
|
|
304
|
+
`startHeartbeat()` nově **vyžaduje objekt se dvěma volbami** a ani jednu si
|
|
305
|
+
nedosazuje (`architecture-principles` §3 a §4; chybějící nebo nesmyslná hodnota je
|
|
306
|
+
výjimka §5 dřív, než vznikne timer):
|
|
307
|
+
|
|
308
|
+
- `maxFailures` — kladné celé číslo beatů **v řadě**, které služba smí minout.
|
|
309
|
+
Jeden úspěch počítadlo nuluje.
|
|
310
|
+
- `onFatal(err)` — zavolá se **právě jednou**, ve chvíli, kdy je rozpočet vyčerpán.
|
|
311
|
+
Dostane chybu, která jmenuje počet, poslední příčinu (i jako `cause`) a Fix.
|
|
312
|
+
Smyčka je v tu chvíli už zastavená, takže do zavírající se služby nepřiletí
|
|
313
|
+
další beat.
|
|
314
|
+
|
|
315
|
+
**Kadence parametrem metody NENÍ** a záměrně: má jednoho vlastníka, a tím je
|
|
316
|
+
volba konstruktoru `heartbeatInterval` (rozhodnutí vlastníka
|
|
317
|
+
`api/docs/governance/confirmations/biz-health-freshness.md` 001 bod 3). Přijímat
|
|
318
|
+
ji ještě jednou při volání by bylo přesně to druhé nezávisle nastavené číslo pro
|
|
319
|
+
jednu věc, které to potvrzení odmítá.
|
|
320
|
+
|
|
321
|
+
Co s tím musí udělat volající: doplnit obě volby. Volání bez nich je od téhle
|
|
322
|
+
verze výjimka, ne tichý default. `@onlineapps/service-wrapper` pak svou vlastní
|
|
323
|
+
smyčku ruší ve prospěch téhle — samostatná dávka, po pinu.
|
|
324
|
+
|
|
325
|
+
Dvě menší změny chování ve stejné smyčce:
|
|
326
|
+
|
|
327
|
+
- **První (okamžitý) beat se počítá do téhož rozpočtu.** Dřív měl vlastní
|
|
328
|
+
`catch` a vlastní hlášku `[RegistryClient] Initial heartbeat failed`; ta
|
|
329
|
+
zaniká. Beat započtený mimo rozpočet by z `maxFailures` udělal „N + 1 pro
|
|
330
|
+
první selhání" — číslo, které volající nenastavil.
|
|
331
|
+
- **Každé selhání se loguje s počtem, kterého dosáhlo, a s rozpočtem, proti
|
|
332
|
+
kterému se počítá** (`consecutiveFailures`, `maxFailures` v meta), takže log
|
|
333
|
+
říká, jak blízko ke konci smyčka je, ne jen že jeden beat neprošel.
|
|
334
|
+
|
|
335
|
+
Testy: nová sada `tests/unit/registryClient.heartbeatBudget.test.js` (13 případů —
|
|
336
|
+
odmítnutí obou voleb hodnotou, vyčerpaný rozpočet, a kontrolní případy, které se
|
|
337
|
+
změnit nesmí: `maxFailures − 1` selhání a úspěch, zdravá smyčka bez `onFatal`,
|
|
338
|
+
idempotentní `stopHeartbeat`) a `tests/integration/heartbeatBudget.integration.test.js`
|
|
339
|
+
(3 případy proti **živému** RabbitMQ: beaty na drátě, skutečné odmítnutí po ztrátě
|
|
340
|
+
spojení, a reset počítadla beatem, který prošel). Selhání se v obou tierech vyrábí
|
|
341
|
+
skutečnou cestou kódu — nikdy atrapou `sendHeartbeat`, která by dokázala jen to, že
|
|
342
|
+
počítadlo počítá volání.
|
|
343
|
+
|
|
344
|
+
Kontrolní počet chyb v `tests/unit/error-message-contract.test.js` je 28 (bylo 25):
|
|
345
|
+
tři nové jsou odmítnutí voleb. Chyba předaná do `onFatal` mezi nimi **není** a hledat
|
|
346
|
+
se tam nemá — nevzniká `throw` ani `reject`, takže ji ta sonda nevidí; její znění
|
|
347
|
+
tvrdí hodnotou nová sada výše.
|
|
348
|
+
|
|
349
|
+
`README.md`, `docs/REGISTRY_CLIENT_GUIDE.md` a oba příklady učí novou signaturu;
|
|
350
|
+
`API.md` je přegenerované (`npm run docs`).
|
|
351
|
+
|
|
352
|
+
### Fixed
|
|
353
|
+
|
|
354
|
+
- `API.md` regenerated from the sources (`npm run docs`). The committed file still
|
|
355
|
+
described the connector as asserting `workflow` and `<serviceName>.registry` on
|
|
356
|
+
startup and as handing out its channel for further operations — a surface replaced
|
|
357
|
+
when the connector moved onto a single `@onlineapps/mq-client-core` client, declared
|
|
358
|
+
its queues through `queueConfig` and stopped asserting infrastructure queues it does
|
|
359
|
+
not own. 167 lines changed, generated content only: the file is written by `jsdoc2md`,
|
|
360
|
+
never by hand (`.claude/rules/doc-code-binding.md` §1). What now keeps it true is
|
|
361
|
+
`scripts/ci/verify-generated-api-docs.mjs` (`npm run docs:api:check`).
|
|
362
|
+
|
|
363
|
+
## [4.0.3] — 2026-09-15
|
|
364
|
+
|
|
365
|
+
## [4.0.2] — 2026-09-15
|
|
366
|
+
|
|
367
|
+
### Fixed — `redactUrl` se bere z `@onlineapps/logger-contract`, ne přes `mq-client-core` (d.455)
|
|
368
|
+
|
|
369
|
+
`src/queueManager.js` si redaktor bral z `@onlineapps/mq-client-core` (d.446b),
|
|
370
|
+
protože tehdy jediný deklaroval platformní sémantiku. Od
|
|
371
|
+
`@onlineapps/logger-contract` 1.2.0 (d.453) ji vlastní core vrstva, kterou
|
|
372
|
+
pinuje každá kategorie, takže import míří tam — vedle `assertLogger`, které si
|
|
373
|
+
tenhle soubor z téhož balíčku bere už dřív. Chování se nemění: pro `amqp` /
|
|
374
|
+
`amqps` vrací obě implementace shodný řetězec (změřeno).
|
|
375
|
+
|
|
376
|
+
Tady se nic nemaže — smazaná deklarace byla `src/redactUrl.js` a odešla už
|
|
377
|
+
v d.446b. Mění se jen to, **odkud** se tatáž funkce bere; `mq-client-core`
|
|
378
|
+
zůstává závislostí kvůli `BaseClient` a `queueConfig`.
|
|
379
|
+
|
|
380
|
+
Sady `queueManager.test.js`, `queueManager.spec.test.js`
|
|
381
|
+
a `queueManager.credentialRedaction.test.js`: dvojník `@onlineapps/mq-client-core`
|
|
382
|
+
už **záměrně nenabízí** `redactUrl` ani `UNPARSEABLE_PLACEHOLDER` — kdyby je
|
|
383
|
+
nabízel, sada by prošla i pro import ze špatného balíčku (to byla červená, která
|
|
384
|
+
tuhle změnu vynutila: `TypeError: redactUrl is not a function`,
|
|
385
|
+
`src/queueManager.js:69`). Přibyl kontrolní případ, že dvojník redaktor nemá,
|
|
386
|
+
a cesta selhání (neparsovatelná adresa → placeholder).
|
|
387
|
+
|
|
388
|
+
## [4.0.1] — 2026-09-14
|
|
389
|
+
|
|
390
|
+
## [4.0.0] — 2026-09-14
|
|
391
|
+
|
|
392
|
+
### Breaking
|
|
393
|
+
|
|
394
|
+
#### Delivery of a registry message belongs to the core, not to this package (d.198b-3)
|
|
395
|
+
|
|
396
|
+
Both consumers here held a RAW amqplib channel (`queueManager.channel`): they
|
|
397
|
+
asserted their own queue, called `channel.consume(…, { noAck: false })` and settled
|
|
398
|
+
every delivery by hand — `channel.ack(msg)` on success,
|
|
399
|
+
`channel.nack(msg, false, false)` on failure. That is a second implementation of the
|
|
400
|
+
delivery contract (`.claude/rules/change-discipline.md` § One rail per concern), and
|
|
401
|
+
it is the half that cannot count: a hand-written nack knows nothing about the attempt
|
|
402
|
+
budget, the `x-oa-delivery-attempts` header or the `message_dlq` event
|
|
403
|
+
`@onlineapps/mq-client-core` publishes. Worse, `nack(requeue=false)` asks the broker
|
|
404
|
+
to DROP the message — and it did, because `<service>.registry` declared no
|
|
405
|
+
dead-letter route at all. A registry reply this service could not read simply ceased
|
|
406
|
+
to exist.
|
|
407
|
+
|
|
408
|
+
Both now run on `consume(queue, handler, { requeueOnError: false })`: one attempt,
|
|
409
|
+
and a failure is rejected into `<service>.dlq` over `dlx`, the route `queueConfig`
|
|
410
|
+
declares for these queues. A handler reports failure by THROWING and settles nothing
|
|
411
|
+
itself. Measured on the live broker (`tests/integration/deliveryPolicy.integration`):
|
|
412
|
+
the poison message arrives in the DLQ verbatim with `x-death[0].reason: 'rejected'`,
|
|
413
|
+
the handler ran exactly once, and the control case leaves both queues empty.
|
|
414
|
+
|
|
415
|
+
`QueueManager` is now the package's single MQ surface, built on
|
|
416
|
+
`@onlineapps/mq-client-core`: `init/isConnected/ensureQueues/checkQueue/
|
|
417
|
+
assertExchange/bindQueue/publish/consume/close`. **It hands out no `channel` any
|
|
418
|
+
more** — a caller reaching for `client.queueManager.channel` gets `undefined`.
|
|
419
|
+
Queue names are this package's; the DECLARATION behind them (durability, TTL, cap,
|
|
420
|
+
dead-letter route) is `queueConfig`'s, so `<service>.registry` and
|
|
421
|
+
`<service>.registry.events` are asserted with no options of our own. The 406 branch
|
|
422
|
+
in `subscribeToChanges()` — "the queue exists with different arguments, use it
|
|
423
|
+
as-is" — went with them: that is the fallback `architecture-principles.md` §3
|
|
424
|
+
forbids, and the reason a queue keeps whatever arguments its first declarer happened
|
|
425
|
+
to pass. `register()` publishes through the core (`persistent: true`), so a failed
|
|
426
|
+
publish THROWS instead of returning `false`, and the infrastructure-queue check runs
|
|
427
|
+
on the core's queue channel rather than a private one.
|
|
428
|
+
|
|
429
|
+
#### `heartbeatInterval` and `specificationEndpoint` are REQUIRED (d.278c)
|
|
430
|
+
|
|
431
|
+
Both were module defaults (`10000`, `'/api/v1/specification'`) and both were
|
|
432
|
+
statements this package had no standing to make.
|
|
433
|
+
|
|
434
|
+
The heartbeat cadence has ONE owner — `wrapper.registry.heartbeatInterval`, fed from
|
|
435
|
+
`BIZ_HEARTBEAT_INTERVAL_MS` (owner confirmation `biz-health-freshness` 001 point 3),
|
|
436
|
+
and the freshness window monitoring computes is a multiple of it. A library default
|
|
437
|
+
beside it is a second cadence that fires whenever the caller forgets the first. The
|
|
438
|
+
specification path is REQUIRED on the wire (`registration-wire.md` §2.1) and the
|
|
439
|
+
registry stores what it is sent, so a substituted path lands in `registry:services`
|
|
440
|
+
for a service that never declared it. Both are refused at construction, by value —
|
|
441
|
+
a string `'10000'` is not parsed into a cadence — naming the fix.
|
|
442
|
+
|
|
443
|
+
**Callers must now pass both.** `@onlineapps/service-wrapper` passes
|
|
444
|
+
`specificationEndpoint` already and does NOT pass `heartbeatInterval` (it runs its
|
|
445
|
+
own heartbeat loop from `wrapper.registry.heartbeatInterval` and calls
|
|
446
|
+
`sendHeartbeat()`); that value has to reach the constructor.
|
|
447
|
+
|
|
448
|
+
#### `register()` and `deregister()` return what actually happened (d.278c)
|
|
449
|
+
|
|
450
|
+
`register()` read the registry's reply and dropped two of its fields on the floor:
|
|
451
|
+
`validationStatus` and `instructions` (`registration-wire.md` §2.5 — the parked
|
|
452
|
+
registration, "you have not pre-validated yet" plus what to run about it). A caller
|
|
453
|
+
saw `success: false` and nothing that explained it. Both are now passed through
|
|
454
|
+
verbatim, like `errorCode`: a confirmed registration sends neither and both stay
|
|
455
|
+
`undefined` — nothing is substituted.
|
|
456
|
+
|
|
457
|
+
`deregister()` answered a hardcoded `{ success: true, message: 'Service deregistered
|
|
458
|
+
successfully' }` whatever happened. The registry sends NO reply to a `deregister`
|
|
459
|
+
(`registration-wire.md` §2.3), so there was no success to report — a claim about the
|
|
460
|
+
registry made by the one party that never heard from it. It now returns what this
|
|
461
|
+
call knows: `{ requestId, serviceName, version, publishedTo, heartbeatStopped,
|
|
462
|
+
publishedAt }`. A publish that fails REJECTS; reaching the return means the broker
|
|
463
|
+
took the message. There is no `success` key.
|
|
464
|
+
|
|
465
|
+
### Fixed
|
|
466
|
+
|
|
467
|
+
#### The integration tier connects with a credential (d.446b)
|
|
468
|
+
|
|
469
|
+
`tests/config.js` composed the AMQP URI as `amqp://${host}:${port}` from
|
|
470
|
+
`RABBITMQ_HOST` + `RABBITMQ_PORT` — a URI with no account, which the broker answers
|
|
471
|
+
`403 ACCESS-REFUSED` to. 21 of the tier's 24 tests failed on it, on every run, for as
|
|
472
|
+
long as the broker has required authentication. The tier now takes `RABBITMQ_URL`
|
|
473
|
+
whole (credential included) and DERIVES host and port from it, so the address it
|
|
474
|
+
probes in `integration/setup.js` is the address it then connects to. The banner it
|
|
475
|
+
prints names host and port only.
|
|
476
|
+
|
|
477
|
+
Two more reds went with them, both stale rather than broken: `readRegistrySpec()`
|
|
478
|
+
asked `GET /services/<name>/spec`, a route `api_services_registry` stopped mounting
|
|
479
|
+
when the `/services` alias went in batch 263c — so `status === 404` passed for the
|
|
480
|
+
WRONG reason (a missing route, not a missing record) while the body assertion failed.
|
|
481
|
+
It reads `/microservices/<name>/spec` now, the route `src/app.js` mounts. And the
|
|
482
|
+
"nobody consumes the registry queue" failure path pointed at a queue that did not
|
|
483
|
+
exist; since the publish path asks the broker whether the target exists, that is now
|
|
484
|
+
a different (and faster) failure, so the harness declares the queue and leaves it
|
|
485
|
+
unconsumed — the situation the timeout exists for.
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
#### AMQP URL v hlášce i logu bez credentialu (d.446)
|
|
489
|
+
|
|
490
|
+
`AMQP_URL` je jediná kolej pro heslo k brokeru, takže obě chybové cesty
|
|
491
|
+
`QueueManager.init()` — timeout spojení i selhání `amqp.connect()` — vypisovaly to
|
|
492
|
+
heslo doslova: URL šla do hlášky nezredigovaná. Chyba konektoru jde rovnou do logu
|
|
493
|
+
služby a odtud do Loki, kde je trvalá a vyhledatelná. Táž třída úniku, kterou pro
|
|
494
|
+
Redis zavřel `shared/service-common/src/redisClient.js`.
|
|
495
|
+
|
|
496
|
+
Nově jde URL do hlášky přes `src/redactUrl.js`: userinfo (`uživatel:heslo@`) pryč,
|
|
497
|
+
schéma, host, port i vhost zůstávají — to je, co čtenář logu potřebuje. Hodnota,
|
|
498
|
+
kterou nelze přečíst jako URL, se nevypisuje vůbec, nahradí ji `<unparseable-url>`
|
|
499
|
+
(neparsovatelný řetězec je právě ten případ, kdy nikdo neumí říct, jestli
|
|
500
|
+
credential nese). Připojení samo dál používá původní URL — redakce se týká výhradně
|
|
501
|
+
renderu do textu.
|
|
502
|
+
|
|
503
|
+
Helper byl zprvu lokální (`src/redactUrl.js`): pinované závislosti balíčku tehdy
|
|
504
|
+
žádnou redakci nevystavovaly. **Od d.446b je pryč** — `@onlineapps/mq-client-core`
|
|
505
|
+
3.0.0 exportuje `redactUrl` i `UNPARSEABLE_PLACEHOLDER` s totožnou sémantikou
|
|
506
|
+
(d.448, celé userinfo pryč), takže lokální kopie byla druhá kolej pro jeden koncept
|
|
507
|
+
(`change-discipline.md` § One rail per concern). `QueueManager` ho importuje z jádra;
|
|
508
|
+
identitu toho exportu drží test, a `tests/unit/redactUrl.test.js` (chování lokální
|
|
509
|
+
kopie) odešel s ní.
|
|
510
|
+
|
|
511
|
+
### Konstruktor klienta už nenese `registryUrl` (d.244)
|
|
512
|
+
|
|
513
|
+
Parametr `registryUrl` i pole `this.registryUrl` zrušeny. Od konfirmace
|
|
514
|
+
`biz-discovery-redis` 001 a commitu `725b25c2` se jen ukládaly a nikdo je nečetl
|
|
515
|
+
(v `src/` žádný `axios`/`fetch`/`http.request`). Uložený nečtený parametr není
|
|
516
|
+
neškodný: říkal každému volajícímu, že na té adrese něco závisí — a to drželo
|
|
517
|
+
`REGISTRY_URL` jako povinný klíč v osmi biz konfiguracích. Konfirmace
|
|
518
|
+
`biz-discovery-redis` 002.
|
|
519
|
+
|
|
520
|
+
Volající, který `registryUrl` stále předá, ho dostane ignorovaný, ne uložený.
|
|
521
|
+
JSDoc konstruktoru ho nikdy nepopisoval (změřeno), takže se tam nic nemazalo.
|
|
522
|
+
Integrační tier balíčku `REGISTRY_URL` dál vyžaduje — čte jím HTTP API registru,
|
|
523
|
+
aby ověřil, co registr uložil; to je testovací harness, ne klient.
|
|
524
|
+
|
|
525
|
+
### `register()` nese `deployable` na drát (d.221)
|
|
526
|
+
|
|
527
|
+
`register()` staví zprávu z pevného seznamu klíčů, takže klíč, který nejmenuje,
|
|
528
|
+
se na drát nedostane — tiše. Nově jmenuje `deployable`: verdikt shody s manifestem
|
|
529
|
+
(krok 7/7 validace), který registry promítá do `infrastructure:health:<service>`
|
|
530
|
+
vedle `status` (potvrzení vlastníka
|
|
531
|
+
`api/docs/governance/confirmations/biz-service-manifest.md` 001 §4). Přenáší se
|
|
532
|
+
doslova ve všech třech stavech — `true`, `false`, `null` (běh konformitu nezměřil) —
|
|
533
|
+
proto test přítomnosti klíče, ne pravdivosti hodnoty. Volající, který verdikt
|
|
534
|
+
neposílá, nemá klíč ve zprávě; nic se za něj nedosazuje.
|
|
535
|
+
|
|
536
|
+
## [3.0.1] — 2026-09-04
|
|
537
|
+
|
|
538
|
+
- Changelog started with this release; the history before it is in git (`git log -- shared/connector/conn-orch-registry`).
|
package/README.md
CHANGED
|
@@ -25,11 +25,11 @@ Duty sections that apply:
|
|
|
25
25
|
|
|
26
26
|
> A lightweight client for microservice registration and heartbeat over RabbitMQ.
|
|
27
27
|
> Sends the full service specification (including `operations`) in the `register`
|
|
28
|
-
> message — see [
|
|
28
|
+
> message — see [registration-wire.md §2](../../../docs/biz/30-operations/registration-wire.md).
|
|
29
29
|
|
|
30
30
|
## 🚀 Features
|
|
31
31
|
|
|
32
|
-
* Automatic queue management (`workflow`, `<serviceName>.registry`, `api_services_queuer`, `registry.register`)
|
|
32
|
+
* Automatic queue management (`workflow`, `<serviceName>.registry`, `<serviceName>.registry.<instanceId>`, `api_services_queuer`, `registry.register`)
|
|
33
33
|
* Periodic heartbeat messages with metadata
|
|
34
34
|
* Full specification (endpoints + operations) published inside `register`
|
|
35
35
|
* Event-driven API using `EventEmitter`
|
|
@@ -63,15 +63,18 @@ const client = new ServiceRegistryClient({
|
|
|
63
63
|
version: '1.0.0',
|
|
64
64
|
specificationEndpoint: '/api/v1/specification', // required — see § Configuration
|
|
65
65
|
heartbeatInterval: 30000, // required — see § Configuration
|
|
66
|
+
instanceId: process.env.SERVICE_INSTANCE_ID, // required — THIS running copy; see § Configuration
|
|
66
67
|
logger // required — see § Logger contract below
|
|
67
68
|
});
|
|
68
69
|
|
|
69
70
|
await client.init();
|
|
70
71
|
|
|
71
72
|
// Publish full service spec in the register message (single round-trip).
|
|
73
|
+
// Only the keys of the registration contract are accepted — endpoints, operations,
|
|
74
|
+
// metadata, health, workspaceScoped, timeout; any other key is refused by name.
|
|
72
75
|
await client.register({
|
|
73
76
|
endpoints: [/* ... */],
|
|
74
|
-
operations: await loadOperationsJson(), // see
|
|
77
|
+
operations: await loadOperationsJson(), // see registration-wire.md §3
|
|
75
78
|
metadata: { /* ... */ },
|
|
76
79
|
health: '/health'
|
|
77
80
|
});
|
|
@@ -104,6 +107,7 @@ Configuration can be provided via environment variables or constructor options:
|
|
|
104
107
|
| `version` | Service version in SemVer format (required) | — |
|
|
105
108
|
| `specificationEndpoint` | Path the `register`/`heartbeat` messages carry (**required**) | — |
|
|
106
109
|
| `heartbeatInterval` | Milliseconds between heartbeats (**required**) | — |
|
|
110
|
+
| `instanceId` | Non-empty string naming THIS running copy, one value per process (**required**) | — |
|
|
107
111
|
| `apiQueue` | Queue name for heartbeat/API messages | `api_services_queuer` |
|
|
108
112
|
| `registryQueue` | Queue name for registry requests | `registry.register` |
|
|
109
113
|
|
|
@@ -122,6 +126,14 @@ whenever the caller forgets the first (architecture-principles §3):
|
|
|
122
126
|
|
|
123
127
|
Both are refused at construction, by value, naming the fix.
|
|
124
128
|
|
|
129
|
+
**`instanceId` carries no default either** (d.817, owner confirmation
|
|
130
|
+
`registry-instance-identity` 001). The service name says which service, the instance id
|
|
131
|
+
says which copy; `register`, `heartbeat` and `deregister` carry it as `instance_id`, and
|
|
132
|
+
the registry deletes a registration only when it matches. The client never makes one:
|
|
133
|
+
`@onlineapps/service-wrapper` makes it once at boot (`SERVICE_INSTANCE_ID` when the
|
|
134
|
+
deployment gives one, otherwise a UUID v4) and hands the same value to monitoring as
|
|
135
|
+
`serviceInstanceId`.
|
|
136
|
+
|
|
125
137
|
`amqpUrl` carries the broker credential, so no error message and no log line of this
|
|
126
138
|
package renders it as given: the URL passes through `redactUrl` from
|
|
127
139
|
`@onlineapps/logger-contract`, which drops the userinfo (`user:password@`) and keeps
|
|
@@ -132,8 +144,11 @@ implementace přestěhovala do core vrstvy; one concern, one rail.)
|
|
|
132
144
|
|
|
133
145
|
## 📬 Delivery of registry messages
|
|
134
146
|
|
|
135
|
-
|
|
136
|
-
|
|
147
|
+
The consumers in this package — the shared `<service>.registry` (the registry's own
|
|
148
|
+
prompts, such as `revalidate`, heard by every copy), the answer queue of THIS copy
|
|
149
|
+
`<service>.registry.<instanceId>` (named as `responseQueue` in `register`, so a copy
|
|
150
|
+
never reads the answer meant for another — owner confirmation `registry-instance-identity`
|
|
151
|
+
001 (c), d.817c) and the `<service>.registry.events` fanout copy — run on the delivery contract of
|
|
137
152
|
[`@onlineapps/mq-client-core`](https://www.npmjs.com/package/@onlineapps/mq-client-core)
|
|
138
153
|
(§ `consume()` — the delivery contract), registered with `requeueOnError: false`.
|
|
139
154
|
|
|
@@ -191,15 +206,25 @@ demands is a logger that component writes to). Standard:
|
|
|
191
206
|
### Register Request
|
|
192
207
|
```javascript
|
|
193
208
|
{
|
|
209
|
+
id: "request-uuid", // which request
|
|
210
|
+
instance_id: "copy-id", // which running copy (the constructor's instanceId)
|
|
194
211
|
type: "register",
|
|
195
212
|
serviceName: "hello-service",
|
|
196
213
|
version: "1.0.0",
|
|
197
|
-
|
|
214
|
+
specificationEndpoint: "/api/v1/specification",
|
|
198
215
|
endpoints: [...],
|
|
216
|
+
operations: { ... },
|
|
217
|
+
metadata: { ... },
|
|
218
|
+
health: "/health",
|
|
219
|
+
responseQueue: "hello-service.registry.copy-id", // THIS copy's answer queue (d.817c)
|
|
199
220
|
timestamp: "ISO-8601"
|
|
221
|
+
// + workspaceScoped when the service declares it,
|
|
222
|
+
// + validationProof / validationData when a proof was injected
|
|
200
223
|
}
|
|
201
224
|
```
|
|
202
225
|
|
|
226
|
+
The wire format has one owner: `api/docs/biz/30-operations/registration-wire.md` §2.
|
|
227
|
+
|
|
203
228
|
### Register Confirmed
|
|
204
229
|
```javascript
|
|
205
230
|
{
|
|
@@ -225,9 +250,12 @@ fixes it: `api/docs/biz/30-operations/registration-wire.md` §8.
|
|
|
225
250
|
### Heartbeat
|
|
226
251
|
```javascript
|
|
227
252
|
{
|
|
253
|
+
id: "message-uuid",
|
|
228
254
|
type: "heartbeat",
|
|
229
255
|
serviceName: "hello-service",
|
|
230
|
-
|
|
256
|
+
instance_id: "copy-id",
|
|
257
|
+
version: "1.0.0",
|
|
258
|
+
specificationEndpoint: "/api/v1/specification",
|
|
231
259
|
timestamp: "ISO-8601"
|
|
232
260
|
}
|
|
233
261
|
```
|