2026-09-17 — Retry moves in after all
2026-09-11 examined an application's private Retry and kept it out of this package, for three
reasons. This session moved it in. Here is what changed about each reason, and the one that did not.
"It reads configuration, so it is not a utility"
Still true, and it still keeps Retry out of utils/. But utils/ was never the only place it could go. core/ is
the layer that behaves: Logger resolves logging on every line, and nobody calls Logger a capability for it.
Retry resolves delivery the same way and sits beside it.
The capability reading had the relationship backwards. A capability is something the kernel names and cannot provide
itself, such as a cache or a queue. Retrying needs no platform resource. The kernel had already declared the retry
policy, delivery.retries and delivery.retryBackoffMs, and simply had no code that read it. A policy in one package
with its only implementation somewhere else is a split worth removing.
"It depends on an HTTP client's error and on the application's own validation error"
Both couplings were real, and neither came across.
- The HTTP error is recognised by shape: an
Errorcarrying a numericstatus.@bayudwiyansatria/web-worker'sHttpErrorhas exactly that, so the judgement is unchanged for errors it raises, and the kernel still imports no HTTP library. Any client with the same shape gets the same treatment, which is what a transport-neutral default should do. - The validation error stays in the application, which passes its own
isRetryable: refuse a validation failure, and defer toRetry.isRetryablefor everything else. That was the one piece of the old judgement that knew anything about the application.
One side effect: the old implementation logged under a hard-coded event name. That became an option, event, defaulting
to 'retry'. A caller adopting this can pass its old name so nothing querying the logs goes quiet.
"It has one consumer" — still true
This is the reason that stands, and the move went ahead anyway. What outweighed it: the kernel has published a retry
setting with no reader since 1.1.0, so the next consumer to honour delivery.retries would otherwise write a second
loop, and the two private copies of chunk had disagreed before anyone noticed.
That is a judgement call, not a rule, so it is written down. If a second consumer never appears, this is the entry to revisit.
Small behaviours settled on the way
- An attempt count below one runs once, with
NaNincluded, matching howList.chunktreats an unusable size. The old code usedMath.max(attempts, 1), andMath.max(NaN, 1)isNaN: the loop never ran, the operation was never called, and the call threwundefined. deliveryis resolved only when a default is used. A call overriding bothattemptsandbackoffMsworks beforeconfigure(). The old code resolved on every call.
Consumer adoption
The consumer was switched over and verified against a packed tarball of this working tree before publication, then re-verified against the registry version once 1.3.1 was published. Both builds produced the same bundle size.