@onlineapps/mq-client-core 5.0.0 → 6.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/CHANGELOG.md +474 -0
- package/README.md +117 -13
- package/package.json +2 -2
- package/src/BaseClient.js +101 -68
- package/src/buffer/InMemoryBuffer.js +161 -13
- package/src/buffer/MessageBuffer.js +8 -3
- package/src/config/configSchema.js +15 -19
- package/src/config/defaultConfig.js +10 -11
- package/src/config/queueConfig.js +35 -1
- package/src/index.js +22 -0
- package/src/layers/PublishLayer.js +241 -35
- package/src/monitoring-initiated.js +6 -9
- package/src/transports/rabbitmqClient.js +912 -151
- package/src/utils/errorHandler.js +25 -0
- package/src/utils/publishErrors.js +49 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,480 @@ All notable changes to this package. Follows [Keep a Changelog](https://keepacha
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [6.0.0] — 2026-10-12
|
|
8
|
+
|
|
9
|
+
### Added — `topology.workflowObserverCopyQueues`: pozorovatelské kopie běhu mají jeden seznam (d.1336, INFRA 559)
|
|
10
|
+
|
|
11
|
+
Které fronty jsou „pozorovatelské kopie běhu“ (fronty na `monitoring.workflow.fanout` pro čtenáře,
|
|
12
|
+
který běh sleduje a není jeho součástí; dead letter z nich = ztracená kopie telemetrie, ne ztracený
|
|
13
|
+
běh) se vybíralo ručně na dvou místech — monitoring `src/consumer/topology.js` `OBSERVER_COPY_QUEUES`
|
|
14
|
+
a plánovač `config/config.js` `observerCopyQueues`. Obsah se shoduje (změřeno hodnotami):
|
|
15
|
+
`monitoring.workflow`, `delivery.workflow.events`, `scheduler.workflow.events`.
|
|
16
|
+
|
|
17
|
+
- `queueConfig` deklaruje seznam jednou (`WORKFLOW_OBSERVER_COPIES`, páry `[section, key]`) — odvodit
|
|
18
|
+
ho z deklarace nejde: vazbu na fanout dělá každý konzument sám při bootu (`bindQueueToFanout()`), na
|
|
19
|
+
položce je jen komentářem, a TTL + dead-letter route nese každá rodina. Nový pozorovatel = jeden
|
|
20
|
+
řádek vedle své deklarace.
|
|
21
|
+
- `queueConfig.workflowObserverCopyQueues()` a `topology.workflowObserverCopyQueues` vrací zmrazené pole
|
|
22
|
+
jmen složených `queueName()` (žádný literál; neznámá položka odmítnuta `ValidationError`).
|
|
23
|
+
- Komentář `monitoring.workflow.fanout` („both receive copies“ — byly tři) ukazuje na seznam.
|
|
24
|
+
- Deklarace front beze změny.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Tests — management API má v IT tieru jeden čtenář: `createMgmtAsk` ze `scripts/tests/sweepOrphanQueues.js` (d.1330)
|
|
28
|
+
|
|
29
|
+
`tests/vhost.js § mgmt` byl po d.1324/d.1326 poslední vlastní čtenář management API v repu
|
|
30
|
+
(vlastní `fetch`, vlastní mez `MGMT_ASK_TIMEOUT_MS` 5 000 ms, vlastní věta `MGMT_NO_ANSWER`);
|
|
31
|
+
`tests/integration/setup.js` navíc bránu alarmu napojoval adaptérem `{ status, json }` nad ním.
|
|
32
|
+
|
|
33
|
+
- `mgmt(path, init)` je tenké volání sdíleného čtenáře: požadavek, oba rozpočty a věta pro
|
|
34
|
+
management API, které neodpoví (`[mq-client-core/integration] The management API did not
|
|
35
|
+
answer - …` / `… did not finish within 10000ms …`), jsou čtenáře; tento soubor přidává jen
|
|
36
|
+
bránu stavů (`allow`, `MGMT_REFUSED`) a tvar `{ status, body }`, který sady balíčku čtou
|
|
37
|
+
(devět IT sad + `mgmtRead`/`mgmtWrite` beze změny). `MGMT_ASK_TIMEOUT_MS`, `MGMT_NO_ANSWER`
|
|
38
|
+
a `config.rabbitmq.mgmtAuthHeader` jsou pryč (bez čtenáře).
|
|
39
|
+
- `tests/config.js` dodává `timeouts: { connection: 5000, operation: 10000 }` (hodnoty d.1324 /
|
|
40
|
+
registry d.744b, důvod u klíče) a `tier: 'mq-client-core/integration'`; `rabbitmq` nese
|
|
41
|
+
`user`/`password` pro čtenáře.
|
|
42
|
+
- `setup.js`: brána `refuseBrokerUnderAlarm` dostává `ask: createMgmtAsk(...)` přímo — adaptér
|
|
43
|
+
odešel.
|
|
44
|
+
- Sonda `tests/unit/mgmt-reader-has-one-rail.test.js`: žádný vlastní `fetch(` v `tests/**`,
|
|
45
|
+
`setup.js` bez adaptéru a s `createMgmtAsk(`, `vhost.js` requiruje sdíleného čtenáře; chování
|
|
46
|
+
`mgmt()` nad atrapou čtenáře (tvar, `allow`, `MGMT_REFUSED`, chyba čtenáře prochází netknutá).
|
|
47
|
+
`src/**` netknuto.
|
|
48
|
+
|
|
49
|
+
### Tests — brána „broker pod alarmem“ IT tieru má jedno tělo v repu (d.1326)
|
|
50
|
+
|
|
51
|
+
- `tests/brokerAlarm.js` (`refuseBrokerUnderAlarm(ask)`, d.807 — „one concern, two
|
|
52
|
+
implementations, on purpose and on record“ do dne, kdy bude sdílené místo) odstraněn; sdílené
|
|
53
|
+
místo je `api/scripts/tests/brokerAlarm.js` vedle `sweepOrphanQueues.js` (d.1320). Čtenář
|
|
54
|
+
zůstává kolej tohoto balíčku `tests/vhost.js` § `mgmt` — `integration/setup.js` ji předává
|
|
55
|
+
s `allow: [503]` a v tvaru Response (`status`, `json()`), který sdílené tělo čte.
|
|
56
|
+
- `tests/unit/broker-alarm-gate.test.js` míří na sdílené tělo a tvrdí totéž (resource, node,
|
|
57
|
+
status, jedna otázka `/health/checks/alarms`, nedokumentovaný tvar = „unreported“, bez `ask`
|
|
58
|
+
odmítnuto jménem) + nově: mlčící čtenář = jeho vlastní chyba projde beze změny (žádný vymyšlený
|
|
59
|
+
alarm), HTTP mimo 200/503 = „did not answer the alarm check“. Tvrzení `init.allow === [503]`
|
|
60
|
+
odešlo z unit testu do adaptéru v `setup.js` (detail koleje, ne pravidla).
|
|
61
|
+
|
|
62
|
+
Příští MAJOR (vlna 5). Tři nálezy INFRA z diagnózy dev plánovače (W224, INFRA 531, 5. 10. 2026):
|
|
63
|
+
N3 je odebrání (BREAKING), N1 a N2 jsou opravy (d.1275).
|
|
64
|
+
|
|
65
|
+
### Added — `topology.infrastructureHealthChecksQueue` = `infrastructure.health.checks` (d.1307)
|
|
66
|
+
|
|
67
|
+
Nejčtenější jméno platformy — fronta, na kterou každá infra služba publikuje health checky a
|
|
68
|
+
kterou odebírá registry (`docs/standards/queue-ownership.md` § Infrastructure Queue Creation
|
|
69
|
+
Order) — mělo deklaraci tady (`queueConfig.infrastructure['health.checks']`), ale žádný složený
|
|
70
|
+
export: `topology` (d.270) nesl `health.events`, monitoring a delivery, ne `health.checks`.
|
|
71
|
+
Jméno proto žije podruhé jako literál v `@onlineapps/service-common` `src/defaults.js`
|
|
72
|
+
(`infrastructureHealthQueueName`), který registry, health publisher i wrapper čtou a v hláškách
|
|
73
|
+
označují za „one owner“ — dva deklarující vlastníci jednoho jména na drátě (nález d.1306).
|
|
74
|
+
|
|
75
|
+
- `topology.infrastructureHealthChecksQueue` je složené z deklarace
|
|
76
|
+
(`queueConfig.queueName('infrastructure', 'health.checks')`), ne přepsané: přejmenování
|
|
77
|
+
v `queueConfig` export přesune, smazaný záznam padne rychle; objekt zůstává zmrazený.
|
|
78
|
+
- Čtenář zatím žádný — `@onlineapps/service-common` / `@onlineapps/infrastructure-tools` se
|
|
79
|
+
přepnou podle rozhodnutí vlastníka A/B (d.1306: service-common cituje export × čtenáři
|
|
80
|
+
citují export přímo a klíč service-common zaniká). Export je krok společný oběma cestám.
|
|
81
|
+
- Žádná změna chování transportu; test `tests/unit/queueConfig-public-export.test.js`
|
|
82
|
+
(hodnota, složení z deklarace a nepřítomnost literálu v `src/index.js`, argumenty fronty,
|
|
83
|
+
kontrola zmrazení a nezměněných jmen). README § topology — řádek tabulky.
|
|
84
|
+
|
|
85
|
+
### Removed — BREAKING — `criticalHealthShutdown`, `criticalHealthShutdownDelay` a událost `health:shutdown` (d.1275, N3)
|
|
86
|
+
|
|
87
|
+
Slovo vlastníka 2026-10-06 (dialog 7 (4)): „volbu i událost ZRUŠIT, žádný slib — o životě
|
|
88
|
+
procesu rozhoduje wrapper/služba“.
|
|
89
|
+
|
|
90
|
+
Co to znamená pro volajícího — hodnotami:
|
|
91
|
+
|
|
92
|
+
- `new BaseClient({ …, criticalHealthShutdown: <cokoli> })` i `criticalHealthShutdownDelay`
|
|
93
|
+
hází `ValidationError` `[BaseClient] Configuration key "criticalHealthShutdown" does not exist
|
|
94
|
+
- Expected: no such key; a shutdown on sustained critical health is not something this client
|
|
95
|
+
has. Fix: remove "criticalHealthShutdown" from the configuration. …` — odmítnutí jménem
|
|
96
|
+
kolejí `NOT_A_KEY_OF_THIS_CLIENT`, jako `persistentBufferEnabled` (d.343); ani `false` se
|
|
97
|
+
nepřijme, protože už není co vypínat;
|
|
98
|
+
- `defaultConfig` ani schéma oba klíče nedeklarují;
|
|
99
|
+
- transport po žádné délce kritického zdraví neemituje `health:shutdown` a nepíše řádky
|
|
100
|
+
„Critical health will trigger shutdown in <ms> if not resolved“ ani „… - triggering shutdown“;
|
|
101
|
+
- beze změny: `health:critical` a `healthCriticalCallback` při každé selhané kontrole, řádky
|
|
102
|
+
„Critical health detected at …“ a „Health recovered after <ms> of critical state“.
|
|
103
|
+
|
|
104
|
+
Čtyři otázky `change-discipline.md` § Removing:
|
|
105
|
+
|
|
106
|
+
1. **Proč vznikl a co měl dělat.** Volba přišla s monitorováním zdraví transportu: po
|
|
107
|
+
`criticalHealthShutdownDelay` (60 s) kritického zdraví emitovat `health:shutdown`, aby
|
|
108
|
+
aplikace proces ukončila a orchestrátor kontejneru ho nahodil znovu. Výchozí `true`.
|
|
109
|
+
2. **Která část konceptu ji nesla a platí ještě.** Žádná živá: koncept životního cyklu říká
|
|
110
|
+
opak — knihovna proces neukončuje, ukončení je rozhodnutí vlastníka životního cyklu
|
|
111
|
+
(`_failFatally()` v `transports/rabbitmqClient.js`; trvalou ztrátu spojení hlásí
|
|
112
|
+
`connection:fatal` + injektovaný `onFatal`). `docs/architecture/rabbitmq-channel-lifecycle.md`
|
|
113
|
+
ji popisuje jako knob, ne jako rozhodnutí.
|
|
114
|
+
3. **Proč ji dnes nic nečte.** Ustoupila rozhodnutí: `@onlineapps/service-wrapper` posluchače
|
|
115
|
+
`health:shutdown` odebral 2026-09-07 (jeho CHANGELOG § „Removed — the MQ `health:shutdown`
|
|
116
|
+
policy that could never apply“), žádná infra služba ho nikdy neměla — změřeno 2026-10-06
|
|
117
|
+
`git grep -n -e "health:shutdown" -e "criticalHealthShutdown"`: mimo tento balíček jen
|
|
118
|
+
komentáře a testy wrapperu, dvě věty v `docs/` a záznamy `TODO.md`, žádný kód. Výchozí `true`
|
|
119
|
+
tak každé kritické okno každého klienta zakončil slibem, který nikdo nesplnil (INFRA 531:
|
|
120
|
+
plánovač na devu hlásil „will trigger shutdown“, proces běžel dál — správně).
|
|
121
|
+
4. **Je náhrada koncepčnější.** Ano: jedna kolej pro „proces končí“ (`onFatal` /
|
|
122
|
+
`connection:fatal`, vlastník životního cyklu rozhoduje), a pro trvalé kritické zdraví
|
|
123
|
+
hlášení bez rozhodnutí (`health:critical`, `healthCriticalCallback`). Slib bez vykonavatele
|
|
124
|
+
koncepčnější není nikdy.
|
|
125
|
+
|
|
126
|
+
### Fixed — `consumers.active` se počítá z vlastního stavu klienta, ne z toho, zda doběhla sonda fronty (d.1275, N1)
|
|
127
|
+
|
|
128
|
+
Dosud se `active` přičítalo jen uvnitř smyčky `checkQueue` v `_performHealthCheck()`. Mrtvý
|
|
129
|
+
frontový (queue) kanál smyčku vůbec nespustil a `checkQueue` bez odpovědi v `brokerAnswerTimeout`
|
|
130
|
+
ji ukončil — v obou případech `active: 0` a verdikt „No consumer of this client is attached …
|
|
131
|
+
this client takes no message off any queue“ o klientovi, jehož consumer kanál žil a přijímal
|
|
132
|
+
(W224 na dev plánovači 5. 10.: spojení i consumer kanál žily, queue kanál thrashoval).
|
|
133
|
+
|
|
134
|
+
- „Připojený konzument“ má jednu definici `_isConsumerAttached()`: consumer kanál je živý
|
|
135
|
+
**a** záznam nese tag brokera. Čtou ji `performHealthCheck()` i `getConsumerState()` (d.613),
|
|
136
|
+
takže se nemění jen zdraví: **`getConsumerState().active` je při mrtvém consumer kanálu `[]`**
|
|
137
|
+
(dosud vypisoval frontu, dokud záznam držel tag kanálu, který už neexistuje).
|
|
138
|
+
- `consumers.active` / `consumers.inactive` se počítají z toho před sondou, nezávisle na ní.
|
|
139
|
+
Mrtvý queue kanál je nález kanálu (`Queue channel is closed or missing`), neúspěšná sonda
|
|
140
|
+
nález sondy (`Error checking queue <q>: …`); věta „No consumer …“ jen když opravdu žádný
|
|
141
|
+
konzument připojený není.
|
|
142
|
+
- Nové pole `queues.consumers`: `{ <fronta>: <consumerCount brokera> | 'unknown' }` — počet
|
|
143
|
+
konzumentů na frontě podle brokera (všech, ne jen tohoto klienta), `'unknown'` kde sonda
|
|
144
|
+
neběžela nebo nedostala odpověď. Nikdy 0 za „neptal jsem se“.
|
|
145
|
+
- Issue u fronty bez připojeného konzumenta jmenuje příčinu: `(consumerTag missing)` při živém
|
|
146
|
+
consumer kanálu, `(consumer channel is not alive)` při mrtvém.
|
|
147
|
+
|
|
148
|
+
### Fixed — buffer publishe expiruje podle vlastních hodin a `PublishError` neslibuje „not lost“ (d.1275, N2)
|
|
149
|
+
|
|
150
|
+
- Záznam bufferu se zahodí **v okamžiku svého TTL** (`publishBufferTtlMs`) jedním časovačem
|
|
151
|
+
bufferu, ne až při příštím `add()`/`flush()`/`getBufferState()`. Změřeno na dev plánovači:
|
|
152
|
+
stáří při líné expiraci 584 843 ms a 5 267 969 ms místo 300 000. Časovač běží po koleji
|
|
153
|
+
časovačů klienta (`_setTimeout()`/`_clearTimeout()`, handle v `_activeTimers`), takže ho
|
|
154
|
+
`disconnect()` ukončí; prázdný buffer žádný časovač nedrží. `InMemoryBuffer`, `MessageBuffer`
|
|
155
|
+
a `PublishLayer` (interní, neexportované) proto vyžadují `timers: { setTimeout, clearTimeout }`
|
|
156
|
+
a bez nich odmítnou konstrukci jménem.
|
|
157
|
+
- ERROR řádek expirace: `sent=false` → `[InMemoryBuffer] <n> buffered message(s) for <cíl> LOST
|
|
158
|
+
after <ms> ms (buffer TTL <ttl> ms; …)`, `sent=true` → `… expired after <ms> ms …` (broker ji
|
|
159
|
+
mohl doručit sám; conf `mq-publish-buffer` 001 — obě věty se nikdy neslučují). Věta `Expected:`
|
|
160
|
+
nově říká, že samotná obnova publisher kanálu nic nepřehraje.
|
|
161
|
+
- `PublishError` z `PublishLayer` s `buffered: true` (vyčerpané pokusy, selhaný reconnect, na
|
|
162
|
+
který retry čekal) už ve `Fix:` neříká „the message is buffered, so it is not lost“, ale: „the message is buffered
|
|
163
|
+
for up to <ttl> ms (publishBufferTtlMs) and replayed only after a reconnect or after the broker
|
|
164
|
+
unblocks the connection — a recovered publisher channel alone does not replay it; if neither
|
|
165
|
+
comes within that time it expires and is reported as LOST …“ (u `sent: true` „reported as an
|
|
166
|
+
expired message whose frame had been sent (the broker may hold a copy)“).
|
|
167
|
+
- **Neudělané (čeká na rozhodnutí):** flush bufferu po obnově samotného publisher kanálu.
|
|
168
|
+
Obnova kanálu na živém spojení je třetí spouštěč, o kterém conf `mq-publish-buffer` 001 nemluví
|
|
169
|
+
(rozhodl jen `reconnected` a `unblocked`), a u záznamu s `sent: true` je volba mezi druhou
|
|
170
|
+
kopií a ztrátou — §1.2 `confirmation-triggers.md`.
|
|
171
|
+
|
|
172
|
+
### Fixed — `consume()`, které čekalo na obnovu, jež nedorazila, dostane chybu toho čekání, ne „queue channel is not available“ (d.1278, INFRA-DOCS ID254)
|
|
173
|
+
|
|
174
|
+
Dosud `_ensureConsumerChannel()` chybu z čekání na obnovu (`_waitForReconnection()`) zapsalo
|
|
175
|
+
jako WARN a odpovědělo `{ reRegistered: 0, failed: n }`, jako by to byl výsledek; `consume()`
|
|
176
|
+
pokračovalo bez kanálu a volající dostal odmítnutí koleje frontového kanálu „Cannot checkQueue
|
|
177
|
+
<q>: queue channel is not available … Fix: await client.connect()“ — bez `cause`, bez jména
|
|
178
|
+
cyklu, a verdikt fatálního cyklu (`MQ_CONNECTION_FATAL`) dostal jen `onFatal`.
|
|
179
|
+
|
|
180
|
+
- `_ensureConsumerChannel()` po neúspěšném čekání **hází** `ConnectionError` `[RabbitMQClient]
|
|
181
|
+
Cannot recreate the consumer channel: the recovery it waited for did not arrive - <věta
|
|
182
|
+
chyby čekání> Expected: … Fix: read error.cause for the verdict of that recovery …` — stejně
|
|
183
|
+
jako publisher kolej (`_ensurePublisherChannel()`) chybu čekání propouští.
|
|
184
|
+
- `error.cause` je **tatáž** chyba, kterou cyklus vydal, ne kopie: po posledním cyklu objekt s
|
|
185
|
+
`code: 'MQ_CONNECTION_FATAL'`, který už dostal `onFatal` (volán právě jednou — hození není
|
|
186
|
+
druhé hlášení); po vyčerpaném cyklu `ConnectionError` jmenující cyklus (`recovery cycle 2/3
|
|
187
|
+
spent its 2 attempts …`) **bez `code`** — klient si `MQ_CONNECTION_STANDBY` nechává
|
|
188
|
+
(`docs/standards/rabbitmq-channel-best-practices.md` § Recovery).
|
|
189
|
+
- Přes `BaseClient.consume()` je to `ConsumeError` → `cause` = ta `ConnectionError` → `cause.cause`
|
|
190
|
+
= verdikt cyklu.
|
|
191
|
+
- Beze změny: `consume()` na živém kanálu; `consume()` po návratu brokeru spustí další cyklus a
|
|
192
|
+
konzumenta připojí (líná polovina obnovy, d.339); cesta `onFatal`.
|
|
193
|
+
|
|
194
|
+
### Fixed — `consume()` na klientovi, který obnovu nespustí, řekne proč — ne „queue channel is not available“ (d.1281, nález d.1278)
|
|
195
|
+
|
|
196
|
+
Zbylé dvě větve `_ensureConsumerChannel()`, které po d.1278 ještě vracely `{ reRegistered: 0,
|
|
197
|
+
failed: n }` (volající pak dostal odmítnutí frontového kanálu „… queue channel is not available
|
|
198
|
+
… Fix: await client.connect()“ bez `cause`), **hází** `ConnectionError` jmenující stav:
|
|
199
|
+
|
|
200
|
+
- spojení trvale ztracené (`isConnectionFatal()`): `… the connection is permanently lost and no
|
|
201
|
+
recovery may start - <věta verdiktu> …`; `error.cause` je **tentýž objekt**, který dostal
|
|
202
|
+
`onFatal` / `connection:fatal` (`code: 'MQ_CONNECTION_FATAL'`) — klient si ho nově drží
|
|
203
|
+
(`_connectionFatalError`), `onFatal` se nevolá podruhé;
|
|
204
|
+
- zavřeno volajícím (`disconnect()`): `… the caller closed this client (disconnect()) and no
|
|
205
|
+
recovery may start - … Fix: await client.connect() before consume(); only an explicit connect()
|
|
206
|
+
re-arms recovery.` — bez `cause`;
|
|
207
|
+
- obnova vypnutá (`reconnectEnabled: false`): `… the connection is closed and recovery is disabled
|
|
208
|
+
by configuration (reconnectEnabled: false) - …` — bez `cause`;
|
|
209
|
+
- čekání skončilo `reconnected`, ale spojení už zase neživé: `… the recovery it waited for
|
|
210
|
+
completed, but the connection is not alive any more - … Fix: run consume() again …` — bez
|
|
211
|
+
`cause` (druhou smrt nic nedrží jako objekt).
|
|
212
|
+
|
|
213
|
+
Beze změny: `consume()` na živém kanálu, `onFatal` cesta, publish cesta.
|
|
214
|
+
|
|
215
|
+
### Fixed — verdikt `MQ_CONNECTION_FATAL` je na chybě z `consume()` čitelný nahoře, ne o `cause` níž (d.1297, nález d.1294)
|
|
216
|
+
|
|
217
|
+
Chyby z d.1278 a d.1281 (`consume()` čekající na obnovu, která skončila fatálně; `consume()` na
|
|
218
|
+
trvale ztraceném klientovi) nesly verdikt cyklu jen jako `cause`. `BaseClient._consumerRefusal()`
|
|
219
|
+
propouští odmítnutí beze změny jen s `code === 'MQ_CONNECTION_FATAL'` na chybě samé (d.577:
|
|
220
|
+
značku nikdy nepohřbít o `cause` hlouběji) — obalilo je tedy `ConsumeError` a vlastník životního
|
|
221
|
+
cyklu, který čte `error.code`, verdikt neviděl.
|
|
222
|
+
|
|
223
|
+
- Transport verdikt **přenáší nahoru** (`_carryVerdict()`, jedna kolej pro obě místa): je-li
|
|
224
|
+
příčina fatální verdikt, chyba nese `code: 'MQ_CONNECTION_FATAL'` a `reason` verdiktu
|
|
225
|
+
(`'cycles-spent'` | `'broker-refused'`); `cause` zůstává tentýž objekt, který dostal `onFatal`.
|
|
226
|
+
- `BaseClient.consume()` takovou chybu hází **beze změny** (`ConnectionError`, ne `ConsumeError`) —
|
|
227
|
+
tak jako každé jiné odmítnutí s verdiktem.
|
|
228
|
+
- Nic nechodí řetězem `cause` (žádný fallback nad pohřbenou značkou): vyčerpaný cyklus (standby),
|
|
229
|
+
`disconnect()` a `reconnectEnabled: false` dál nenesou `code` ani `reason`.
|
|
230
|
+
- Věty `Fix:` obou chyb říkají „read error.code“ místo „read error.cause“.
|
|
231
|
+
|
|
232
|
+
### Changed — BREAKING — `publish()`, jehož čekání na obnovu skončí standby/fatal, zprávu DRŽÍ v bufferu (`buffered: true`) a nese verdikt nahoře (d.1300, B1)
|
|
233
|
+
|
|
234
|
+
Slovo vlastníka 2026-10-06 (dialog 9): „B1 — držet v bufferu, `buffered: true`“ — jedna kolej
|
|
235
|
+
pro „čekání na reconnect selhalo“ se sousední cestou `PublishLayer` (přechodná chyba → čekání na
|
|
236
|
+
běžící reconnect → selhání → držení). Dosud (INFRA-DOCS ID254 bod 1, sonda d.1278): send kanál,
|
|
237
|
+
který pokus nedostal, protože spojení zůstalo dole, hodil chybu čekání syrově — volající dostal
|
|
238
|
+
`PublishError` se `sent: false` a **bez `buffered`**, zpráva držena nebyla.
|
|
239
|
+
|
|
240
|
+
Co se mění pro volajícího — hodnotami:
|
|
241
|
+
|
|
242
|
+
- `publish()` čekající na obnovu, která skončí **standby** (vyčerpaný cyklus) nebo **dokončením
|
|
243
|
+
do mrtvého spojení** — tedy tam, kde na tomto klientovi ještě může přijít `reconnected` —
|
|
244
|
+
odmítne `PublishError` s `buffered: true`, `sent: false`; zpráva je v bufferu a **přehraje se
|
|
245
|
+
po příštím `reconnected` / `unblocked`** (nic jiného ji nepřehrává), TTL `publishBufferTtlMs`
|
|
246
|
+
běží od držení, při expiraci ERROR řádek d.1275 (`LOST after <ms>`). Věta `Fix:` jmenuje stav
|
|
247
|
+
(`recovery cycle 2/3 spent its 2 attempts …`) a pravidla přehrání.
|
|
248
|
+
- Tam, kde `reconnected` už nikdy nepřijde — čekání skončilo **fatálem**, klient je **trvale
|
|
249
|
+
ztracený**, zavřený **`disconnect()`**, nebo má **`reconnectEnabled: false`** — se **nic
|
|
250
|
+
nedrží** (`buffered: false`, věta „NOT buffered: this client will not reconnect on its own …
|
|
251
|
+
the caller holds the only copy“; d.1301 zúžení B1 rozhodnutím leada: držení má smysl jen tam,
|
|
252
|
+
kde přijde `reconnected`; jinak je to ztráta s odkladem hlášená až při TTL). Třída to nese v
|
|
253
|
+
povinném `replayable` (boolean), které transport odvozuje ze stavu po skončení čekání.
|
|
254
|
+
- `bufferOnFailure: false` platí i tady: `buffered: false`, nic se nedrží (orchestrátor,
|
|
255
|
+
`publish_pending`).
|
|
256
|
+
- **Verdikt nahoře** (publishová polovina d.1297): skončí-li obnova fatálně, nese `PublishError`
|
|
257
|
+
z `BaseClient.publish()` i chyba `PublishLayer` `code: 'MQ_CONNECTION_FATAL'` a `reason`
|
|
258
|
+
(`'cycles-spent'` | `'broker-refused'`), `cause` řetěz končí tímtéž objektem, který dostal
|
|
259
|
+
`onFatal`. Standby, `disconnect()` a `reconnectEnabled: false` `code` nenesou (kontrakt).
|
|
260
|
+
- Nová třída `RecoveryEndedPublishError extends TransientPublishError` (export
|
|
261
|
+
`publishErrors.RecoveryEndedPublishError`): co transport hodí z `_ensurePublisherChannel()`,
|
|
262
|
+
když spojení zůstalo dole; `PublishLayer` ji drží okamžitě (žádný retry — další cyklus
|
|
263
|
+
spouští až příští použití klienta) týmž pomocníkem `_failedAfterRecovery()` jako selhané
|
|
264
|
+
čekání retry. Při `publishRetryEnabled: false` se hází tato chyba sama s `buffered`.
|
|
265
|
+
- Událost `publish:failed` nese `reconnectFailed: true`, `retryable: false` — jako u sousední
|
|
266
|
+
cesty; `publish:buffered` při držení.
|
|
267
|
+
- `carryVerdict()` z `utils/errorHandler.js` je jedno tělo přenosu verdiktu (transport,
|
|
268
|
+
`PublishLayer`, `BaseClient.publish()`).
|
|
269
|
+
- Beze změny: sousední cesta (přechodná chyba → čekání na běžící reconnect → selhání) — stejný
|
|
270
|
+
tvar jako dosud; publish na živém spojení; `onFatal` právě jednou.
|
|
271
|
+
|
|
272
|
+
### Fixed — `BaseClient.publish()`/`consume()` po `connection:fatal` odmítá s verdiktem nahoře, ne radou „await client.connect()“ (d.1301, nález d.1294 §4/2)
|
|
273
|
+
|
|
274
|
+
Dosud po fatálu `_connected = false` → `ConnectionError` „Cannot publish/consume: client is not
|
|
275
|
+
connected … Fix: await client.connect()“ bez `code`, ačkoli klient verdikt držel (`_fatalError`) —
|
|
276
|
+
rada nepravdivá (po fatálu rozhoduje vlastník životního cyklu z `onFatal`, ne `connect()`) a
|
|
277
|
+
větve „trvale ztracený“ transportu (d.1281, d.1300) byly přes `BaseClient` nedosažitelné.
|
|
278
|
+
|
|
279
|
+
- `BaseClient` po fatálu volání **pouští do transportu**, který stav zná a odpoví sám (jedna
|
|
280
|
+
kolej, žádná druhá věta): `publish()` → `PublishError` s `code: 'MQ_CONNECTION_FATAL'`,
|
|
281
|
+
`reason`, `buffered: false`, `sent: false`, `cause` řetěz končí objektem z `onFatal`;
|
|
282
|
+
`consume()` → `ConnectionError` s týmž `code`/`reason`, `cause` = tentýž objekt
|
|
283
|
+
(`_consumerRefusal()` ji propouští beze změny). Nikde slovo `connect()` jako rada.
|
|
284
|
+
- Beze změny: před prvním `connect()` a po čistém `disconnect()` zůstává „not connected … Fix:
|
|
285
|
+
await client.connect()“ bez `code` — tam je rada pravdivá. Explicitní `connect()` verdikt
|
|
286
|
+
smaže (staví nový transport).
|
|
287
|
+
|
|
288
|
+
### Fixed — každá operace na brokeru po `connection:fatal` odpovídá verdiktem nahoře, ne radou „await client.connect()“ (d.1314, nález d.1301 §6/2)
|
|
289
|
+
|
|
290
|
+
Rozšíření d.1301 na zbylé strážené metody `BaseClient`: `assertQueue`, `assertExchange`,
|
|
291
|
+
`bindQueue`, `checkQueue`, `purgeQueue`, `deleteQueue`, `cancelConsumer`, `ack`, `nack` (měřeno:
|
|
292
|
+
všech devět po fatálu hlásilo „Cannot <op>: client is not connected … Fix: await client.connect()“
|
|
293
|
+
bez `code`).
|
|
294
|
+
|
|
295
|
+
- `BaseClient`: jediná stráž `_requireUsable(op)` pro všech jedenáct operací (dosud jedenáct kopií);
|
|
296
|
+
odmítá jen před prvním `connect()` a po `disconnect()` — toutéž větou jako dosud; po fatálu
|
|
297
|
+
pouští volání do transportu.
|
|
298
|
+
- Transport: jeden stavitel odmítnutí `_refusalWithoutRecovery(headline, verb)` (dosud jen pro
|
|
299
|
+
consumer kanál) odpovídá i v koleji frontového kanálu (`_requireQueueChannel`: assert/check/
|
|
300
|
+
purge/delete/bind/assertExchange), v `cancelConsumer()` a v `ack()`/`nack()`, je-li spojení
|
|
301
|
+
trvale ztracené: `ConnectionError` s `code: 'MQ_CONNECTION_FATAL'`, `reason`, `cause` = verdikt z
|
|
302
|
+
`onFatal`; věta „… the connection is permanently lost and no recovery may start - …“ místo
|
|
303
|
+
„queue channel is not available … wait for the reconnect“ / „consumer channel is not initialized
|
|
304
|
+
… await client.connect()“.
|
|
305
|
+
- Stavové metody po fatálu nehází a říkají pravdu (beze změny): `isConnected()` `false`,
|
|
306
|
+
`isConnectionFatal()` `true`, `getConsumerState()` `{ tracked, active: [] }`,
|
|
307
|
+
`performHealthCheck()` `healthy: false` s `connection.closed: true`, metriky a stav bufferu.
|
|
308
|
+
|
|
309
|
+
### Fixed — transport: „spojení dole a obnova nemůže začít“ odpovídá na každé koleji touž větou stavu; predikát „obnova může začít“ má jedno tělo (d.1319, nálezy d.1314)
|
|
310
|
+
|
|
311
|
+
- (A) Po `disconnect()` a při `reconnectEnabled: false` (spojení dole) radily přímé koleje transportu
|
|
312
|
+
podle staré věty — frontový kanál „queue channel is not available … wait for the reconnect“,
|
|
313
|
+
`ack`/`nack` „consumer channel is not initialized … await client.connect() and start the consumer“,
|
|
314
|
+
`cancelConsumer` „call cancelConsumer() again once the connection is back“ — ačkoli žádný reconnect
|
|
315
|
+
nepřijde. Teď všechny (assert/check/purge/delete/bind/assertExchange, `cancelConsumer`, `ack`,
|
|
316
|
+
`nack`) odpovídají `_refusalWithoutRecovery()` (tři stavy jako consume d.1281): „… the caller closed
|
|
317
|
+
this client (disconnect()) and no recovery may start - …“ resp. „… recovery is disabled by
|
|
318
|
+
configuration (reconnectEnabled: false) - …“, bez `code` (není fatál). Beze změny: standby
|
|
319
|
+
(cyklus může přijít) dál „wait for the reconnect“; po fatálu verdikt (d.1314). Přes `BaseClient`
|
|
320
|
+
tyto dva stavy dál chytá jeho stráž („not connected … Fix: await client.connect()“).
|
|
321
|
+
- (B) `_recoveryMayStart()` je jediné tělo predikátu „obnova může začít“ (ne fatál, ne `disconnect()`,
|
|
322
|
+
`reconnectEnabled: true`); čtou ho `_resumeRecovery()`, `replayable` v `_recoveryEndedForPublish()`
|
|
323
|
+
a koleje z (A). Chování beze změny.
|
|
324
|
+
|
|
325
|
+
### Fixed — zavření spojení z opuštěného handshaku čeká na `close-ok` nejvýš `brokerAnswerTimeout` (d.1322, položka vlny 5)
|
|
326
|
+
|
|
327
|
+
`_closeAbandonedHandshake()` (handshake dokončený po `connectTimeout`, který klientovi nepatří)
|
|
328
|
+
čekalo na `late.close()` bez rozpočtu: broker, který na close neodpověděl, nechal pozdní spojení,
|
|
329
|
+
jeho socket i heartbeat žít do konce procesu (únik handshaků). Teď jde zavření toutéž kolejí jako
|
|
330
|
+
teardown (`_closeWithinTeardownBudget()` → `_awaitBrokerAnswer()`, jediný rozpočet čekání na broker —
|
|
331
|
+
conf `mq-broker-answer-budget` 001): po `brokerAnswerTimeout` WARN „The broker did not answer the
|
|
332
|
+
abandoned <connect|reconnect> handshake close within the teardown budget (<ms>ms) - …“ a socket s
|
|
333
|
+
heartbeatem se zahodí (`_dropUnansweredConnection()`, jako u napůl otevřeného spojení v `connect()`).
|
|
334
|
+
Časovač rozpočtu je na koleji klienta a s odpovědí se ruší; odmítnuté close hlásí dosavadní WARN.
|
|
335
|
+
|
|
336
|
+
## [5.1.0] — 2026-10-03
|
|
337
|
+
|
|
338
|
+
### Added
|
|
339
|
+
|
|
340
|
+
- `publish(queue, message, { bufferOnFailure: false })` — volba pro jedno volání: zpráva, jejíž
|
|
341
|
+
publikace selhala přechodně (vyčerpané pokusy, `publishRetryEnabled: false`, selhaný reconnect,
|
|
342
|
+
na který retry čekal), se NEuloží do bufferu, takže ji žádný reconnect znovu neodešle. Výchozí
|
|
343
|
+
`true` = chování beze změny. Pro volajícího, který zprávu drží trvale jinde (orchestrátor,
|
|
344
|
+
`publish_pending`, conf `step-publish-failure` 001; d.1122). Hodnota jiná než boolean se
|
|
345
|
+
odmítne `ValidationError` před prvním pokusem.
|
|
346
|
+
- Chyba ze tří přechodných cest nese `buffered` (boolean) — na chybě `PublishLayer` i na obalu
|
|
347
|
+
`PublishError` z `BaseClient.publish()`: `true` jen když buffer zprávu opravdu drží. Trvalé
|
|
348
|
+
odmítnutí `buffered` nenese. Věta `Fix:` chyby už neslibuje „is buffered“ tam, kde zpráva
|
|
349
|
+
v bufferu není (volba `false`, nebo buffer zprávu nepřijal).
|
|
350
|
+
- Chyba publishe nese `sent` (boolean) — zda rámec zprávy OPUSTIL klienta (`sendToQueue()` /
|
|
351
|
+
`publish()` kanálu vrátil bez výjimky), vedle `buffered` z d.1122: na obalu `PublishError`
|
|
352
|
+
z `BaseClient.publish()` i na chybě `PublishLayer` (`true`, když ho odeslal KTERÝKOLI pokus
|
|
353
|
+
volání); chyba jednoho pokusu transportu (`_publishOnce()`) nese `sent` toho pokusu. Záznam
|
|
354
|
+
bufferu nese totéž. Neříká nic o tom, zda broker zprávu uložil: při memory alarmu broker
|
|
355
|
+
odeslané rámce doručí sám po jeho zrušení (d.1143: 3 kopie publishe na exchange, 1 kopie
|
|
356
|
+
prvního publishe do fronty, 0 u neodeslaného). Rozhodnutí vlastníka
|
|
357
|
+
`docs/governance/confirmations/mq-publish-buffer.md` 001 (D); orchestrátor ho kopíruje do
|
|
358
|
+
záznamu `publish_pending` (`step-publish-failure` 003). Chyba transportu bez booleovského
|
|
359
|
+
`sent` je vada souseda: `PublishLayer` ji odmítne jménem a zprávu nebufferuje (d.1145).
|
|
360
|
+
|
|
361
|
+
### Changed
|
|
362
|
+
|
|
363
|
+
- Každé čekání na odpověď brokera má limit `brokerAnswerTimeout` — jedna kolej, žádný nový klíč
|
|
364
|
+
(rozhodnutí vlastníka, `docs/governance/confirmations/mq-broker-answer-budget.md` 001, verdikt A;
|
|
365
|
+
d.1135). Dosud ho mělo jen potvrzení publishe a `close-ok` při `disconnect()`; `assertQueue`,
|
|
366
|
+
`checkQueue` (veřejné i kontrola před publishem do fronty), `bindQueue`, `assertExchange`,
|
|
367
|
+
`purgeQueue`, `deleteQueue`, `consume`, `cancelConsumer`, prefetch a otevření kanálu
|
|
368
|
+
(`createChannel` / `createConfirmChannel`, při `connect()`, obnově i reconnectu) čekaly bez
|
|
369
|
+
limitu. Změřeno d.1126 na izolovaném brokeru platformního obrazu: při alarmu paměti (spojení
|
|
370
|
+
žije, heartbeat teče) visela po 300 s, u zmrazeného brokeru je ukončil až heartbeat po ~90 s.
|
|
371
|
+
Co se mění pro volajícího — hodnotami:
|
|
372
|
+
- volání, na které broker v limitu neodpoví, skončí po `brokerAnswerTimeout` chybou
|
|
373
|
+
`ConnectionError` s větou `The broker did not answer <operace> <cíl> within
|
|
374
|
+
brokerAnswerTimeout (<N>ms)`; kanál, který požadavek nesl, se vyřadí a příští operace otevře
|
|
375
|
+
nový (kanál, který broker otevře až po limitu, se hned zavře);
|
|
376
|
+
- publish do fronty, jehož kontrola `checkQueue` (nebo obnova kanálu) nedostala odpověď, je
|
|
377
|
+
přechodné selhání: `TransientPublishError` → retry → buffer, s `bufferOnFailure: false`
|
|
378
|
+
`PublishError(cause=TransientPublishError)` a `buffered: false` (D2, conf
|
|
379
|
+
`step-publish-failure` 001);
|
|
380
|
+
- holé `Channel ended, no reply will be forthcoming` z amqplibu (kanál nebo spojení skončilo
|
|
381
|
+
dřív, než broker odpověděl) se na těchto voláních vrací jako `ConnectionError` s původní
|
|
382
|
+
chybou v `cause`; číslované odmítnutí brokera (404, 406, …) zůstává beze změny;
|
|
383
|
+
- `connect()`, jemuž broker neodpoví na otevření kanálu, skončí do limitu a zavře spojení
|
|
384
|
+
v témže limitu (neodpovězené zavření zahodí socket jako `disconnect()`).
|
|
385
|
+
Heartbeat, potvrzení publishe a teardown beze změny.
|
|
386
|
+
- Klient poslouchá `blocked`/`unblocked` svého spojení (broker v memory/disk alarmu blokuje
|
|
387
|
+
spojení, které publikuje; d.1143 změřil na obrazu platformy). `blocked` = `warn` s důvodem
|
|
388
|
+
brokera; `unblocked` = `warn` a flush bufferu jednou kolejí `PublishLayer.flushBuffered('unblocked')`:
|
|
389
|
+
znovu se pošle JEN záznam, jehož rámec nikdy neopustil klienta; záznam s `sent: true` se vyřadí
|
|
390
|
+
s `warn` (fronta/exchange, stáří v bufferu), protože broker podržené rámce po alarmu doručí sám.
|
|
391
|
+
Flush po reconnectu beze změny — `flushBuffered('reconnected')` pošle všechno. Změřeno na
|
|
392
|
+
izolovaném brokeru d1145-rmq: po uvolnění alarmu u brokera 3 kopie odeslaného publishe na
|
|
393
|
+
exchange (beze změny, žádná navíc), 1 kopie odeslaného prvního publishe do fronty (beze změny)
|
|
394
|
+
a nově 1 kopie publishe, který se neodeslal (dřív 0 a tichá expirace); buffer po uvolnění
|
|
395
|
+
prázdný (dřív 2 a 1). Rozhodnutí vlastníka `docs/governance/confirmations/mq-publish-buffer.md`
|
|
396
|
+
001 (D), d.1145.
|
|
397
|
+
- `PublishLayer.flushBuffered(trigger)` — jediný argument je nově POVINNÝ spouštěč
|
|
398
|
+
`'reconnected'` | `'unblocked'` (rozhoduje, co se stane se záznamem, jehož rámec už odešel);
|
|
399
|
+
jiná hodnota se odmítne jménem. `InMemoryBuffer.flush(flushFn, { sentEntries })` totéž
|
|
400
|
+
(`'resend'` | `'drop'`). Uvnitř balíčku jediný volající je transport (d.1145).
|
|
401
|
+
Druhý argument `flushOptions` (slučoval se do voleb každé znovu poslané zprávy) je PRYČ:
|
|
402
|
+
nikdo ho nepředával (0 volajících v `shared/**`, `infra/**`, `api_biz/*/src`); znovu poslaná
|
|
403
|
+
zpráva nese přesně volby, se kterými ji buffer držel (d.1145).
|
|
404
|
+
- Vypršení zprávy v bufferu je `error`, ne `info`: jeden řádek na cíl (fronta nebo exchange)
|
|
405
|
+
a hodnotu `sent`, s počtem, stářím nejstarší a TTL; `sent=false` = zpráva je ZTRACENÁ,
|
|
406
|
+
`sent=true` = broker ji mohl doručit sám. Dřív „Removed N expired buffered messages“ bez
|
|
407
|
+
fronty i stáří. Rozhodnutí vlastníka `docs/governance/confirmations/mq-publish-buffer.md`
|
|
408
|
+
001 (D), d.1145.
|
|
409
|
+
- Publish, jehož potvrzení ještě čekalo, když broker zavřel spojení nebo kanál (`320
|
|
410
|
+
CONNECTION_FORCED`, restart či stop brokeru, výpadek spojení), končil `PermanentPublishError`
|
|
411
|
+
bez bufferu a bez retry — zpráva byla ztracena, ač o ní broker nic neřekl (měřeno d.1126).
|
|
412
|
+
Příčina: amqplib odpovídá nepotvrzeným publishům `Error('channel closed')` ze svého `close`
|
|
413
|
+
posluchače registrovaného v konstruktoru kanálu, tedy DŘÍV, než klient kanál označil za
|
|
414
|
+
mrtvý; callback pak četl živý kanál. Klientův `close` posluchač send kanálu se nyní
|
|
415
|
+
registruje `prependListener` a běží první, takže tahle cesta je přechodná
|
|
416
|
+
(`TransientPublishError`) a jde dnešní cestou retry/buffer, včetně `bufferOnFailure: false`
|
|
417
|
+
(`buffered: false`). `nack` brokeru a číslovaný close frame této zprávy zůstávají trvalé
|
|
418
|
+
(d.1134).
|
|
419
|
+
- Hláška `PublishError` větve fronty při zavření kanálu s čekajícím potvrzením tvrdila „and not
|
|
420
|
+
reconnecting“ — v S3 nepravda: spojení v tu chvíli ještě neohlásilo `close` a reconnect
|
|
421
|
+
následuje. Nově: „Channel closed during publish to queue "<q>" before the broker confirmed the
|
|
422
|
+
message …“, bez tvrzení o reconnectu, `Fix:` odkazuje na `error.buffered` (d.1134).
|
|
423
|
+
- Re-registrace konzumentů po obnově kanálu se ptá na živost konzumentského kanálu před KAŽDÝM
|
|
424
|
+
konzumentem (a znovu po kroku fronty), ne jednou před smyčkou. Kanál, který zemřel uprostřed
|
|
425
|
+
smyčky (zavřel ho broker, nebo ho vyřadil `brokerAnswerTimeout`, d.1135), dřív vedl
|
|
426
|
+
u dalšího konzumenta na `TypeError: Cannot read properties of null (reading 'consume')`
|
|
427
|
+
a na zbytečnou deklaraci jeho fronty. Nově `ConnectionError` „Cannot re-register the consumer
|
|
428
|
+
of <q>: consumer channel is not available …“ v `consumer:re-registration:failed`; konzument
|
|
429
|
+
zůstává sledovaný bez tagu a obnoví ho příští obnova, jako dosud (d.1138).
|
|
430
|
+
- Hláška `_waitForReconnection()` po vypršení jmenovala env `RABBITMQ_RECONNECT_WAIT_TIMEOUT`,
|
|
431
|
+
který nic nečte. Nově jmenuje, co se čte: klíč konfigurace `reconnectWaitTimeout` („set in the
|
|
432
|
+
client configuration“), nebo odvození `max(60000, maxReconnectAttempts × reconnectMaxDelay)`,
|
|
433
|
+
když klíč zadaný není; `Fix:` odkazuje na tentýž klíč. Chování beze změny (d.1138).
|
|
434
|
+
- Přechodná chyba cesty publishe (`_publishPathTransient()`) tvrdila „queue channel is not
|
|
435
|
+
available (connection may be closed)“ i při ŽIVÉM spojení blokovaném memory alarmem (d.1143,
|
|
436
|
+
nález 3; od d.1135 končí touto chybou i neodpovězená kontrola `checkQueue`). Věta se nově
|
|
437
|
+
volí ze stavu klienta: zavřené spojení → „the connection is closed“; blokované → „the broker
|
|
438
|
+
has blocked this connection (reason: …)“; jinak „the queue channel is not available on a
|
|
439
|
+
live, unblocked connection“ s příčinou v `error.cause`. Třída beze změny (d.1145).
|
|
440
|
+
|
|
441
|
+
### Fixed
|
|
442
|
+
|
|
443
|
+
- Spojení, které obnova navázala až po `disconnect()` volajícího, se zavírá v rozpočtu
|
|
444
|
+
`brokerAnswerTimeout` (`_closeWithinTeardownBudget()`, jedna kolej, conf
|
|
445
|
+
`mq-broker-answer-budget` 001). Dřív `await revived.close()` bez limitu: broker, který v tu
|
|
446
|
+
chvíli neodpovídal, držel kolo obnovy (`_reconnecting: true`) a socket otevřené navždy.
|
|
447
|
+
Neodpovězené zavření nově zahodí socket jako teardown (d.1138).
|
|
448
|
+
|
|
449
|
+
### Documentation — JSDoc a popis klíče na pravdu, docblock jedné koleje `workflow_initiated` bez smazané cesty (d.1134, d.1138, d.1176)
|
|
450
|
+
|
|
451
|
+
- JSDoc `workflowIdOf()` tvrdil, že alias `workflowId` dosud píše webhook submit gatewaye.
|
|
452
|
+
Nepravda: žádný producent v `shared/**` ani `infra/**` alias nepíše (hlavičky gatewaye nesou
|
|
453
|
+
jen `workflow_id`, `infra/api_gateway/shared/workflowMessageHeaders.js`); čtení aliasu
|
|
454
|
+
v transportu neexistuje od d.416, chování beze změny (d.1134).
|
|
455
|
+
- Popis klíče `heartbeat` v `configSchema.js` na pravdu: hodnota se navrhuje brokeru v URL
|
|
456
|
+
spojení (ne „předává se amqplibu“) a `0` heartbeat NEVYPNE — vyjednávání amqplibu vezme
|
|
457
|
+
stranu, která limit stanoví, tedy hodnotu brokera (změřeno d.1126: pauza zjištěna po 179 s).
|
|
458
|
+
Chování beze změny (d.1138).
|
|
459
|
+
- Docblock `src/monitoring-initiated.js` popisuje rozhodnutí o jedné koleji události
|
|
460
|
+
`workflow_initiated` (conf `scheduler` 009 a 024) bez cesty na smazaný soubor gatewaye
|
|
461
|
+
a bez vyprávění o kopiích, které to rozhodnutí rušilo; chování beze změny (d.1176).
|
|
462
|
+
|
|
463
|
+
### Tests — sady bez nástěnného času a s pravdivým popisem bloku (d.1174, d.1138)
|
|
464
|
+
|
|
465
|
+
- Test, ne chování: sada `connect-timeout-closes-late-handshake.test.js` čekala nástěnný čas
|
|
466
|
+
(`connectTimeout` 20 ms, pak `setTimeout` 80 ms na druhý pokus reconnectu) a pod zátěží runneru
|
|
467
|
+
`oa-staging` (pipeline 2904670335) četla `amqp.connect` 2× místo 3×, proto hodiny teď řídí
|
|
468
|
+
test (`jest.useFakeTimers()` + `advanceTimersByTimeAsync`) a výsledek nezávisí na zátěži
|
|
469
|
+
stroje (d.1174).
|
|
470
|
+
- Popis bloku testu `connection-parameters-reach-the-broker.test.js` („…one name for its two
|
|
471
|
+
consumers“) srovnán s d.1135: budget `brokerAnswerTimeout` platí pro každé čekání na odpověď
|
|
472
|
+
brokera, ne pro dva konzumenty (d.1138).
|
|
473
|
+
- Test, ne chování: akceptační sada čekací koleje `waiting-helper.test.js` měřila vzorkování
|
|
474
|
+
`waitForValue`/`waitForEvent` na nástěnných hodinách a pod loadem 17–30 (pre-K sweep
|
|
475
|
+
`release/w4`) vzorek rastru 20 ms zpožděný o víc než 40 ms minul okno 150–190 ms, takže
|
|
476
|
+
„s `pollMs: 20` tentýž stav VIDÍ" četl `true`; hodiny teď řídí test (`jest.useFakeTimers()`
|
|
477
|
+
+ `advanceTimersByTimeAsync`, čas čte `jest.now()`); hodnoty a počty v tvrzeních beze změny,
|
|
478
|
+
podlahy pauzy přesně podle pravidla, bez rezervy na plánovač; kolej `tests/waiting.js`
|
|
479
|
+
nedotčena (d.1192).
|
|
480
|
+
|
|
7
481
|
## [5.0.0] — 2026-09-27
|
|
8
482
|
|
|
9
483
|
### Removed — BREAKING — fronta `workflow.control` (d.1061)
|