Repository navigation
Commit c48cd2c
fix(service-automation)!: a connector pull whose input breaks the action's declared inputSchema is refused before dispatch (#28140)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DNxhDE3NbjyqbvDsmL1Aox7)_
Fixes #28040
Clause-②: yes (narrowing)
Metadata-protocol: none
Skills-docs: updated here — `content/docs/automation/jobs.mdx` ("How a
run is recorded") and `packages/spec/docs/SYNC_ARCHITECTURE.md` ("How a
pull runs") name the new refusal; `skills/**`: none — `git grep -n -i
"connectorSource\|pullConnectorSource\|inputSchema" -- skills` has 1
hit, `skills/objectui/guides/plugin-development.md:113`
(`ComponentInputSchema`, unrelated), so no skill teaches the pull door.
## 维护者速读
- 改了什么:连接器拉取(job 的 `pull`)在调用连接器动作前,先用动作声明的 `inputSchema`
校验将要发送的输入(含水位参数);不合格即以 `VALIDATION_ERROR` 400 拒绝并点名参数,上游零调用。
- 为什么改:#27995 只给 `connector_action`
流程节点加了这道校验,拉取入口仍把缺必填参数的请求发到远端(OpenAPI 路径占位符被原样发送)。
- 风险与回滚:收窄(BREAKING,patch 级):原本"侥幸成功"的拉取若输入不满足声明的 schema 会转为 job 运行
`failed`;回滚即 revert 本 PR。节点的行为与报错文字逐字节不变。
-
先看:`packages/services/service-automation/src/connector-pull.ts`(新增校验段)、`src/connector-input-schema.ts`(从节点原样搬出的校验器)、`src/builtin/connector-nodes.ts`(改为调用共享校验器)。
- 两分钟验证:`pnpm --filter @objectstack/service-automation exec vitest run
--maxWorkers=2 src/connector-pull.test.ts`,预期 23/23 通过,其中 `[#28040]` 组 5
条。
- 需要你做的:无,席位落地。
## What changes
- **One checker, two doors.** The `inputSchema` judgment the
`connector_action` node gained in #28035 (#27995) moves out of
`builtin/connector-nodes.ts` into `src/connector-input-schema.ts`,
unchanged byte for byte (the moved block was diffed against
`origin/main`'s, `IDENTICAL`). The module exports
`findConnectorActionInputSchema` (the descriptor read the node made),
`judgeConnectorInput` (the violations joined as the node joined them,
capped at five plus `; and N more`) and `TEXT_FOR_SCALAR_HINT`. It is
not re-exported from the package entry.
- **The node** calls the shared helper. Its refusal text is assembled
from the same pieces in the same order, so `code`, `error` and the cast
hint are byte-identical; its existing pins
(`connector-input-schema.test.ts`, untouched) stay green.
- **The pull door** (`pullConnectorSource`, the door a job's `pull`
runs) judges the input it is about to send, after the watermark
parameter is added and before the handler is called. A failing input
throws `ConnectorPullError` with `code: 'VALIDATION_ERROR'`, `status:
400` (the pair the door already uses for its other input faults) and
`reason: 'input_schema_mismatch'`, and the message names each parameter
by dotted path, says nothing was pulled, and points at `GET
/api/v1/automation/connectors`. Nothing is called upstream and nothing
is written.
- **Public TypeScript surface** (why `Clause-②: yes (narrowing)`,
re-derived from the diff rather than the order's `no (narrowing)`):
`ConnectorPullRefusalReason` gains `'input_schema_mismatch'`, and
`ConnectorPullRegistry` now extends `ConnectorInputSchemaSource`, which
requires `getConnectorDescriptors()`. The automation engine (what
`AutomationServicePlugin` passes) already has it; a hand-built registry
adds it.
- Changeset `.changeset/28040-connector-pull-input-schema.md`: `patch`,
BREAKING banner, both declaration lines, ADR-0087 `not-required
(no-migration-prescription)` marker, FROM / TO / fix.
## Why
Prime Directive #10: a rule holds at every call site. Triage 6521041814
graded the pull door as the sibling call site of #27995, to be judged by
the same check through one helper and refused before dispatch, naming
the parameter.
## Tests
- **Re-measure on `main` (premise holds).** The three refusal pins added
here, run against `origin/main`'s `connector-pull.ts` (`8cb8c7f4`), fail
with `the pull must refuse, not resolve: expected undefined to be an
instance of ConnectorPullError`: the pull resolved, so it called the
action. `Tests 3 failed | 20 passed (23)`.
- **New pins** (`src/connector-pull.test.ts`, `[#28040]` group, through
the shipped `pullConnectorSource` with a counting handler):
1. A missing required parameter (`query.since`) → `VALIDATION_ERROR` /
400 / `input_schema_mismatch`, the message names `'query.since'`, the
handler is called 0 times, nothing is written.
2. A missing required section → names it with the parameters it requires
(`missing required 'query' (with 'query.since')`), 0 calls.
3. A wrong type (`query.limit: '50'` against `integer`) → refused naming
it, 0 calls; text is not converted.
4. The input judged is the input sent: a required `query.since` supplied
only by the watermark pulls (1 call, with the watermark value).
5. A valid input still pulls (1 call) and writes the record.
Every existing pull pin passes with no `inputSchema` declared, which is
the "no schema ⇒ pull what you are given" leg.
- **Ablation** (`node scripts/ablation-replace.mjs`, on the committed
fix): `if (verdict)` → `if (verdict && false /* ABLATION */)` in
`connector-pull.ts`, landed on disk (anchor 1 → 0, marker `grep -c` = 1,
blob `6b358231` → `349fc8e6`). Exactly the three refusal pins turned red
(`Tests 3 failed | 20 passed (23)`); restored, `blob == HEAD
(6b35823) and git diff HEAD is empty`. The tests import
`./connector-pull.js` from source, so no `dist/` leg applies.
## Acceptance notes
- A required parameter that only the watermark supplies is absent on a
first pull (nothing stored yet), so that pull is now refused. The
changeset says so and gives the fix (give it a first value in
`connectorSource.input`).
- The `rest` provider's `request` action declares no `required` and its
declared types match what a pull writes, so the shipped `rest` pull (the
integration pin) is unaffected; `openapi` actions are where the check
bites.
- The refusal comes after the watermark read and the object-schema read
(both reads, no writes), because the judged input includes the watermark
value. Nothing is called upstream and nothing is written.
- Out of scope, seen on the dogfood battery:
`packages/qa/dogfood/test/api-action-door-forward.dogfood.test.ts:178`
expects the `ACTION_DISABLED` message to contain
`sys_metadata_activation`, but #27964 (`3e0fd7a8f7`, an ancestor of
`origin/main`: `merge-base --is-ancestor` exit 0) rewrote that refusal
as a plain sentence. The pin is red on `main`; this diff touches no file
on that path. Reported to the seat for filing.
## Verification
All local readings are at head `02de39cc91`.
<details><summary>Package test + typecheck (service-automation),
connector-openapi dispatch pin</summary>
- `pnpm --filter @objectstack/service-automation build` → `VERDICT
command-exit 0`
- `pnpm --filter @objectstack/service-automation typecheck` → exit 0
(includes `check:test-typecheck`: `0 file(s) / 0 error(s)`)
- `pnpm --filter @objectstack/service-automation test` → `Test Files 227
passed (227)`, `Tests 2881 passed (2881)`, `VERDICT command-exit 0`
- `pnpm --filter @objectstack/connector-openapi exec vitest run
--maxWorkers=2 src/input-schema-dispatch.test.ts` (#28035's end-to-end
node pin) → `Tests 4 passed (4)`
- Focused: `connector-pull.test.ts`,
`builtin/connector-input-schema.test.ts`,
`builtin/connector-nodes.test.ts`, `connector-pull.integration.test.ts`,
`connector-pull-service-door.test.ts` → `Test Files 5 passed (5)`,
`Tests 64 passed (64)`
</details>
<details><summary>Derived gate families (dispatch-gates) + pnpm
lint</summary>
- `node scripts/pm/dispatch-gates.mjs --commands` derived 92 families
from 7 paths vs merge base `8cb8c7f46`.
- All 92 exit 0. Three first ran into a prerequisite (exit 3) and were
re-run green once it was met: `check-plugin-teardown-shape --self-test`
(after `git fetch --unshallow`), `check:skill-examples` and
`check:dual-build-cjs-loads` (after the full build that
`check:type-check-debt` runs). `check:type-check-debt` first queued
behind the dogfood run (exit 99, not measured) and passed on re-run:
`124/127 workspace packages type-checked, 0 in the DEBT ledger`.
- `node scripts/pm/dispatch-gates.mjs --ran` → `92 derived famil(ies)
accounted for — 92 run, 0 NOT-MEASURED`; `pnpm lint` recorded outside
the derivation.
- `pnpm lint` → exit 0.
- No exported symbol is renamed or removed (the moved helpers were
module-private), so `check-platform-checklist --changed-from` is not
owed.
</details>
<details><summary>Dogfood battery (the narrowing arm owes it)</summary>
- Closure `pnpm --filter '@objectstack/dogfood^...' build` → exit 0;
`pnpm --filter @objectstack/dogfood test` under the lock → `VERDICT
command-exit 1`, `Test Files 1 failed | 357 passed | 3 skipped (361)`,
`Tests 1 failed | 2882 passed | 20 skipped (2903)`.
- The one failure is `api-action-door-forward.dogfood.test.ts > the
switch holds: ban_user OFF answers 409 ACTION_DISABLED…`: `expected
'This action is turned off for this de…' to contain
'sys_metadata_activation'`. That is the stale pin on `main` named under
Acceptance notes, not a consequence of this diff (no automation or
connector path).
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(No schema touched.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: only `docs/SYNC_ARCHITECTURE.md` (not
in `files[]`, no generator reads it), so nothing to regenerate.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English (the maintainer summary
is Chinese by the seat's convention).
---
_Generated by [Claude
Code](https://claude.ai/code/session_016WV4cxUdTtczoG6TtcncPa)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 412908c commit c48cd2c
7 files changed
Lines changed: 350 additions & 127 deletions
File tree
- .changeset
- content/docs/automation
- packages
- services/service-automation/src
- builtin
- spec/docs
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
260 | 260 | | |
261 | 261 | | |
262 | 262 | | |
263 | | - | |
| 263 | + | |
| 264 | + | |
264 | 265 | | |
265 | 266 | | |
266 | 267 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| |||
128 | 129 | | |
129 | 130 | | |
130 | 131 | | |
131 | | - | |
132 | | - | |
133 | | - | |
134 | | - | |
135 | | - | |
136 | | - | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
137 | 135 | | |
138 | | - | |
139 | | - | |
140 | | - | |
141 | | - | |
142 | | - | |
143 | | - | |
144 | | - | |
145 | | - | |
146 | | - | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
147 | 141 | | |
148 | 142 | | |
149 | 143 | | |
150 | 144 | | |
151 | 145 | | |
152 | | - | |
153 | | - | |
| 146 | + | |
154 | 147 | | |
155 | 148 | | |
156 | | - | |
157 | | - | |
158 | | - | |
159 | | - | |
160 | | - | |
161 | | - | |
162 | | - | |
| 149 | + | |
163 | 150 | | |
164 | 151 | | |
165 | 152 | | |
| |||
196 | 183 | | |
197 | 184 | | |
198 | 185 | | |
199 | | - | |
200 | | - | |
201 | | - | |
202 | | - | |
203 | | - | |
204 | | - | |
205 | | - | |
206 | | - | |
207 | | - | |
208 | | - | |
209 | | - | |
210 | | - | |
211 | | - | |
212 | | - | |
213 | | - | |
214 | | - | |
215 | | - | |
216 | | - | |
217 | | - | |
218 | | - | |
219 | | - | |
220 | | - | |
221 | | - | |
222 | | - | |
223 | | - | |
224 | | - | |
225 | | - | |
226 | | - | |
227 | | - | |
228 | | - | |
229 | | - | |
230 | | - | |
231 | | - | |
232 | | - | |
233 | | - | |
234 | | - | |
235 | | - | |
236 | | - | |
237 | | - | |
238 | | - | |
239 | | - | |
240 | | - | |
241 | | - | |
242 | | - | |
243 | | - | |
244 | | - | |
245 | | - | |
246 | | - | |
247 | | - | |
248 | | - | |
249 | | - | |
250 | | - | |
251 | | - | |
252 | | - | |
253 | | - | |
254 | | - | |
255 | | - | |
256 | | - | |
257 | | - | |
258 | | - | |
259 | | - | |
260 | | - | |
261 | | - | |
262 | | - | |
263 | | - | |
264 | | - | |
265 | | - | |
266 | | - | |
267 | | - | |
268 | | - | |
269 | | - | |
270 | | - | |
271 | | - | |
272 | | - | |
273 | | - | |
274 | | - | |
275 | | - | |
276 | | - | |
277 | | - | |
278 | | - | |
279 | | - | |
280 | | - | |
281 | | - | |
282 | | - | |
283 | | - | |
284 | | - | |
285 | | - | |
286 | | - | |
287 | | - | |
288 | | - | |
289 | | - | |
290 | | - | |
291 | | - | |
292 | | - | |
293 | | - | |
294 | | - | |
295 | | - | |
296 | | - | |
297 | | - | |
298 | | - | |
0 commit comments