|
| 1 | +--- |
| 2 | +'@objectstack/spec': minor |
| 3 | +'@objectstack/connector-rest': patch |
| 4 | +'@objectstack/connector-openapi': patch |
| 5 | +'@objectstack/connector-mcp': patch |
| 6 | +'@objectstack/connector-slack': patch |
| 7 | +'@objectstack/service-automation': patch |
| 8 | +--- |
| 9 | + |
| 10 | +feat(spec)!: retire `connector.connectionTimeoutMs` — declared, bounded, defaulted, served back, and never applied as a deadline |
| 11 | + |
| 12 | +**BREAKING** — `connector.connectionTimeoutMs` is removed. ADR-0049 |
| 13 | +enforce-or-remove; maintainer ruling 2026-09-22, letter A. It is the narrower |
| 14 | +**second** decision this key was owed: the earlier ruling that made its nine |
| 15 | +liveness siblings live (`retryConfig.*`, `requestTimeoutMs`) left this one dead |
| 16 | +on a stated reason rather than by oversight, and `packages/spec/liveness/connector.json` |
| 17 | +has been asking for this decision since. |
| 18 | + |
| 19 | +The key was bounded (`min(1000).max(300000)`), defaulted (`30000`), |
| 20 | +`.describe()`d, authorable on both carriers and served back by |
| 21 | +`/meta/connector`. Every signal an authoring surface can give said it worked. |
| 22 | + |
| 23 | +### FROM → TO |
| 24 | + |
| 25 | +| removed | what to write instead | |
| 26 | +| --- | --- | |
| 27 | +| `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. | |
| 28 | +| `ConnectorProviderContext.connectionTimeoutMs` (handed to every `ConnectorProviderFactory`) | `ctx.requestTimeoutMs`, or the factory's own `providerConfig` where the provider owns the vocabulary. | |
| 29 | +| 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. | |
| 30 | + |
| 31 | +**The one-line fix: delete the key** — and, for a custom provider factory, stop |
| 32 | +reading `ctx.connectionTimeoutMs`. `os migrate meta --from 17` lists the |
| 33 | +mechanical edits for existing sources; apply them by hand. |
| 34 | + |
| 35 | +⚠️ Runtime behaviour is **unchanged for every shipped provider**, because none |
| 36 | +ever applied the value: a connector that authored `connectionTimeoutMs: 1000` |
| 37 | +made exactly the same calls, with exactly the same deadlines, as one that did |
| 38 | +not. What does change is observable and intended: the def served by |
| 39 | +`GET /connectors` no longer echoes a connect deadline nobody keeps. |
| 40 | + |
| 41 | +### ⭐ This is NOT the zero-mention retirement shape |
| 42 | + |
| 43 | +Measured with `git grep -n connectionTimeoutMs SHA -- . ':!packages/spec'` at |
| 44 | +`origin/main`: **thirteen** non-test source occurrences over seven files in five |
| 45 | +packages — **six reads** (`openapi-connector.ts:242`, `openapi-provider.ts:193`, |
| 46 | +`rest-connector.ts:134`, `rest-provider.ts:64`, `plugin.ts:307`, |
| 47 | +`plugin.ts:1589`), **four type declarations**, and **three** surviving hardcoded |
| 48 | +`30000` writes. Reading the retirement as "nothing referenced it" loses the |
| 49 | +finding. Measured across all six reads, every one is a **pass-through**: the |
| 50 | +value's only termini were the def `GET /connectors` echoes and the fingerprint |
| 51 | +that decides whether to re-materialize. `connectorFetchOptions()` — the one |
| 52 | +mapping from authored policy onto the platform's outbound `fetch` — was handed |
| 53 | +`{ retryConfig, requestTimeoutMs }` only. Carrying a number is not honouring it, |
| 54 | +and ADR-0049 forbids the parsed-unmarked-unenforced state whether the inert |
| 55 | +value travels or sits still. |
| 56 | + |
| 57 | +Nor was the `实现` arm available. A connector's outbound call is a WHATWG |
| 58 | +`fetch`, whose only cancellation surface is ONE `AbortSignal` covering the whole |
| 59 | +operation; nothing in that interface observes the connection phase. Bounding |
| 60 | +"time until the response arrives" with this key would kill a slow-but-connected |
| 61 | +upstream the author meant to allow with a large `requestTimeoutMs` — breaking |
| 62 | +the very promise the key makes. (undici's `connectTimeout` needs a custom |
| 63 | +dispatcher: Node-only, and a new subsystem underneath every connector, which the |
| 64 | +ruling that made the siblings live forbids.) |
| 65 | + |
| 66 | +### The retirement kit |
| 67 | + |
| 68 | +- The **authorable key** is a `retiredKey()` tombstone on `ConnectorSchema`, |
| 69 | + registered as `integration/Connector:connectionTimeoutMs` and |
| 70 | + `integration/DeclarativeConnectorEntry:connectionTimeoutMs` in |
| 71 | + `RETIRED_KEYS_BY_MAJOR[18]`. The schema is not `.strict()`, so a bare deletion |
| 72 | + would strip an authored key in silence (ADR-0104): the tombstone is audible in |
| 73 | + both channels — `tsc` (input type `never`) and the parse, which raises the |
| 74 | + prescription itself. `DeclarativeConnectorEntrySchema` carries it too — both |
| 75 | + published carriers wrap the same private `ConnectorBaseSchema` — so |
| 76 | + `stack.connectors[]` and the `/meta/connector` door refuse it too. |
| 77 | +- **A D2 conversion, `connector-connection-timeout-ms-removed`** — one strip per |
| 78 | + `connectors[]` entry, a pure lossless delete. ⭐ The ruling left whether one was |
| 79 | + owed to be **measured** ("a D2 conversion only if a stored connector row can |
| 80 | + carry the key"). It can, and both legs were measured before the tombstone |
| 81 | + landed: `getMetadataTypeSchema('connector')` — what `PUT /meta/connector/:name` |
| 82 | + validates against — parsed a body carrying the key and its output **retained** |
| 83 | + the authored value, so the number reached `sys_metadata`; and |
| 84 | + `applyConversionsToStoredItem('connector', …)` is live for this type. Rows |
| 85 | + written on 17.x therefore replay clean. |
| 86 | +- **A D3 semantic entry, |
| 87 | + `connector-provider-context-connection-timeout-ms-retired`**, for the withdrawn |
| 88 | + `ConnectorProviderContext` member. A provider factory is code: there is no |
| 89 | + authored source and no `sys_metadata` row for a conversion to rewrite, so the |
| 90 | + removal reaches a factory author as a `tsc` error and as that entry. |
| 91 | +- **No def leaves.** The key was a bare `z.number()`, never a `ConfigSchema` |
| 92 | + shape, so `RETIRED_DEFS_BY_MAJOR[18]` gains nothing — and `api-surface/` and |
| 93 | + `json-schema.manifest/` are byte-identical, which is the correct reading for a |
| 94 | + key-only tombstone rather than a missed regeneration. |
| 95 | +- `authorable-surface/integration.json` gains two `[RETIRED]` rows; |
| 96 | + `authorable-defaults/integration.json` loses the two `= 30000` rows. |
| 97 | +- The liveness row **stays** `dead` with a `REMOVED` note, because `retiredKey()` |
| 98 | + keeps the key in the walked shape. Its previous note claimed "every occurrence |
| 99 | + outside `packages/spec` is a WRITE". That reading was **correct at the SHA the |
| 100 | + card cited and dated** (`0870fb5418` — exactly five non-spec source hits, all |
| 101 | + five `connectionTimeoutMs: 30000,`) and was superseded by `b929e0a662`, the PR |
| 102 | + the card itself flagged as pending. It is **stale, not false**, and the row now |
| 103 | + carries both readings with their trees rather than one undated claim. |
| 104 | +- **An `acceptRetiredDefaultResidue` stage** (#12840), `{ connectionTimeoutMs: 30000 }` |
| 105 | + on both carriers. The key was `.optional().default(30000)`, so a 17.x parse |
| 106 | + materialized it into **every** connector — measured across two builds: the base |
| 107 | + build emits it for an entry that authored only `name`/`label`/`type`, and the |
| 108 | + tombstoned build refuses that exact object at `connectors.0.connectionTimeoutMs`. |
| 109 | + The D2 does **not** discharge the obligation, and the precedent shows it: |
| 110 | + `ObjectPermission:allowPurge` carries a D2 **and** the residue stage, for its |
| 111 | + own reason (a released toolchain materialized its default into every built |
| 112 | + artifact's entries). The reason *here* is a different one — this schema has a |
| 113 | + second door: `AutomationEngine.registerConnector` parses `ConnectorSchema` for |
| 114 | + a def a plugin or provider factory builds **in code**, where no conversion |
| 115 | + ever runs, and all four shipped connector packages put the materialized value |
| 116 | + straight into that def literal. So the emitted `30000` |
| 117 | + is accepted-and-stripped while `15000` keeps the tombstone's refusal, and |
| 118 | + nothing is un-retired: `z.input` stays `never` and the `[RETIRED]` row stays. |
| 119 | +- **No deprecation window** (maintainer 2026-08-27: 「项目在创业阶段,用户也很少,短期不考虑渐进」), |
| 120 | + and no staged retirement. |
| 121 | + |
| 122 | +⚠️ **The out-of-repo consumer population is NOT MEASURED.** `@objectstack/spec` |
| 123 | +is published, so this is breaking for consumers no download, dependent or source |
| 124 | +telemetry was consulted for. The pinned sibling checkout **was** measured: zero |
| 125 | +occurrences of the name at objectui `87af769e`, against a lit control on the same |
| 126 | +command and scope, so no sibling fix or pin bump rides with this. |
| 127 | + |
| 128 | +`Clause-②: yes (narrowing)` — a published authorable key is removed on two |
| 129 | +carriers and a published interface member leaves `ConnectorProviderContext`, so |
| 130 | +the accept set a consumer writes against narrows. Nothing is widened and nothing |
| 131 | +is renamed. Contract-review tier. |
| 132 | + |
| 133 | +<!-- adr-0087: registered connector-connection-timeout-ms-removed, connector-provider-context-connection-timeout-ms-retired --> |
0 commit comments