|
| 1 | +--- |
| 2 | +'@objectstack/spec': minor |
| 3 | +--- |
| 4 | + |
| 5 | +feat(spec)!: retire the connector `triggers` array — the `ConnectorTrigger` shape nothing ever registered, polled or received (#20287) |
| 6 | + |
| 7 | +**BREAKING** — `connector.triggers` (the `ConnectorTrigger` array: `key`, `label`, |
| 8 | +`description`, `type: 'polling' | 'webhook'`, `intervalSeconds`) is removed from |
| 9 | +`ConnectorSchema` and `DeclarativeConnectorEntrySchema` — so from `defineConnector`, |
| 10 | +`stack.connectors[]`, the `PUT /api/v1/meta/connector/:name` door and |
| 11 | +`AutomationEngine.registerConnector` — and the `ConnectorTriggerSchema` / |
| 12 | +`ConnectorTrigger` exports leave `@objectstack/spec/integration` with it. ADR-0049 |
| 13 | +enforce-or-remove, ruled RETIRE on the maintainer's criterion for a |
| 14 | +declared-but-unenforced family; ADR-0041 is unchanged: connector-event triggers stay |
| 15 | +in its third tier, as their own trigger package, promoted when real projects ask for |
| 16 | +them — and then in the mainstream shape (subscribe / unsubscribe lifecycle, |
| 17 | +signature verification, a dedupe cursor), which these five keys could not carry. |
| 18 | + |
| 19 | +Measured before removal: `registerConnector` walks a connector's `actions` only and |
| 20 | +stores the rest of the def unread; the engine's trigger registry holds FLOW trigger |
| 21 | +kinds (`record_change`, `time_relative`, `schedule`, `api`) and no connector trigger |
| 22 | +ever entered it; no polling loop read `intervalSeconds`; no receiver was driven by a |
| 23 | +`webhook` trigger; and no connector package, provider or example declared one. A |
| 24 | +declared trigger parsed clean and never started a flow. |
| 25 | + |
| 26 | +### FROM → TO |
| 27 | + |
| 28 | +| removed | what to write instead | |
| 29 | +| --- | --- | |
| 30 | +| `connector.triggers` with a `type: 'polling'` trigger (`intervalSeconds`, or the pre-rename `interval`) | delete the key, and write a `schedule` flow whose `connector_action` node calls the connector's action — at the cadence you meant, in seconds. | |
| 31 | +| `connector.triggers` with a `type: 'webhook'` trigger | delete the key, and write an `api` flow that the external sender calls, with a `connector_action` node calling the connector's action. It opens an inbound endpoint that never existed before: an `api` flow is refused without a per-flow secret and every call must carry its signature, so the sender must be able to sign. | |
| 32 | +| `ConnectorTriggerSchema`, `ConnectorTrigger` | no replacement — nothing parsed or constructed a connector trigger. | |
| 33 | + |
| 34 | +**The one-line fix: delete `triggers:` from every connector.** |
| 35 | +`os migrate meta --from 17` lists the mechanical edits for existing sources. |
| 36 | + |
| 37 | +⚠️ Runtime behaviour is deliberately **unchanged**: no connector trigger ever started |
| 38 | +anything. What changes is the answer an author gets — the key is refused at parse |
| 39 | +with a prescription naming the two shapes that work, and in `tsc` (its input type is |
| 40 | +`never`), instead of being saved with no effect. |
| 41 | + |
| 42 | +### The retirement kit |
| 43 | + |
| 44 | +- **Tombstone.** `triggers` is a `retiredKey()` tombstone on the private |
| 45 | + `ConnectorBaseSchema` both published carriers wrap (the schema is not `.strict()`, |
| 46 | + so a bare deletion would be a silent strip, ADR-0104). |
| 47 | + `RETIRED_KEYS_BY_MAJOR[18]`: `integration/Connector:triggers` and |
| 48 | + `integration/DeclarativeConnectorEntry:triggers`. The key had no default, so no |
| 49 | + retired-default residue is owed. |
| 50 | +- **The provider-bound refusal is gone.** `DeclarativeConnectorEntrySchema` used to |
| 51 | + refuse `triggers` on a provider-bound instance, reasoned "the provider derives them |
| 52 | + from the upstream at boot" — untrue, since no provider ever derived a trigger. The |
| 53 | + tombstone refuses every value on every carrier, so that rule became unreachable and |
| 54 | + was deleted rather than re-reasoned; a provider-bound instance now meets the |
| 55 | + retirement prescription. |
| 56 | +- **The def leaves whole** (`RETIRED_DEFS_BY_MAJOR[18]`: `integration/ConnectorTrigger`). |
| 57 | +- **D2 conversion `connector-triggers-removed`** (step 18, retired from the load path): |
| 58 | + strips the array from `connectors[]` and from stored `sys_metadata` connector rows |
| 59 | + (the rehydration seam replays it), one notice per connector, as a lossless delete. |
| 60 | + A trigger is stripped, never turned into a flow. |
| 61 | +- **The chain.** In the same step, `connector-health-and-trigger-durations-unit-in-key` |
| 62 | + renamed `triggers[].interval` to `intervalSeconds`. That trigger half is absorbed by |
| 63 | + this removal, as its breaker half already was by the `health` removal, so with neither |
| 64 | + half left the rename conversion is gone from the table and from step 18; an author |
| 65 | + holding either spelling ends with no `triggers` at all. The retired-key row |
| 66 | + `integration/ConnectorTrigger:interval` stays as the record. |
| 67 | +- **D3 entry `connector-triggers-retired`** carries the family's judgement: which |
| 68 | + triggers should exist now as flows, the cadence in seconds, and whether an external |
| 69 | + sender can sign the calls a signed `api` flow requires. The absorbed rename's own D3 entry |
| 70 | + (`connector-resilience-durations-unit-in-key`) is gone with its |
| 71 | + conversion. |
| 72 | +- **No deprecation window**, per the project's startup-stage posture. |
| 73 | + |
| 74 | +⚠️ **The out-of-repo consumer population is NOT MEASURED.** `@objectstack/spec` is |
| 75 | +published, so this is breaking for consumers no telemetry was consulted for. |
| 76 | + |
| 77 | +Clause-②: no (narrowing) |
| 78 | + |
| 79 | +<!-- adr-0087: registered connector-triggers-removed, connector-triggers-retired --> |
0 commit comments