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 8368f1c
Browse filesBrowse the repository at this point in the historyBrowse files
feat(service-automation,core,types): the connector sync executor pulls a mapping's connectorSource through the import runner, moved beside bulkWrite (#21084)
Fixes#20919
Clause-②: yes (widening)
Stage ② of ruling A on #20281: the **connector sync executor**.
`service-automation` pulls a `mapping` whose `connectorSource` names a
declared `rest` / `openapi` connector, and writes the records through
the import runner's upsert-by-match-key. Built to the seat's stage-②
direction (`5922160386` on #20281) and the amended claim surface
(`5922189483`).
## What changed
**1. The move (destination A, 0 new `package.json` edges).**
`service-automation` → `rest` would invert a layer, so the runner's
shared pieces moved down. ⛔ None of them is a copy.
- To `@objectstack/core` (`src/utils/`, beside `bulkWrite`):
`import-runner.ts` and `import-coerce.ts` (whole files), the mapping
apply (`applyMappingToRows`, `refuseUnknownMappingTargets`,
`MappingArtifactLike`, `MappingFailure`) as `import-mapping.ts`, and the
field meta (`ExportFieldMeta`, `buildFieldMetaMap`) as
`import-field-meta.ts`. Their suites moved with them: 6
`import-runner-*` suites, `import-coerce`, `import-mapping`.
- To `@objectstack/types`: the classification half of
`rest/src/error-response.ts` (`CLIENT_MESSAGE_MAX` through
`classifyDataError`, 1,892 lines) as `data-error-classification.ts`.
`rest` keeps the emitters.
- `rest` re-exports every moved name: from `export-format.ts`,
`import-mapping.ts`, `error-response.ts` and the package index. Its
production callers are unchanged: `rest-server.ts`'s two import routes,
and `plugin-auth`'s `admin-import-users.ts` through the index. The one
exception is an import line in `rest-server.ts` (`runImport` now comes
from `@objectstack/core`).
- Two suites stayed in `rest` because they need `objectql` /
`metadata-protocol`, which `core` cannot depend on:
`import-runner-historical-readonly-insert`,
`import-runner-unique-violation-row`. They now import from the new home.
**2. The executor:**
`packages/services/service-automation/src/connector-pull.ts`. It is
exposed as `pullConnectorSource(deps, opts)` and as
`AutomationServicePlugin.pullConnectorSource({ mapping, context })`.
- It reads the mapping through the protocol's `getMetaItem({ type:
'mapping', name })`, as the import door does.
- It resolves `connectorSource.connector` to a **declared** instance.
The plugin now records each materialized instance's provider, because
the registered def carries none. Only `rest` / `openapi` is accepted.
- It makes **one** action call per pull. `watermark.param` goes into the
call's `query`. The records are read at `recordsPath` (default `body`).
- **W1 watermark.** The starting point is the highest value stored in
the target field that a transform-`none` `fieldMapping` entry copies
`watermark.field` onto. It is one read, under the caller's context.
Nothing else stores it.
- It writes through the moved `runImport`. Every knob `connectorSource`
does not declare takes the door's default: `runAutomations` true,
`trimWhitespace` true, the rest false.
- Nothing schedules a pull. That is stage ③ (a `job`).
**3. The texts the executor makes true** (`domain:spec`, the amendment's
list):
- the `connectorSource` and `watermark` describes;
- the `mapping.json` ledger rows: `planned` → `live`, citing
`connector-pull.ts#pullConnectorSource`. `authorWarn` stays, with the
hint reworded to "pulled when a job drives it; nothing schedules it
until stage ③";
- the `syncConfig` prescription and its pin;
- the D3 entry (it drops "validated at authoring");
- `SYNC_ARCHITECTURE.md`: how a pull runs, the one-response limit, and
the two stale Best Practices bullets rewritten;
- the `packages/lint` group-C comments and test title.
The migrations registry region and `content/docs/references/**` were
regenerated with `check:generated --fix` and `gen:schema` / `gen:docs`.
⛔ No new key, cadence key, credential or paging convention.
## The executor's refusals
Every refusal is a typed `ConnectorPullError` (`code`, `status`,
`reason`). It is raised **before anything is written**, and each pin
asserts that no write reached the protocol. The codes are
standard-catalog members or the code the import door already answers
with, so no new ledger code was minted.
| Condition | `code` | `status` | `reason` |
|---|---|---|---|
| action answered `ok: false` | `EXTERNAL_SERVICE_ERROR` | 502 |
`upstream_not_ok` |
| non-array at `recordsPath` | `INTEGRATION_ERROR` | 502 |
`records_not_array` |
| a record that is not an object | `INTEGRATION_ERROR` | 502 |
`record_not_object` |
| plugin-origin connector (not a `connectors[]` entry) |
`VALIDATION_ERROR` | 400 | `connector_plugin_origin` |
| unmapped `watermark.field` (none, or only through a transform) |
`VALIDATION_ERROR` | 400 | `watermark_field_unmapped` |
| `update` / `upsert` with an empty `upsertKey` | `VALIDATION_ERROR` |
400 | `upsert_key_empty` |
| unknown mapping | `MAPPING_NOT_FOUND` | 404 | `mapping_not_found` |
| mapping without `connectorSource` | `VALIDATION_ERROR` | 400 |
`no_connector_source` |
| connector not registered | `VALIDATION_ERROR` | 400 |
`connector_not_registered` |
| degraded instance | `SERVICE_UNAVAILABLE` | 503 | `connector_degraded`
|
| provider not `rest` / `openapi` | `VALIDATION_ERROR` | 400 |
`connector_provider_unsupported` |
| action the connector does not declare | `VALIDATION_ERROR` | 400 |
`connector_action_unknown` |
| `javascript` transform | `UNSUPPORTED_TRANSFORM` | 400 |
`unsupported_transform` |
| `connectorSource.input` / `input.query` not an object (watermarked
pull) | `VALIDATION_ERROR` | 400 | `input_query_not_object` |
| target that is no field of the object | `INVALID_FIELD` (the door's) |
400 | `mapping_target_refused` |
The first six are the order's mandated refusals.
## Pins
- `connector-pull.test.ts` (18 cases). Every refusal above, plus the
watermark read: the target field, highest first, `limit: 1`, merged into
the declared `input.query`. A first incremental pull sends no starting
point.
- `connector-pull.integration.test.ts`: **end to end through a real
`rest` connector**.
- A declared `connectors[]` entry is materialized by the real
`ConnectorRestPlugin` provider. It reads a local `node:http` fixture
server over a real socket, and writes into a real ObjectQL engine on
SQLite.
- Pull 1 sends no `since` and creates 2 rows.
- Pull 2 sends `since=2026-01-02T00:00:00.000Z`, the max `synced_at`
**read from the target**. It **updates** `c2` by `upsertKey` and creates
`c3`.
- It imports `connector-rest`'s source by relative path. That package
already dev-depends on `service-automation`, so a manifest edge back
would close a cycle. The read is declared in
`scripts/cross-package-test-inputs.mjs` and `turbo.json`, and
`service-automation`'s type-check `rootDir` is widened to admit it (tsup
emit unaffected; `check-dts-emitted` 2/2).
- Red first: `4f4260ee` committed the pins alone. The run against that
state failed both files: `Cannot find module './connector-pull.js'` and
`automation.pullConnectorSource is not a function`. `4b301797` then
added the code. The executor file's hash was identical before and after
that red run (`26a83b0d4a20fdab`).
- The moved suites are green at their new homes: `core` 69 files / 2044
tests, `rest` 241 / 4576.
## Verification
### Move proof (a move is a move)
An extraction script compared every moved range at base `e161ad35` with
its destination:
```
IDENTICAL packages/rest/src/export-format.ts:35-65 (31 lines) -> packages/core/src/utils/import-field-meta.ts
IDENTICAL packages/rest/src/export-format.ts:139-182 (44 lines) -> packages/core/src/utils/import-field-meta.ts
IDENTICAL packages/rest/src/import-mapping.ts:37-55 (19 lines) -> packages/core/src/utils/import-mapping.ts
IDENTICAL packages/rest/src/import-mapping.ts:117-328 (212 lines) -> packages/core/src/utils/import-mapping.ts
IDENTICAL packages/rest/src/error-response.ts:66-1957 (1892 lines) -> packages/types/src/data-error-classification.ts
WHOLE-FILE packages/rest/src/import-runner.ts -> packages/core/src/utils/import-runner.ts: 7 changed line(s), all import lines
WHOLE-FILE packages/rest/src/import-coerce.ts -> packages/core/src/utils/import-coerce.ts: 9 changed line(s), all import lines
```
`git diff -M --color-moved=zebra` over the split files, counting `+`/`-`
lines painted as moved against those not:
```
packages/types/src/data-error-classification.ts: moved+=1896 non-moved+=49 (header, import lines, trailing export list)
packages/rest/src/error-response.ts: moved-=1896 non-moved+=28 non-moved-=5 (header note, import/re-export lines)
packages/core/src/utils/import-mapping.ts: moved+=241 non-moved+=14 (header, import lines)
packages/rest/src/import-mapping.ts: moved-=241 non-moved+=11 (import/re-export lines)
packages/core/src/utils/import-field-meta.ts: moved+=75 non-moved+=13 (header)
packages/rest/src/export-format.ts: moved-=75 non-moved+=4 non-moved-=1 (import/re-export lines)
```
The eight classification helpers the REST emitters compose are exported
through a **trailing** `export { … }` list, so no declaration line was
edited. Never a copy: no declaration of `mapDataError`,
`classifyDataError`, `structuredCodeAnswer`, `declaredHttpStatus`,
`applyMappingToRows`, `refuseUnknownMappingTargets`,
`buildFieldMetaMap`, `runImport`, `coerceRow`, `ExportFieldMeta` or
`MappingArtifactLike` is left in `packages/rest/src` (0 each). The
control: the same grep finds each one at its new home.
R2 held: the moved half has 0 code references to the emission half or to
`log.js`. Its only mentions of the emitters are `{@link}` prose in
docblocks.
### #20963 (PR #21001)
It had landed (`e161ad35`) before the split, so the branch starts from
it. The moved half carries #21001's 18 added lines byte-equal (`18 of
18` found verbatim in `data-error-classification.ts`). There was no git
conflict.
### Tests and typechecks
Run on head `ec59b16a`, after merging `main`:
- `types` 22 files / 685 tests;
- `core` 69 / 2044;
- `rest` 241 / 4576 (+135 skipped), `test:repo` 5 / 179;
- `service-automation` 162 / 2021;
- `spec` 587 / 17235;
- `plugin-auth` import suites 2 / 50;
- `lint` 118 / 5452;
- `runtime` vocabulary suites 4 / 70.
`typecheck` is green for `types`, `core`, `rest`, `service-automation`,
`spec`, `lint`, `runtime` and `plugin-auth`. `spec check:generated`: all
15 artifacts up to date. `check:liveness`: green.
### Gates
`dispatch-gates --ran` reconciles: 139 derived, 138 run, 1 NOT-MEASURED,
0 UNRUN, run on head `ec59b16a`.
- **NOT MEASURED: `check:pm-dispatch-gates`.** Its non-self-test half
outlives the ~10-minute foreground cap here (killed at 595s); its
`--self-test` half exits 0. CI runs it.
- **One red, not this PR's: `check:platform-checklist`.** The only
finding is `areas/identity-auth.json` →
`plugin-auth/src/auth-plugin.ts#twoFactor`, and it is red identically at
base `e161ad35` and on `origin/main`. This PR's own checklist anchors
(`classifyDataError`, the mapping `transform` switch) are repointed and
resolve.
- The 11 ⛔ roster families the derivation printed are all exit 0.
## Changed lines
**6,032** (`additions + deletions`, rename detection on: 3,672 / 2,360,
64 files), over the 5,000 line. Per AGENTS.md this PR takes the
**human-merge path** (an authorised approval, then the owning seat, or
the maintainer's merge), not the queue alone. About 4,100 of those lines
are relocation, and GitHub shows the whole-file moves as renames.
## Acceptance notes
**Carriers, not touched here:**
- **ADR-0114**'s consumer line names `@objectstack/rest`'s
`import-coerce.ts` / `import-runner.ts`. That is `docs/adr` (Tier H),
left for a docs follow-up.
- `packages/objectql` comments cite `packages/rest/src/import-runner.ts`
(`index.ts`, `summary-errors.ts`, and two `plugin-audit-created-*` test
comments). They are outside the surface.
- Further comment-only path citations of the moved files live in
`driver-turso`, `metadata-protocol`, `plugin-security`, `spec`
(`conversions/registry.ts`, `api/export.zod.ts`, `data/mapping.zod.ts`'s
older docblocks), `scripts/check-dispatcher-error-vocabulary.mjs`,
`check-error-status-conformance.mjs` and
`check-durability-degradation-log-level.mjs`. They are prose only, and
every gate is green.
- `.changeset/20281-connector-sync-moved-to-mapping.md` (stage ①,
unreleased) still says the binding is "declared, not yet executed". This
PR's spec changeset supersedes it; whoever compiles the release notes
should read the two together.
**Surface extensions, each a mechanical consequence of the move or of
the pin:**
- `turbo.json` and `scripts/cross-package-test-inputs.mjs`: `rest`'s
source-reading pins read the moved runner and classification, and
`service-automation`'s integration pin reads `connector-rest` source;
- `packages/rest/vitest.repo-tests.json`;
- `packages/services/service-automation/tsconfig.json` (`rootDir`);
-
`docs/qa/platform-checklist/areas/{attachments-storage,records-forms}.json`
anchors;
- `packages/spec/liveness/state-counts/mapping.md` (generated).
**Choices the direction left open, settled here:**
- The refusal codes come from the standard catalog, so the ADR-0112
ledger is not edited. `reason` is the discriminator.
- The watermark only reads a transform-`none` `fieldMapping` entry: a
transformed target does not hold the pulled value.
**Not measured:**
- a real third-party `openapi` target (the pin uses `rest`);
- the paging behaviour of any real upstream (no measured target exists;
the one-response limit is documented);
- `spec test:repo` was narrowed to the 7 repo suites that read this
diff's files. The full project outlives the foreground cap here.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---
_Generated by [Claude
Code](https://claude.ai/code/session_012Kijp4k6e9fvgq63BLGsxY)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
feat(core): the bulk-import runner, its row coercion, the mapping apply and the field-meta map now live in `@objectstack/core`, beside `bulkWrite` (#20919)
6
+
7
+
`runImport` (with `sanitizeRowError` and its option/result types), the cell
refactor(rest): the import runner, coercion, mapping apply, field-meta map and error classification moved to `@objectstack/core` / `@objectstack/types`; `rest` re-exports them (#20919)
6
+
7
+
`runImport`, `coerceRow`, `buildFieldMetaMap` and their types (from the package
8
+
index), and `mapDataError` with its sibling classification exports, are now
9
+
re-exported from their new homes — byte-identical code, the same names, the same
10
+
behaviour at both import routes and at `plugin-auth`'s identity import. Nothing to
docs(spec): `mapping.connectorSource` is pulled when a job drives it — the describes say where the watermark is read from and that a pull reads one response (#20919)
6
+
7
+
The `connectorSource` describe no longer says the pull is not executed: the
8
+
connector sync executor in `@objectstack/service-automation` reads the binding,
9
+
and nothing schedules a pull until the `job` stage lands. `watermark.field` now
10
+
states that the next pull's starting point is read from the TARGET field a
11
+
`fieldMapping` entry copies it onto (an unmapped one is refused at pull time), and
12
+
`watermark` states the one-response limit: the connector's paging is not followed,
13
+
so a paged endpoint yields its first page only. The liveness ledger's
14
+
`connectorSource` rows are `live` and keep `authorWarn` (nothing schedules a pull
15
+
yet). The retired `connector.syncConfig` prescription and the
16
+
`connector-sync-keys-retired` upgrade entry say the same, and the entry's
17
+
acceptance criterion no longer claims the connector is validated at authoring.
|**upsertKey**|`string[]`| optional | Fields to match for upsert (e.g. email) |
52
-
|**connectorSource**|`{ connector: string; action: string; input?: Record<string, any>; recordsPath?: string; … }`| optional | Pull binding: the rest/openapi connector this mapping pulls rows from (one-way, full or timestamp-incremental; a `job` sets the cadence). Declared; the pull is not executed yet |
52
+
|**connectorSource**|`{ connector: string; action: string; input?: Record<string, any>; recordsPath?: string; … }`| optional | Pull binding: the rest/openapi connector this mapping pulls rows from (one-way, full or timestamp-incremental; a `job` sets the cadence). Pulled when a job drives it; nothing schedules it yet |
|**_lockReason**|`string`| optional | Human-readable reason shown when a write is refused by _lock. |
55
55
|**_lockSource**|`Enum<'artifact' \| 'package' \| 'env-forced'>`| optional | Layer that set _lock (artifact \| package \| env-forced). |
@@ -75,7 +75,7 @@ const result = ImportFieldMappingSchema.parse(data);
75
75
|**action**|`string`| ✅ | Key of the connector action that reads the records: `request` on a `rest` connector, an operation key on an `openapi` connector |
76
76
|**input**|`Record<string, any>`| optional | Fixed input passed to the action on every pull (e.g. `{ method: 'GET', path: '/contacts' }` for `rest`) |
77
77
|**recordsPath**|`string`| optional | Dot path, within the action's result, to the array of records (e.g. `body.results`); omitted ⇒ the result's `body` is the array |
78
-
|**watermark**|`{ field: string; param: string }`| optional | Timestamp-incremental pull; omitted ⇒ every pull reads the full set |
78
+
|**watermark**|`{ field: string; param: string }`| optional | Timestamp-incremental pull; omitted ⇒ every pull reads the full set. Either way a pull makes ONE action call and reads ONE response: the connector's paging is not followed, so a paged endpoint yields its first page only — and an incremental pull over a newest-first paged endpoint moves its starting point past the pages it never read|
|**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; apply them by hand. |
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. That binding is declared but its pull is not executed yet, and authoring it warns until it is. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. |
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; nothing schedules one yet, and authoring the binding warns until a job can. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them 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; apply them by hand. |
196
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; apply them 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. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
|**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; apply them by hand. |
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. That binding is declared but its pull is not executed yet, and authoring it warns until it is. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. |
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; nothing schedules one yet, and authoring the binding warns until a job can. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them 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; apply them by hand. |
493
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; apply them 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. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
0 commit comments