@particle-academy/fancy-connector-core 0.0.0 → 0.2.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
@@ -1,103 +1,179 @@
1
- # Changelog
2
-
3
- All notable changes to `fancy-connector-core` are documented here, in
4
- [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format.
5
-
6
- **This package is pre-1.0, so breaking changes land in MINOR releases.** The
7
- version number is not a promise it can keep yet; the entries below are.
8
-
9
- ## [Unreleased]
10
-
11
- ## [0.1.0] — unreleased
12
-
13
- First cut. Extracted and generalised from the `_connector` shared runtime in the
14
- fancy-flow node marketplace, then rebuilt around the requirements in The Ripple
15
- Effect's packager brief, which came from five adapters dry-verified against real
16
- APIs.
17
-
18
- The connector **catalogue** lives in `fancy-connectors` and releases on its own
19
- clock; this package is the runtime it is written on.
20
-
21
- ### Added
22
-
23
- - **Core runtime, TypeScript and PHP, with zero runtime dependencies in either
24
- ecosystem.** WebCrypto and `fetch` on Node; `hash()` and cURL behind a
25
- swappable `Transport` in PHP.
26
- - **`delivery` — a four-way failure classification.** `unreachable` /
27
- `refused-explicitly` / `ambiguous` / `rejected`, with retryability a function
28
- of the kind **and** the connector's declared idempotency. An unrecognised
29
- error falls to `ambiguous`, never to safe.
30
- - **`DeliveryDeclaration`** — `idempotent` + a cited `why` + `minIntervalMs` +
31
- `rateSource` (`documented` | `self-imposed`) + a dated `Citation`.
32
- - **`seam` — `ProviderAdapter` and `Connector`**, deliberately separate, plus
33
- the written list of everything the package never owns: approval, liveness, the
34
- approved-bytes comparison, consent, second review, journals, credential
35
- storage.
36
- - **`text`** — `measure(text, unit)` over graphemes / characters / UTF-8 bytes,
37
- and a `ByteRange` type producible only by a function that encoded the string.
38
- - **`render`** — pure, versioned (version **inside** the payload hash), loss
39
- reported rather than applied, driven by a declarative `RenderRules` so most
40
- providers need no rendering code at all.
41
- - **`chain`** — root fixed, parent advancing, with the post function as an
42
- argument so threading is testable without a live session.
43
- - **`metrics`** — declared metric shapes, `compareShape()`,
44
- `capabilityProblems()`, and `reported()` so *absent stays absent* is a
45
- property of the code.
46
- - **`probe`** — dry-verify against the real API with a deliberately invalid
47
- credential, per-provider `authStatuses`, a 2xx counted as a failure, and
48
- offline reported as **skipped** rather than failed.
49
- - **`drift`** — API-drift *reporting*: a narrow projection of an OpenAPI
50
- document against the operations a connector actually calls, a recorded-shape
51
- fallback for the majority of providers that publish nothing, and `unchecked`
52
- as a first-class outcome that is never collapsed into `clean`.
53
- - **Explicit-credentials resolution.** Credentials passed straight in bypass the
54
- connection registry entirely; an incomplete set is a loud failure rather than
55
- a silent fall-through.
56
- - **`CONNECTOR_API_VERSION` and `assertConnectorApi`** (`ConnectorApi` in PHP) —
57
- the compatibility number that makes a separate release clock safe for a
58
- catalogue whose connectors ship as VENDORED source. It moves only when the
59
- surface a connector is written against moves, the window is the current
60
- version and the one before, and a mismatch names which side is behind rather
61
- than reporting "incompatible".
62
-
63
- #### Two things the PHP core does differently, and why
64
-
65
- - **A custom `Transport` must throw `TransportException`** — via
66
- `unreachable()`, `ambiguous()` or `fromCurlErrno()` — to get the *always safe
67
- to retry* case. PHP has no error-code vocabulary the way Node does, and
68
- matching on message text is not an option: it is localised and changes between
69
- HTTP-client versions. **What a consumer must do:** nothing if you use the
70
- bundled `CurlTransport`. If you bind your own, throw `TransportException`
71
- rather than a bare exception — a bare one is classified `Ambiguous`, which is
72
- conservative and correct but gives up the retry a refused connection deserves.
73
- - **`RunIdentity` is bridged rather than implemented.** PHP interfaces are
74
- nominal, so `FancyFlow\Runtime\RunIdentity` — which matches the shape exactly —
75
- is not an instance of ours. `ForeignRunIdentity::adapt()` wraps any object
76
- carrying the five members, and every `Idempotency` entry point runs its
77
- argument through it. **What a consumer must do: nothing.** Pass `$ctx->run`
78
- straight in, exactly as the TypeScript side does.
79
-
80
- ### Fixed
81
-
82
- - **An ambiguous failure was retried on a non-idempotent connector.** The
83
- runtime this was extracted from classified a thrown transport as
84
- `ConnectorTransient` with `retryable = true` and retried it twice. A thrown
85
- transport includes a timeout, which may be a request the provider already
86
- acted on — so any connector without an idempotency key could be retried into a
87
- silent double write. **What a consumer must do: nothing, unless you relied on
88
- that behaviour.** A connector that genuinely is safe to repeat now says so
89
- with `idempotent: true` and keeps its retries; everything else becomes
90
- conservative, which is the correct direction.
91
- - **The sentence splitter broke URLs.** `splitToFit` treated every `.` as a
92
- sentence terminator, so `https://example.test/x` was a candidate break point
93
- and a link could land across two messages at a low limit. A terminator now
94
- only ends a sentence when whitespace or the end follows it. **What a consumer
95
- must do: nothing** — but any payload hash computed under the old splitting
96
- will differ, so an approval recorded against it will correctly refuse.
97
- - **The core no longer imports a workflow engine.** The run identity used for
98
- idempotency keys is declared structurally, so `fancy-flow`'s `RunIdentity`
99
- satisfies it with no dependency in either direction and a host that has never
100
- heard of a workflow engine can implement it.
101
-
102
- [Unreleased]: https://github.com/Particle-Academy/fancy-connector-core/compare/v0.1.0...HEAD
103
- [0.1.0]: https://github.com/Particle-Academy/fancy-connector-core/releases/tag/v0.1.0
1
+ # Changelog
2
+
3
+ All notable changes to `fancy-connector-core` are documented here, in
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format.
5
+
6
+ **This package is pre-1.0, so breaking changes land in MINOR releases.** The
7
+ version number is not a promise it can keep yet; the entries below are.
8
+
9
+ ## [Unreleased]
10
+
11
+ ## [0.2.0] — unreleased
12
+
13
+ The reference consumer migrated five adapters onto `0.1.0`, ran its acceptance
14
+ suite, and filed four findings. All four were cases where the type or the
15
+ classifier was *safe* and still *wrong* — the hardest kind to notice from inside,
16
+ because nothing fails.
17
+
18
+ ### Fixed
19
+
20
+ - **`classifyError` ignored the classification `httpFailure` attached.** It
21
+ re-derived from scratch, found no error code, and answered `ambiguous`
22
+ ("nobody can tell") for a `400` the provider had been explicit about.
23
+ **This was never a retry bug** — `deliver()` reads `error.classified` first and
24
+ only falls back — but a host that calls the exported classifier directly, which
25
+ is a reasonable thing to do, was told nobody could tell. The **PHP twin already
26
+ did this**, so the two runtimes disagreed. **What a consumer must do: nothing,
27
+ unless you asserted the old answer.** If you pinned `classifyError` on an
28
+ `httpFailure` to `ambiguous`, that assertion now reads `rejected` or
29
+ `refused-explicitly` — which is the answer you wanted.
30
+
31
+ ### Changed
32
+
33
+ - **BREAKING (types only): `ChainRef` is `object`, was
34
+ `Record<string, string | number>`.** An ordinary interface does not satisfy an
35
+ index signature unless it declares one, so `interface PostRef { uri; cid }` did
36
+ **not** satisfy the old constraint — and the only other fix was to add an index
37
+ signature to a type used across a codebase in order to please a generic used in
38
+ one place. The reference consumer worked around it with `postChain<T & ChainRef>`
39
+ and two `as unknown as` casts, and could not use `ChainOutcome<T>` in its own
40
+ signatures at all. **What a consumer must do: delete the casts.** A constraint
41
+ everyone casts past is not enforcing anything.
42
+
43
+ ### Added
44
+
45
+ - **`SandboxKind` gained `restricted-reach`** — the fifth shape, and the
46
+ dangerous one. A Meta app in Development Mode, an unaudited TikTok app that can
47
+ only post `SELF_ONLY`: same credentials, same endpoints, same estate, and only
48
+ the **audience** restricted. That is none of the previous four, and `none` —
49
+ which is what it was being forced into — is actively wrong. It is the shape
50
+ most likely to be mistaken for a sandbox, because it looks exactly like a
51
+ successful post that nobody can see: `ok: true`, a real id, no audience.
52
+ - **`SandboxKind` gained `unverified`** — *nobody has checked yet*. A real state,
53
+ and the type has to carry it: this is the field where being wrong sends someone
54
+ to a live estate believing it is a test one, so "researched but not verified"
55
+ must not be forced to pick one of the other five. A comment saying so is not a
56
+ type.
57
+ - **`sandboxIsSelectable()` / `sandboxRefusal()`** (`SandboxKind::isSelectable()`
58
+ / `->refusal()` in PHP). Auto-resolution no longer picks "sandbox" for anything
59
+ that cannot be selected, and each refusal explains **its own** reason —
60
+ collapsing the three into "no sandbox available" would hide the one that
61
+ matters.
62
+ - **`providerProblems()`** — the sibling of `capabilityProblems()`, one level up.
63
+ Reports `implemented: true` with `sandbox: "unverified"`, a `verify` that does
64
+ not say what it **proves**, a `restricted-reach` provider whose summary never
65
+ mentions reach, and empty fields or setup on something claimed to be
66
+ implemented.
67
+ - **`ProviderAdapter.proves`** — what a verify does NOT cover, declared on the
68
+ adapter as well as on each result, so a surface can say it *before* anyone runs
69
+ the check.
70
+ - **`withResolvedLimit(rules, limit)`** — the supported way to resolve a
71
+ per-connection limit. A Mastodon instance publishes its own `max_toot_chars`;
72
+ baking a limit into a connector renders to 500 on an instance allowing 5000, or
73
+ to 5000 on one allowing 500, and both fail silently. `undefined` keeps the
74
+ declared limit, because *"I did not look"* is not *"there is no limit"* — and
75
+ `null` is a real answer meaning **uncounted**.
76
+ - **`CREDENTIAL_SCOPES`, `SANDBOX_KINDS`, `PROBLEM_SEVERITIES`,
77
+ `CANONICAL_METRICS`** — every string union that crosses a JSON boundary now
78
+ ships as **data** as well as a type. The compiler cannot follow a type across
79
+ JSON: when `scope` was renamed from `app`/`brand`, a consumer re-declaring the
80
+ field shape on its client kept compiling and its `scope === "brand"` silently
81
+ became never-true, so every credential field would have rendered as shared.
82
+ (PHP had this for free — an enum is runtime data, and `tryFrom` fails loudly on
83
+ a value nobody serves any more.)
84
+
85
+
86
+ ## [0.1.0] — unreleased
87
+
88
+ First cut. Extracted and generalised from the `_connector` shared runtime in the
89
+ fancy-flow node marketplace, then rebuilt around the requirements in The Ripple
90
+ Effect's packager brief, which came from five adapters dry-verified against real
91
+ APIs.
92
+
93
+ The connector **catalogue** lives in `fancy-connectors` and releases on its own
94
+ clock; this package is the runtime it is written on.
95
+
96
+ ### Added
97
+
98
+ - **Core runtime, TypeScript and PHP, with zero runtime dependencies in either
99
+ ecosystem.** WebCrypto and `fetch` on Node; `hash()` and cURL behind a
100
+ swappable `Transport` in PHP.
101
+ - **`delivery` — a four-way failure classification.** `unreachable` /
102
+ `refused-explicitly` / `ambiguous` / `rejected`, with retryability a function
103
+ of the kind **and** the connector's declared idempotency. An unrecognised
104
+ error falls to `ambiguous`, never to safe.
105
+ - **`DeliveryDeclaration`** — `idempotent` + a cited `why` + `minIntervalMs` +
106
+ `rateSource` (`documented` | `self-imposed`) + a dated `Citation`.
107
+ - **`seam` — `ProviderAdapter` and `Connector`**, deliberately separate, plus
108
+ the written list of everything the package never owns: approval, liveness, the
109
+ approved-bytes comparison, consent, second review, journals, credential
110
+ storage.
111
+ - **`text`** — `measure(text, unit)` over graphemes / characters / UTF-8 bytes,
112
+ and a `ByteRange` type producible only by a function that encoded the string.
113
+ - **`render`** — pure, versioned (version **inside** the payload hash), loss
114
+ reported rather than applied, driven by a declarative `RenderRules` so most
115
+ providers need no rendering code at all.
116
+ - **`chain`** — root fixed, parent advancing, with the post function as an
117
+ argument so threading is testable without a live session.
118
+ - **`metrics`** — declared metric shapes, `compareShape()`,
119
+ `capabilityProblems()`, and `reported()` so *absent stays absent* is a
120
+ property of the code.
121
+ - **`probe`** — dry-verify against the real API with a deliberately invalid
122
+ credential, per-provider `authStatuses`, a 2xx counted as a failure, and
123
+ offline reported as **skipped** rather than failed.
124
+ - **`drift`** — API-drift *reporting*: a narrow projection of an OpenAPI
125
+ document against the operations a connector actually calls, a recorded-shape
126
+ fallback for the majority of providers that publish nothing, and `unchecked`
127
+ as a first-class outcome that is never collapsed into `clean`.
128
+ - **Explicit-credentials resolution.** Credentials passed straight in bypass the
129
+ connection registry entirely; an incomplete set is a loud failure rather than
130
+ a silent fall-through.
131
+ - **`CONNECTOR_API_VERSION` and `assertConnectorApi`** (`ConnectorApi` in PHP) —
132
+ the compatibility number that makes a separate release clock safe for a
133
+ catalogue whose connectors ship as VENDORED source. It moves only when the
134
+ surface a connector is written against moves, the window is the current
135
+ version and the one before, and a mismatch names which side is behind rather
136
+ than reporting "incompatible".
137
+
138
+ #### Two things the PHP core does differently, and why
139
+
140
+ - **A custom `Transport` must throw `TransportException`** — via
141
+ `unreachable()`, `ambiguous()` or `fromCurlErrno()` — to get the *always safe
142
+ to retry* case. PHP has no error-code vocabulary the way Node does, and
143
+ matching on message text is not an option: it is localised and changes between
144
+ HTTP-client versions. **What a consumer must do:** nothing if you use the
145
+ bundled `CurlTransport`. If you bind your own, throw `TransportException`
146
+ rather than a bare exception — a bare one is classified `Ambiguous`, which is
147
+ conservative and correct but gives up the retry a refused connection deserves.
148
+ - **`RunIdentity` is bridged rather than implemented.** PHP interfaces are
149
+ nominal, so `FancyFlow\Runtime\RunIdentity` — which matches the shape exactly —
150
+ is not an instance of ours. `ForeignRunIdentity::adapt()` wraps any object
151
+ carrying the five members, and every `Idempotency` entry point runs its
152
+ argument through it. **What a consumer must do: nothing.** Pass `$ctx->run`
153
+ straight in, exactly as the TypeScript side does.
154
+
155
+ ### Fixed
156
+
157
+ - **An ambiguous failure was retried on a non-idempotent connector.** The
158
+ runtime this was extracted from classified a thrown transport as
159
+ `ConnectorTransient` with `retryable = true` and retried it twice. A thrown
160
+ transport includes a timeout, which may be a request the provider already
161
+ acted on — so any connector without an idempotency key could be retried into a
162
+ silent double write. **What a consumer must do: nothing, unless you relied on
163
+ that behaviour.** A connector that genuinely is safe to repeat now says so
164
+ with `idempotent: true` and keeps its retries; everything else becomes
165
+ conservative, which is the correct direction.
166
+ - **The sentence splitter broke URLs.** `splitToFit` treated every `.` as a
167
+ sentence terminator, so `https://example.test/x` was a candidate break point
168
+ and a link could land across two messages at a low limit. A terminator now
169
+ only ends a sentence when whitespace or the end follows it. **What a consumer
170
+ must do: nothing** — but any payload hash computed under the old splitting
171
+ will differ, so an approval recorded against it will correctly refuse.
172
+ - **The core no longer imports a workflow engine.** The run identity used for
173
+ idempotency keys is declared structurally, so `fancy-flow`'s `RunIdentity`
174
+ satisfies it with no dependency in either direction and a host that has never
175
+ heard of a workflow engine can implement it.
176
+
177
+ [Unreleased]: https://github.com/Particle-Academy/fancy-connector-core/compare/v0.2.0...HEAD
178
+ [0.2.0]: https://github.com/Particle-Academy/fancy-connector-core/compare/v0.1.0...v0.2.0
179
+ [0.1.0]: https://github.com/Particle-Academy/fancy-connector-core/releases/tag/v0.1.0
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Particle Academy
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Particle Academy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.