@particle-academy/fancy-connector-core 0.2.0 → 0.3.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 CHANGED
@@ -6,6 +6,43 @@ All notable changes to `fancy-connector-core` are documented here, in
6
6
  **This package is pre-1.0, so breaking changes land in MINOR releases.** The
7
7
  version number is not a promise it can keep yet; the entries below are.
8
8
 
9
+ ## [0.3.0] - 2026-08-19
10
+
11
+ ### Fixed
12
+
13
+ - **`ECONNRESET` and `EPIPE` are `ambiguous`, not `unreachable`. This was a live
14
+ duplicate-send hazard.** Both were listed as codes proving the request never
15
+ left. They do not. A peer sends RST whenever it tears the connection down,
16
+ *including after it has received and acted on the request but before the
17
+ response comes back*; `EPIPE` is the same story from our side. Node surfaces
18
+ the safe case and the unsafe case as the same code, so the code cannot carry
19
+ the claim.
20
+
21
+ Because `shouldRetry` returns true for `unreachable` **without consulting
22
+ `idempotent`**, a reset produced up to three sends of a connector that had
23
+ explicitly declared `idempotent: false`. For a consumer publishing to social
24
+ networks that is a second public post that cannot be withdrawn.
25
+
26
+ This is the same defect as the 5xx / thrown-transport split fixed earlier, and
27
+ it contradicted this module's own stated rule -- *"an unknown failure treated
28
+ as unreachable would be retried, and the one thing worse than a lost send is
29
+ two sends nobody approved."* The two codes were standing exceptions to a rule
30
+ that should have covered them.
31
+
32
+ **What a consumer must DO:** nothing, if your connectors declare `idempotent`
33
+ honestly -- the fix only removes retries that were never safe. If you have a
34
+ connector marked `idempotent: true` that is not actually safe to repeat, this
35
+ release does not save you; that field is still load-bearing. If you were
36
+ relying on resets being retried for a genuinely idempotent connector, they
37
+ still are: `ambiguous` retries when `idempotent: true`.
38
+
39
+ `ECONNREFUSED`, `ENOTFOUND`, `EAI_AGAIN`, `EHOSTUNREACH` and `ENETUNREACH`
40
+ stay `unreachable` -- they do prove nothing was delivered -- and a test now
41
+ pins them there so the fix cannot overcorrect.
42
+
43
+ Found by a consumer's conformance fixture disagreeing with the core on its
44
+ first run, with a reproduction.
45
+
9
46
  ## [Unreleased]
10
47
 
11
48
  ## [0.2.0] — unreleased
package/dist/index.cjs CHANGED
@@ -158,16 +158,33 @@ function satisfiesMinimum(coreVersion, minimum) {
158
158
 
159
159
  // src/delivery.ts
160
160
  var UNREACHABLE_CODES = /* @__PURE__ */ new Set([
161
+ // Every code here must PROVE the request never reached an application:
162
+ // nothing listening, no route, or a name that never resolved. If a code can
163
+ // also occur after the peer received and acted on the request, it belongs in
164
+ // AMBIGUOUS_CODES -- `shouldRetry` retries `unreachable` without consulting
165
+ // `idempotent`, so a wrong entry here is a duplicate send, not a slow one.
161
166
  "ECONNREFUSED",
162
167
  "ENOTFOUND",
163
168
  "EAI_AGAIN",
164
169
  "EHOSTUNREACH",
165
170
  "ENETUNREACH",
166
- "ECONNRESET",
167
- "EPIPE",
168
171
  "ERR_SOCKET_CONNECTION_TIMEOUT"
169
172
  ]);
170
173
  var AMBIGUOUS_CODES = /* @__PURE__ */ new Set([
174
+ // A peer sends RST whenever it tears the connection down -- INCLUDING after
175
+ // it has received and acted on the request but before the response comes
176
+ // back. EPIPE is the same story from our side: we wrote to a socket the peer
177
+ // had already closed, and whether an earlier complete request was processed
178
+ // first is not knowable from here. Node surfaces the safe case and the unsafe
179
+ // case as the same code, so the code cannot carry the claim.
180
+ //
181
+ // These two sat in UNREACHABLE_CODES until 0.3.0, which made them
182
+ // unconditionally retryable and produced three sends of connectors that had
183
+ // declared `idempotent: false`. Same defect as the 5xx / thrown-transport
184
+ // split this module already fixed once, and a stated exception to a rule that
185
+ // should have covered them.
186
+ "ECONNRESET",
187
+ "EPIPE",
171
188
  "ETIMEDOUT",
172
189
  "UND_ERR_HEADERS_TIMEOUT",
173
190
  "UND_ERR_BODY_TIMEOUT",