Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions .changeset/18975-connector-retry-config-and-request-timeout.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ declared policy onto those options — one execution site, not one per connector
package.

**How the authored value gets there.** `ConnectorProviderContext` gains
`retryConfig`, `connectionTimeoutMs` and `requestTimeoutMs`, read-only and
`retryConfig` and `requestTimeoutMs`, read-only and
resolved from the entry (the automation service parses `retryConfig` so a
factory reads real values instead of re-deriving the schema's defaults), so a
custom provider that does its own I/O can honour them. The built-in HTTP
Expand Down Expand Up @@ -56,19 +56,20 @@ policy could never reach. They now go through the same wrapper as
`connector-rest` and `connector-slack`, which gives them the 30s per-attempt
timeout and bounded retry those two already had.

**⚠️ `connectionTimeoutMs` is NOT enforced, deliberately, and is the one thing
**⚠️ `connectionTimeoutMs` is NOT made live, deliberately, and is the one thing
the ruling assumed that measurement refused.** A connector's call is a WHATWG
`fetch`, whose only cancellation surface is one `AbortSignal` over the whole
operation; nothing in that interface observes the connection phase separately.
Bounding time-to-response with it would kill a slow-but-connected upstream the
author meant to allow with a large `requestTimeoutMs` — breaking the very
promise the key makes. So it is carried onto `ConnectorProviderContext` (a
custom provider on a transport that *can* separate the phases may honour it)
and left unenforced by the platform, with the reason recorded at the mapping and
in `packages/spec/liveness/connector.json`, which keeps that one row `dead`. It
is owed a second, narrower ADR-0049 decision: retire it, or re-describe it as
something the platform can enforce.
promise the key makes. So this change leaves it unenforced, with the reason
recorded at the mapping and in `packages/spec/liveness/connector.json`, whose
row for it stays `dead`. That left it owed a second, narrower ADR-0049
decision, and this same release takes it: `connector.connectionTimeoutMs` is
**retired**, and its own entry in this release says what to write instead. The
key never reaches `ConnectorProviderContext` in any release.

Nine of the ten ledger rows flip `dead` → `live` with the consumer site named.
No declaration moves: the connector schema keeps every key, every bound and
every default it had.
Nine of the ten ledger rows flip `dead` → `live` with the consumer site named;
the tenth is `connectionTimeoutMs`, above. This change itself moves no
declaration: it leaves every key, every bound and every default on the
connector schema as it found them.
53 changes: 35 additions & 18 deletions .changeset/19580-retire-connector-connection-timeout-ms.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,20 @@ The key was bounded (`min(1000).max(300000)`), defaulted (`30000`),
| removed | what to write instead |
| --- | --- |
| `connector.connectionTimeoutMs` (on `Connector` and on `DeclarativeConnectorEntry`, so `stack.connectors[]` and `PUT /meta/connector/:name`) | `requestTimeoutMs` — the deadline the platform keeps, applied as `resilientFetch`'s per-attempt timeout. For a connect-only bound, configure it at a connector provider or upstream gateway on a transport that can separate the phases. |
| `ConnectorProviderContext.connectionTimeoutMs` (handed to every `ConnectorProviderFactory`) | `ctx.requestTimeoutMs`, or the factory's own `providerConfig` where the provider owns the vocabulary. |
| `ConnectorProviderContext.connectionTimeoutMs` (handed to every `ConnectorProviderFactory` — added after `@objectstack/spec@17.4.0` and never in a release, see below) | `ctx.requestTimeoutMs`, or the factory's own `providerConfig` where the provider owns the vocabulary. |
| The `ZodObject` combinators on `ConnectorSchema` and `DeclarativeConnectorEntrySchema` — `.extend()`, `.omit()`, `.pick()`, `.partial()`, `.merge()`, `.strict()`, `.keyof()`, `.safeExtend()` | Both exports are now `z.preprocess` **pipes** (the residue stage below), so those methods no longer exist on them. **Build on the object and re-wrap:** `acceptRetiredDefaultResidue(<your extended object>, { connectionTimeoutMs: 30000 })`, the `EffectiveObjectPermissionSchema` route. ⚠️ `.superRefine()` still *exists* on a pipe but returns a schema with no read-through `shape`, so refine before wrapping, not after. Parsing, `z.input` / `z.infer`, and the read-through `.shape` are unchanged. |

**The one-line fix: delete the key** — and, for a custom provider factory, stop
reading `ctx.connectionTimeoutMs`. `os migrate meta --from 17` lists the
**The one-line fix: delete the key.** `os migrate meta --from 17` lists the
mechanical edits for existing sources; apply them by hand.

The three interface members withdrawn with it were **never in a release**:
`ConnectorProviderContext.connectionTimeoutMs`,
`RestConnectorOptions.connectionTimeoutMs` and
`OpenApiConnectorConfig.connectionTimeoutMs` all entered with `b929e0a662`,
after the `@objectstack/*@17.4.0` tag, and leave in this same release. A factory
or caller built against a released version never saw them; only code written
against an unreleased `main` in between can read them, and it stops.

⚠️ Runtime behaviour is **unchanged for every shipped provider**, because none
ever applied the value: a connector that authored `connectionTimeoutMs: 1000`
made exactly the same calls, with exactly the same deadlines, as one that did
Expand All @@ -41,7 +48,8 @@ not. What does change is observable and intended: the def served by
### ⭐ This is NOT the zero-mention retirement shape

Measured with `git grep -n connectionTimeoutMs SHA -- . ':!packages/spec'` at
`origin/main`: **thirteen** non-test source occurrences over seven files in five
`e07843b5a6`, the tree this retirement landed on: **thirteen** non-test source
occurrences over seven files in five
packages — **six reads** (`openapi-connector.ts:242`, `openapi-provider.ts:193`,
`rest-connector.ts:134`, `rest-provider.ts:64`, `plugin.ts:307`,
`plugin.ts:1589`), **four type declarations**, and **three** surviving hardcoded
Expand Down Expand Up @@ -73,7 +81,8 @@ ruling that made the siblings live forbids.)
both channels — `tsc` (input type `never`) and the parse, which raises the
prescription itself. `DeclarativeConnectorEntrySchema` carries it too — both
published carriers wrap the same private `ConnectorBaseSchema` — so
`stack.connectors[]` and the `/meta/connector` door refuse it too.
`stack.connectors[]` and the `/meta/connector` door refuse it too: every value
but the retired default `30000`, which the residue stage below strips first.
- **A D2 conversion, `connector-connection-timeout-ms-removed`** — one strip per
`connectors[]` entry, a pure lossless delete. ⭐ The ruling left whether one was
owed to be **measured** ("a D2 conversion only if a stored connector row can
Expand All @@ -85,9 +94,11 @@ ruling that made the siblings live forbids.)
written on 17.x therefore replay clean.
- **A D3 semantic entry,
`connector-provider-context-connection-timeout-ms-retired`**, for the withdrawn
`ConnectorProviderContext` member. A provider factory is code: there is no
authored source and no `sys_metadata` row for a conversion to rewrite, so the
removal reaches a factory author as a `tsc` error and as that entry.
`ConnectorProviderContext` member (never in a release, above). A provider
factory is code: there is no authored source and no `sys_metadata` row for a
conversion to rewrite, so the removal reaches a factory author who read it —
possible only against an unreleased `main` — as a `tsc` error and as that
entry.
- **No def leaves.** The key was a bare `z.number()`, never a `ConfigSchema`
shape, so `RETIRED_DEFS_BY_MAJOR[18]` gains nothing — and `api-surface/` and
`json-schema.manifest/` are byte-identical, which is the correct reading for a
Expand All @@ -103,19 +114,26 @@ ruling that made the siblings live forbids.)
carries both readings with their trees rather than one undated claim.
- **An `acceptRetiredDefaultResidue` stage** (#12840), `{ connectionTimeoutMs: 30000 }`
on both carriers. The key was `.optional().default(30000)`, so a 17.x parse
materialized it into **every** connector — measured across two builds: the base
build emits it for an entry that authored only `name`/`label`/`type`, and the
tombstoned build refuses that exact object at `connectors.0.connectionTimeoutMs`.
materialized it into **every** connector — measured on both sides of the
retirement: the released
`@objectstack/spec@17.4.0` emits `connectionTimeoutMs: 30000` for an entry that
authored only `name`/`label`/`type`, and the tombstone **without the stage**
refuses that exact object at `connectionTimeoutMs`. With the stage, as it
ships, that object is **accepted and the key stripped** before the tombstone
reads it — on `ConnectorSchema`, `DeclarativeConnectorEntrySchema`, the
`/meta/connector` schema and `stack.connectors[]` alike.
The D2 does **not** discharge the obligation, and the precedent shows it:
`ObjectPermission:allowPurge` carries a D2 **and** the residue stage, for its
own reason (a released toolchain materialized its default into every built
artifact's entries). The reason *here* is a different one — this schema has a
second door: `AutomationEngine.registerConnector` parses `ConnectorSchema` for
a def a plugin or provider factory builds **in code**, where no conversion
ever runs, and all four shipped connector packages put the materialized value
straight into that def literal. So the emitted `30000`
is accepted-and-stripped while `15000` keeps the tombstone's refusal, and
nothing is un-retired: `z.input` stays `never` and the `[RETIRED]` row stays.
ever runs, and in 17.4.0 all four shipped connector packages put that `30000`
straight into the def literal. So the emitted `30000` is accepted-and-stripped,
while every other value (`15000`, `1000`, the string `"30000"`) keeps the
tombstone's refusal — at `connectionTimeoutMs`, or at
`connectors.0.connectionTimeoutMs` inside a stack — and nothing is un-retired:
`z.input` stays `never` and the `[RETIRED]` row stays.
- **No deprecation window** (maintainer 2026-08-27: 「项目在创业阶段,用户也很少,短期不考虑渐进」),
and no staged retirement.

Expand All @@ -126,8 +144,7 @@ occurrences of the name at objectui `87af769e`, against a lit control on the sam
command and scope, so no sibling fix or pin bump rides with this.

`Clause-②: yes (narrowing)` — a published authorable key is removed on two
carriers and a published interface member leaves `ConnectorProviderContext`, so
the accept set a consumer writes against narrows. Nothing is widened and nothing
is renamed. Contract-review tier.
carriers, so the accept set a consumer writes against narrows. Nothing is
widened and nothing is renamed. Contract-review tier.

<!-- adr-0087: registered connector-connection-timeout-ms-removed, connector-provider-context-connection-timeout-ms-retired -->
Loading