You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 6663a66
Browse filesBrowse the repository at this point in the historyBrowse files
fix(spec): the retired connector.webhooks refusal prescribes the declared-webhook model (#27499)
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwB5CGwFpmZc621vVaShTPStz)_Fixes#27448
Clause-②: no
Metadata-protocol: none
**Prose only.** The refusal, its code, its path and the accept set do
not move. Measured on the compile repro below: before and after,
`objectstack compile` exits 2 with one issue, `code: invalid_type`,
`expected: never`, `path: ["webhooks"]`. Only the `message` text
changes.
## 维护者速读
- **改了什么**:连接器上已退役的 `webhooks` 键被拒收时,处方原说「顶层 `webhooks:` 会物化进
`sys_webhook`」,现改为 ADR-0131 D2 的真实模型:平台从元数据注册表读取声明的 webhook 并在记录事件上投递,不写
`sys_webhook` 行。
- **为什么改**:作者照旧文案去找一条永远不会出现的 `sys_webhook` 行;实测声明的 webhook 写入 0 行。
- **风险与回滚**:纯文案,拒收本身、错误码、路径与可接受集不变;回滚即还原本 PR。
- **先看**:`packages/spec/src/integration/connector.zod.ts` 的
`WEBHOOKS_RETIRED`;`packages/spec/src/migrations/entries/semantic/18.connector-resilience-keys-retired.ts`
的 `replacement` / `reason`;新 pin 在
`connector-resilience-keys-retirement.test.ts`。
- **两分钟验证**:`pnpm --filter @objectstack/spec exec vitest run
src/integration/connector-resilience-keys-retirement.test.ts`,预期 22/22
通过,其中一条断言新处方含「reads a declared webhook from the metadata
registry」且不含「materialized」。
- **需要你做的**:无,席位落地。
## What changes
- **The refusal.** `WEBHOOKS_RETIRED` (the `connector.webhooks`
tombstone's prescription) now says the stack's top-level `webhooks:`
collection is read from the metadata registry and delivered on record
events, writes no `sys_webhook` row, and that `sys_webhook` holds only
the webhooks an organization authors at runtime. It also drops "never
materialized into `sys_webhook`" from the account of why a nested
webhook never ran: the dispatcher never read it.
- **The D3 entry** `connector-resilience-keys-retired`: `replacement`
says the top-level collection is read from the metadata registry and
writes no `sys_webhook` row; `reason` says a nested webhook was never
registered as a `webhook` item, read by the webhook dispatcher or
delivered (was "registered, materialized or delivered").
- **The protocol-18 rationale fragment** for the same family in
`migrations/registry.ts.template` (the upgrade text `os migrate` and the
generated upgrade guide project): the same correction.
- **Generated reference.**
`content/docs/references/integration/connector.mdx`, both `webhooks`
rows, regenerated by `gen:docs` from the new `.describe()` (never by
hand).
- **Comments carrying the same sentence:** the `webhooks` JSDoc on
`ConnectorBaseSchema` (ships in `dist/*.d.ts`), two section comments in
`connector.zod.ts`, the conversion docblock in
`conversions/registry.ts`, and the retired-key and retired-def entry
files for this retirement.
- **The liveness proof summary.**
`packages/spec/scripts/liveness/proof-registry.mts`, row
`webhook-materialization`: the summary said "a stack/connector-authored
webhook is materialized into a dispatchable sys_webhook row on boot"; it
now states what its proof
(`packages/qa/dogfood/test/webhook-materialization.dogfood.test.ts`)
pins: a webhook declared in the top-level `webhooks:` collection is
dispatched from the metadata registry and writes no sys_webhook row,
through the `declaredWebhooks` reader the plugin binds on the data
engine alone, with `object`→`object_name` applied, on a boot without
realtime. The comment beside its bindings says the same. `id`, `label`,
`proofId`, `proofRef`, `bound` and `ledgerBindings` are unchanged.
- A `patch` changeset for `@objectstack/spec`.
Not touched: released `CHANGELOG.md` entries and
`content/docs/releases/**` (release-owned). No hit in `skills/**` or
`docs/adr/**` for the old sentence or its kin.
### The message, before and after
<details><summary>Before (origin/main 273137d)</summary>
```text
`connector.webhooks` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — a webhook nested inside a connector was never registered as a `webhook` item, so it was never materialized into `sys_webhook` and never delivered, and nothing emits the connector events its `events` list could name (`sync.completed`, `auth.expired` and the rest). Delete the key; the nested shape leaves with it (`WebhookConfig`, `WebhookEvent`, `WebhookSignatureAlgorithm`). To have a webhook actually sent, declare it in the stack's top-level `webhooks:` collection, which is materialized into `sys_webhook` and delivered on record events — note that doing so STARTS deliveries this connector never made. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand.
```
</details>
<details><summary>After (this branch)</summary>
```text
`connector.webhooks` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — a webhook nested inside a connector was never registered as a `webhook` item, so the webhook dispatcher never read it and never delivered it, and nothing emits the connector events its `events` list could name (`sync.completed`, `auth.expired` and the rest). Delete the key; the nested shape leaves with it (`WebhookConfig`, `WebhookEvent`, `WebhookSignatureAlgorithm`). To have a webhook actually sent, declare it in the stack's top-level `webhooks:` collection. The platform reads a declared webhook from the metadata registry and delivers it on record events; it writes no `sys_webhook` row for it (`sys_webhook` holds only the webhooks an organization authors at runtime). Note that declaring it STARTS deliveries this connector never made. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand.
```
</details>
## Tests
- New pin in
`packages/spec/src/integration/connector-resilience-keys-retirement.test.ts`:
the `webhooks` refusal contains `reads a declared webhook from the
metadata registry` and `it writes no \`sys_webhook\` row for it`, and
matches neither `materialized into \`sys_webhook\`` nor `materiali[sz]`;
the D3 entry's `replacement` contains `reads from the metadata registry`
and `writes no \`sys_webhook\` row`, and neither `replacement` nor
`reason` says `materiali[sz]`. The existing pins (path, `invalid_type`,
the opening sentence, `top-level \`webhooks:\` collection`, the migrate
sentence, no tracker number) are unchanged and still green.
- Round 1, at head `4ec164b9` (liveness summary), under
`os-verify-lock`: `pnpm --filter @objectstack/spec check:liveness` green
(`✓ … every governed-type property … is classified`, state counts
current: 1219 classified); the liveness script tests `vitest run
scripts/liveness/` **12/12 files, 358/358**; `pnpm --filter
@objectstack/spec test:repo` **86/86 files, 1420/1420**.
- Round 0, at head `80e34d7f`, all under `os-verify-lock` (shared-box
seconds):
- `pnpm --filter @objectstack/spec test` (local project): **750/750
files, 22313 passed, 1 todo**.
- `pnpm --filter @objectstack/spec test:repo` (the repo project, which
holds `connector-resilience-keys-retirement.test.ts`): **86/86 files,
1420/1420 tests**.
- `pnpm --filter @objectstack/spec typecheck`: exit 0
(`check:test-typecheck: OK`).
- **Reverse check of the new pin (one-off, no file left).** With
`connector.zod.ts` reset to `origin/main`'s bytes (old sentence present:
1 hit; new phrase: 0 hits), the retirement test file reads **1 failed |
21 passed (22)**: `AssertionError: expected '\`connector.webhooks\` was
removed in @…' to contain 'reads a declared webhook from the met…'`.
Restored with `git checkout HEAD --
packages/spec/src/integration/connector.zod.ts`; `git diff HEAD` empty
and the blob hash matches HEAD.
## Acceptance notes
- **Liveness proof prose is in scope (seat, round 1).** The
`webhook-materialization` proof summary carried the same false sentence;
corrected here (see What changes). Its proof already asserted ADR-0131
D2, so only text moved.
- **Governed surfaces.** No hit for the old sentence or its kin in
`skills/**` or `docs/adr/**`, so none is named.
- Released `CHANGELOG.md` entries (`@objectstack/spec` and five
connector/automation packages) still carry the original retirement
note's "never materialized into `sys_webhook`". That clause is about the
nested array and was true of the model when it shipped; release-owned
text, not edited.
## Verification
<details><summary>Measure first, on origin/main 273137d</summary>
- **Compile refusal reproduced.** Scratch copy of
`examples/app-showcase` (removed afterwards), `webhooks:
[{url:'http://x/c', events:['create']}] as any` added to
`showcase_status_api`, `npx objectstack compile`: **exit 2**, one issue
`{"expected":"never","code":"invalid_type","path":["webhooks"]}` with
the "Before" message above (quoted verbatim from that run). Same copy
against this branch's rebuilt spec: **exit 2**, the same `expected`,
`code` and `path`, the "After" message.
- **A declared webhook writes no `sys_webhook` row, on the code path.**
`packages/plugins/plugin-webhooks/src/declared-webhooks.ts` reads
declared webhooks from `engine._registry.listItems('webhook')` (falling
back to the kernel `metadata` service) on every cache refresh; no
`sys_webhook` insert exists outside tests. Its pin
`declared-webhooks.test.ts` › "nothing writes a sys_webhook row: the
plugin boot binds the reader and seeds zero rows"
(`engine.writes.filter((w) => w.object === 'sys_webhook')` equals `[]`):
**1 passed | 21 skipped**, `VERDICT command-exit 0`.
</details>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `4ec164b9`: 107 derived (round 0's 105
plus `check:pm-governed-merges` and `check:pnpm-flag-spellings`).
Reconciled with `--ran`: **107 accounted, 105 run, 2 NOT-MEASURED, 0
UNRUN** (`✓ dispatch-gates --ran`). The two exit 3 are prerequisite
misses that need a full repo build (`spec check:skill-examples`,
`check:dual-build-cjs-loads`), not findings; CI's check-runs carry them.
<details><summary>107 gate commands and exit codes (head
4ec164b)</summary>
```text
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-adr-links.mjs :: exit 0
node scripts/check-adr-links.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-dev-prereqs.mjs --self-test :: exit 0
node scripts/check-doc-route-spelling.mjs --advisory :: exit 0
node scripts/check-doc-route-spelling.mjs --self-test :: exit 0
node scripts/check-docs-section-name.mjs :: exit 0
node scripts/check-docs-section-name.mjs --self-test :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/check-section-landing-index.mjs :: exit 0
node scripts/check-section-landing-index.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0
pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0
pnpm --filter @objectstack/spec run check:api-surface :: exit 0
pnpm --filter @objectstack/spec run check:authorable-surface :: exit 0
pnpm --filter @objectstack/spec run check:browser-reachable-entries :: exit 0
pnpm --filter @objectstack/spec run check:docs :: exit 0
pnpm --filter @objectstack/spec run check:dual-source-exports :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm --filter @objectstack/spec run check:empty-state :: exit 0
pnpm --filter @objectstack/spec run check:entry-nameability :: exit 0
pnpm --filter @objectstack/spec run check:export-origins :: exit 0
pnpm --filter @objectstack/spec run check:exported-any :: exit 0
pnpm --filter @objectstack/spec run check:generated :: exit 0
pnpm --filter @objectstack/spec run check:liveness :: exit 0
pnpm --filter @objectstack/spec run check:llms-txt :: exit 0
pnpm --filter @objectstack/spec run check:skill-examples :: exit 3
pnpm --filter @objectstack/spec run check:skill-refs :: exit 0
pnpm --filter @objectstack/spec run check:spec-changes :: exit 0
pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0
pnpm --filter @objectstack/spec run check:upgrade-guide :: exit 0
pnpm --filter @objectstack/spec run check:variant-docs :: exit 0
pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:corpus-claim-drift :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:doc-anchors :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:doc-frontmatter :: exit 0
pnpm check:docs-audit-scope :: exit 0
pnpm check:docs-redirects :: exit 0
pnpm check:docs-single-h1 :: exit 0
pnpm check:docs-spec-enumerations :: exit 0
pnpm check:docs-transcript-drift :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 3
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:merge-driver :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:objectql-double-limit :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:pm-governed-merges :: exit 0
pnpm check:pm-prior-rulings :: exit 0
pnpm check:pnpm-flag-spellings :: exit 0
pnpm check:published-files :: exit 0
pnpm check:published-readme-links :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:react-page-adapter-contract :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:role-word :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:spec-docblock-symbol-anchors :: exit 0
pnpm check:spec-parsed-alias :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:vendor-version-stamps :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
```
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01WGufzgrsarJGTAxa3CuzzA)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01WGufzgrsarJGTAxa3CuzzA
---------
Co-authored-by: Claude <noreply@anthropic.com>
fix(spec): the retired `connector.webhooks` refusal describes declared webhooks as they work now: read from the metadata registry, with no `sys_webhook` row
6
+
7
+
Clause-②: no
8
+
Metadata-protocol: none
9
+
10
+
An author who writes `webhooks` on a connector is refused, and the refusal tells them to declare the webhook in the stack's top-level `webhooks:` collection. It used to add that such a webhook "is materialized into `sys_webhook`". That is no longer how the platform works (ADR-0131 D2): a declared webhook is read from the metadata registry and delivered on record events, and no `sys_webhook` row is written for it. `sys_webhook` holds only the webhooks an organization authors at runtime. An author following the old text looked for a row that never appears.
11
+
12
+
The refusal now says this. The same correction is made in the protocol-18 upgrade note for the connector resilience family (its replacement and reason) and in the generated connector reference.
13
+
14
+
Prose only. The refusal itself, its error code, its path and what the connector schema accepts are unchanged.
Copy file name to clipboardExpand all lines: content/docs/references/integration/connector.mdx
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -193,7 +193,7 @@ const result = ConnectorSchema.parse(data);
193
193
|**triggers**|`never`| optional |[REMOVED]`connector.triggers` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — a connector trigger never started anything: `AutomationEngine.registerConnector` registers a connector's actions only, no polling loop read `intervalSeconds` (or the `interval` spelling it was renamed from), and no receiver was driven by a `webhook` trigger. Delete the key; the `ConnectorTrigger` shape leaves with it. To start work from an external system, write a flow that calls the connector's action in a `connector_action` node: for an external event, an `api` flow that the event's sender calls; for a scheduled pull, a `schedule` flow. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
194
194
|**syncConfig**|`never`| optional |[REMOVED]`connector.syncConfig` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no engine ever ran a connector-attached sync: nothing read `strategy`, `direction`, `realtimeSync`, `timestampField`, `conflictResolution`, `batchSize`, `deleteMode` or `filters`, so the `latest_wins` and `soft_delete` defaults resolved and deleted nothing. Delete the key; the `DataSyncConfig` shape leaves with it. A sync is defined on its TARGET: a `mapping` (`targetObject`, `fieldMapping`, `mode`, `upsertKey`) whose `connectorSource` names the `rest` or `openapi` connector it pulls from, the read action and an optional timestamp `watermark`, with a `job` for the cadence. Its pull runs when a `job` drives it — a `job` whose `pull: { mapping }` names that mapping; the binding alone moves no rows. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
195
195
|**fieldMappings**|`never`| optional |[REMOVED]`connector.fieldMappings` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no engine ever moved a value through a connector field mapping: nothing read `source`, `target`, `defaultValue`, `dataType`, `required` or `syncMode`. Delete the key; the `ConnectorFieldMapping` shape leaves with it. Map fields on the sync's TARGET instead: a `mapping`'s `fieldMapping` (`source` → `target`, with a `transform` the import path executes), which its `connectorSource` pulls through. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
196
-
|**webhooks**|`never`| optional |[REMOVED]`connector.webhooks` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — a webhook nested inside a connector was never registered as a `webhook` item, so it was never materialized into `sys_webhook` and never delivered, and nothing emits the connector events its `events` list could name (`sync.completed`, `auth.expired` and the rest). Delete the key; the nested shape leaves with it (`WebhookConfig`, `WebhookEvent`, `WebhookSignatureAlgorithm`). To have a webhook actually sent, declare it in the stack's top-level `webhooks:` collection, which is materialized into `sys_webhook` and delivered on record events — note that doing so STARTS deliveries this connector never made. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
196
+
| **webhooks** | `never` | optional | [REMOVED] `connector.webhooks` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — a webhook nested inside a connector was never registered as a `webhook` item, so the webhook dispatcher never read it and never delivered it, and nothing emits the connector events its `events` list could name (`sync.completed`, `auth.expired` and the rest). Delete the key; the nested shape leaves with it (`WebhookConfig`, `WebhookEvent`, `WebhookSignatureAlgorithm`). To have a webhook actually sent, declare it in the stack's top-level `webhooks:` collection. The platform reads a declared webhook from the metadata registry and delivers it on record events; it writes no `sys_webhook` row for it (`sys_webhook` holds only the webhooks an organization authors at runtime). Note that declaring it STARTS deliveries this connector never made. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
197
197
|**rateLimitConfig**|`never`| optional |[REMOVED]`connector.rateLimitConfig` was removed in @objectstack/spec 17.0.0 (ADR-0049 D2) — the entire shape is gone, not just this key: `ConnectorRateLimitConfig` and its `RateLimitStrategy` enum were removed with it, because no outbound rate-limiting engine ever existed. The platform's only token bucket (runtime `security/rate-limit.ts`) throttles INBOUND requests to us; nothing throttled the calls a connector makes out, so every knob here was inert while reading like a configured cap. Delete the key. Do NOT substitute `shared``RateLimitConfig` — that is the inbound limiter and would cap the wrong direction; until an outbound throttle exists, rate-limit at the connector provider or upstream gateway. This edit left `os migrate meta` with the protocol-16.x conversions: apply it by hand, or run the tool on a release that still carries the conversion — https://objectstack.ai/docs/protocol-upgrade.|
|**connectionTimeoutMs**|`never`| optional |[REMOVED]`connector.connectionTimeoutMs` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — the platform never honoured it and cannot honour it where it was declared: a connector's outbound call is a WHATWG `fetch`, whose only cancellation surface is one `AbortSignal` over the whole operation, so nothing there observes the connection phase separately, and the value only ever travelled (onto the reported def and the materialization fingerprint) without ever bounding a connect. Delete the key. Use `requestTimeoutMs` for the deadline the platform does keep — it is applied as `resilientFetch`'s per-attempt timeout — and bound the connect phase at a connector provider or upstream gateway on a transport that can separate the phases. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
@@ -490,7 +490,7 @@ Connector type
490
490
|**triggers**|`never`| optional |[REMOVED]`connector.triggers` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — a connector trigger never started anything: `AutomationEngine.registerConnector` registers a connector's actions only, no polling loop read `intervalSeconds` (or the `interval` spelling it was renamed from), and no receiver was driven by a `webhook` trigger. Delete the key; the `ConnectorTrigger` shape leaves with it. To start work from an external system, write a flow that calls the connector's action in a `connector_action` node: for an external event, an `api` flow that the event's sender calls; for a scheduled pull, a `schedule` flow. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
491
491
|**syncConfig**|`never`| optional |[REMOVED]`connector.syncConfig` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no engine ever ran a connector-attached sync: nothing read `strategy`, `direction`, `realtimeSync`, `timestampField`, `conflictResolution`, `batchSize`, `deleteMode` or `filters`, so the `latest_wins` and `soft_delete` defaults resolved and deleted nothing. Delete the key; the `DataSyncConfig` shape leaves with it. A sync is defined on its TARGET: a `mapping` (`targetObject`, `fieldMapping`, `mode`, `upsertKey`) whose `connectorSource` names the `rest` or `openapi` connector it pulls from, the read action and an optional timestamp `watermark`, with a `job` for the cadence. Its pull runs when a `job` drives it — a `job` whose `pull: { mapping }` names that mapping; the binding alone moves no rows. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
492
492
|**fieldMappings**|`never`| optional |[REMOVED]`connector.fieldMappings` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no engine ever moved a value through a connector field mapping: nothing read `source`, `target`, `defaultValue`, `dataType`, `required` or `syncMode`. Delete the key; the `ConnectorFieldMapping` shape leaves with it. Map fields on the sync's TARGET instead: a `mapping`'s `fieldMapping` (`source` → `target`, with a `transform` the import path executes), which its `connectorSource` pulls through. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
493
-
|**webhooks**|`never`| optional |[REMOVED]`connector.webhooks` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — a webhook nested inside a connector was never registered as a `webhook` item, so it was never materialized into `sys_webhook` and never delivered, and nothing emits the connector events its `events` list could name (`sync.completed`, `auth.expired` and the rest). Delete the key; the nested shape leaves with it (`WebhookConfig`, `WebhookEvent`, `WebhookSignatureAlgorithm`). To have a webhook actually sent, declare it in the stack's top-level `webhooks:` collection, which is materialized into `sys_webhook` and delivered on record events — note that doing so STARTS deliveries this connector never made. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
493
+
| **webhooks** | `never` | optional | [REMOVED] `connector.webhooks` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — a webhook nested inside a connector was never registered as a `webhook` item, so the webhook dispatcher never read it and never delivered it, and nothing emits the connector events its `events` list could name (`sync.completed`, `auth.expired` and the rest). Delete the key; the nested shape leaves with it (`WebhookConfig`, `WebhookEvent`, `WebhookSignatureAlgorithm`). To have a webhook actually sent, declare it in the stack's top-level `webhooks:` collection. The platform reads a declared webhook from the metadata registry and delivers it on record events; it writes no `sys_webhook` row for it (`sys_webhook` holds only the webhooks an organization authors at runtime). Note that declaring it STARTS deliveries this connector never made. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
494
494
|**rateLimitConfig**|`never`| optional |[REMOVED]`connector.rateLimitConfig` was removed in @objectstack/spec 17.0.0 (ADR-0049 D2) — the entire shape is gone, not just this key: `ConnectorRateLimitConfig` and its `RateLimitStrategy` enum were removed with it, because no outbound rate-limiting engine ever existed. The platform's only token bucket (runtime `security/rate-limit.ts`) throttles INBOUND requests to us; nothing throttled the calls a connector makes out, so every knob here was inert while reading like a configured cap. Delete the key. Do NOT substitute `shared``RateLimitConfig` — that is the inbound limiter and would cap the wrong direction; until an outbound throttle exists, rate-limit at the connector provider or upstream gateway. This edit left `os migrate meta` with the protocol-16.x conversions: apply it by hand, or run the tool on a release that still carries the conversion — https://objectstack.ai/docs/protocol-upgrade.|
|**connectionTimeoutMs**|`never`| optional |[REMOVED]`connector.connectionTimeoutMs` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — the platform never honoured it and cannot honour it where it was declared: a connector's outbound call is a WHATWG `fetch`, whose only cancellation surface is one `AbortSignal` over the whole operation, so nothing there observes the connection phase separately, and the value only ever travelled (onto the reported def and the materialization fingerprint) without ever bounding a connect. Delete the key. Use `requestTimeoutMs` for the deadline the platform does keep — it is applied as `resilientFetch`'s per-attempt timeout — and bound the connect phase at a connector provider or upstream gateway on a transport that can separate the phases. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
'a stack/connector-authored webhook is materialized into a dispatchable sys_webhook row on boot (#3461/#3489) — the authored value crosses manifest-decomposition → the ObjectQL registry (type `webhook`) → the bridge → engine.insert, and the bridge was first gated BEHIND the realtime/messaging dispatch guard, silently materializing nothing on a realtime-less boot despite green unit tests.',
167
+
'a webhook declared in a stack\'s top-level `webhooks:` collection is dispatched from the metadata registry and writes no sys_webhook row (ADR-0131 D2; the boot seeder of #3461/#3489 is retired) — the authored value crosses manifest-decomposition → the ObjectQL registry (type `webhook`) → the `declaredWebhooks` reader the plugin binds on the data engine alone → the dispatcher, which delivers a matching record event with `object`→`object_name` applied. The proof boots WITHOUT realtime because the first bridge was gated BEHIND the realtime/messaging dispatch guard and silently bound nothing on a realtime-less boot despite green unit tests.',
0 commit comments