Repository navigation
Commit 50df124
fix(spec)!: defineConnector() refuses a top-level key the connector does not declare (#28815)
<!-- claude-projects-attribution: {"github_login":"os-steve"} -->
_Requested by **Steve** · [project
thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9RavNfuxRRXtHCcMEJuJFjH)_
Fixes #28738
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: none — the published skills' connector recipes
(`skills/objectstack-automation/references/integration-recipes.md`)
author declared keys only and say nothing about the helper's key
posture, so no skill text turns false.
## 维护者速读
- 改了什么:`defineConnector()` 改为解析闭合的连接器形状;写了未声明的顶层键会当场报错并点名,附处方(凭据类键指向
`auth.credentialRef`,提供方设置指向 `providerConfig`,`timeout` 指向
`requestTimeoutMs`)。
- 为什么改:它原来解析开放的 `ConnectorSchema`,先把未声明的键悄悄丢掉,`defineStack`
等闭合门再也看不到,作者放错位置的设置(比如把 `timeout` 写在根上)无声丢失。现在它和其它所有连接器入口一样响亮拒收。
- 风险与回滚:属于收窄(破坏性)。仓内 7 处调用和文档示例都只写已声明键,不受影响;第三方若在 `defineConnector`
里写了未声明键,加载时会报错。插件给 `registerConnector` 的定义不变。回滚只需把 `defineConnector`
函数体改回 `ConnectorSchema.parse`。
- 先看:`packages/spec/src/integration/connector.zod.ts` 的
`DefineConnectorSchema` 与 `defineConnector`
函数体;`connector-entry-undeclared-keys.test.ts`
新增的一组测试;`.changeset/harden-connector-undeclared-keys.md` 改动的那一句。
- 两分钟验证:`pnpm --filter @objectstack/spec exec vitest run
src/integration/connector-entry-undeclared-keys.test.ts`,应看到 24 条全过。
- 需要你做的:在本 PR 上书面确认对 #28688 待发 changeset 那一句的更正(Check Changeset
因此保持红,它不是必需检查,不挡合并)。
## What changes
- `defineConnector()` (`@objectstack/spec`,
`@objectstack/spec/integration`) parses a module-private
`DefineConnectorSchema`: the closed carrier
`DeclarativeConnectorEntryBaseSchema` behind the same retired-default
residue stage (`CONNECTOR_RETIRED_KEY_RESIDUE`) that `ConnectorSchema`
and `DeclarativeConnectorEntrySchema` use. An undeclared top-level key
now throws a `ZodError` with one `unrecognized_keys` issue at the root,
carrying the entry's own message, aliases and guidance. The signature is
unchanged: input `z.input` of `ConnectorSchema`, return
`ConnectorParsed`; `check:api-surface` and the rest of `check:generated`
needed no regeneration.
- `ConnectorSchema` (the def a plugin hands to `registerConnector`)
keeps its open posture; option C of the card is not taken.
- The helper does not apply the stack-only ADR-0097 / ADR-0143
refinements of `DeclarativeConnectorEntrySchema` (no inline
`authentication`, `providerConfig` / `auth` need a `provider`, no
`actions` on a provider-bound instance, the oauth2 / signed instance
rules). Every stack door still applies them to the value the helper
returns. See the Acceptance notes for why, and the measured fact that
the two choices agree on every in-repo caller.
- The ADR-0087 entry `connector-entry-undeclared-key-refused` (landed
with #28688, not yet released) now names `defineConnector()` in its
surface and reason. This PR's changeset disposes as `not-required
(already-registered connector-entry-undeclared-key-refused)`.
- Docblocks in `connector.zod.ts` and the `integration/` row of
`docs/audits/2026-07-unknown-key-strictness-ledger.md` stop saying the
helper parses the open shape.
### A pending release note this PR corrects
`.changeset/harden-connector-undeclared-keys.md` (from #28688, still
pending) said: "`ConnectorSchema` — the def a plugin hands to
`registerConnector`, and the shape `defineConnector()` parses — keeps
its posture." This PR makes the `defineConnector()` half false, so the
clause is removed and the sentence now reads "`ConnectorSchema` — the
def a plugin hands to `registerConnector` — keeps its posture."
`check-empty-changeset` refuses this edit by design (the DELIBERATE
CORRECTION class), so **Check Changeset stays red** until a maintainer
confirms the correction here in writing. It is not a required context.
## Tests
New `describe` block in
`packages/spec/src/integration/connector-entry-undeclared-keys.test.ts`
(12 cases; the file had 12): for an instance and a descriptor, each
refusal pinned by `code`, `path` and `keys` on the thrown `ZodError`,
the value never echoed; `timeout` → `requestTimeoutMs` with the declared
spelling as control; the credentialRef and `providerConfig` guidance;
the helper's issue equal to the stack door's issue on the same entry;
residue (`status: 'inactive'`) and the ADR-0010 envelope still accepted;
and a pin that the helper applies no stack-only refinement. Forced test
edits: none; no existing test changed.
Ablation at 4185e6a (`node scripts/ablation-replace.mjs`, body put back
to `ConnectorSchema.parse(config)`, anchor 1 → 0, restored blob equal to
HEAD): **7 failed | 17 passed (24)**. The 7 are exactly the refusal
cases; controls, residue, envelope and the no-refinement pin pass on
both sides, as they should.
<details><summary>Readings (packages/spec and the consumers that import
defineConnector)</summary>
- `@objectstack/spec` test at f668513: Test Files 788 passed (788),
Tests 23279 passed, 1 todo. Typecheck (incl. `check:test-typecheck`):
exit 0.
- `@objectstack/spec` `src/integration/` at bb3b9bc: Test Files 14
passed (14), Tests 264 passed. `check:generated` at bb3b9bc: all 14
artifacts up to date.
- Consumers, found by `git grep "defineConnector("`, closure built first
(`turbo run build`, 65 tasks), at f668513:
- `@objectstack/example-showcase` (4 calls): typecheck exit 0; Test
Files 42 passed (42), Tests 511 passed.
- `@objectstack/connector-app-github` (1 call): Test Files 2 passed (2),
Tests 13 passed; typecheck exit 0.
- `@objectstack/connector-app-stripe` (1 call): Test Files 2 passed (2),
Tests 19 passed; typecheck exit 0.
- `@objectstack/downstream-contract` (source-resolution identity pin on
both import paths): Test Files 3 passed (3), Tests 31 passed; typecheck
exit 0.
- Full dogfood (`pnpm --filter @objectstack/dogfood test`, no shard, at
f668513): VERDICT command-exit 0; Test Files 377 passed, 3 skipped
(380); Tests 3101 passed, 28 skipped. One named skip is
`print-service-render` (no built Console on this host).
- The final commit bb3b9bc edits one docblock line. The rebuilt
`packages/spec/dist` at bb3b9bc equals the dist the dogfood run read
(f668513): 360 of 360 non-map files byte-identical once esbuild's
chunk-hash names are normalized. So the dogfood reading carries to the
final head.
</details>
## Acceptance notes
- **Why the closed base, not the full entry schema.** The card's option
B says the helper "parses the closed twin". The full
`DeclarativeConnectorEntrySchema` would also refuse an inline non-`none`
`authentication` and the other stack-only rules at the helper. That
would break any plugin code that builds a `registerConnector` def
through `defineConnector`, which is the code-to-code break option C was
kept out of this card for. Measured: all 7 in-repo callers and all 5
`os:check` blocks in `content/docs/automation/connectors.mdx` give the
same verdict under either choice, so the choice changes nothing in this
repo. It is listed as an open question for the seat.
- The helper's ZodError message is the strict entry's message, because
the parse runs through the same `strictObject` carrier. It says "this
connector entry", which fits the documented use.
- `content/docs/releases/v17/17-6.mdx` names `defineConnector` among the
doors that refuse retired keys. That is release-owned and still true;
not touched.
## Verification
Gate list derived by `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` (84 commands) and run at the final
head bb3b9bc; reconciled with `--ran` (exit codes recorded): 84
derived, 84 run, 0 NOT-MEASURED, 0 UNRUN. 83 exit 0. One is red by
design: `node scripts/check-empty-changeset.mjs --base origin/main` exit
1, the DELIBERATE CORRECTION class above.
<details><summary>Gate notes</summary>
- An earlier sweep at f668513 had two exit-3 prerequisites, both met
before the final sweep: `check-plugin-teardown-shape.mjs --self-test`
needed its pinned fixture commit in the shallow clone (fetched; exit 0,
48 cases), and `pnpm check:dual-build-cjs-loads` needed 7 packages built
(built; exit 0, 115 require entry points across 70 packages load).
- `check-adr-0087-registration.mjs --base origin/main`: exit 0. The new
changeset reads
`[BREAKING+bang+clause-②-narrowing+metadata-protocol-narrowed]`,
disposition `not-required (already-registered)`.
- Local-only `pnpm check:dev-prereqs`: exit 1. It reads this host's
build state (35 `ui/` packages with no dist), not this diff. Reported,
not acted on.
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes: eslint on the changed
files exits 0 locally; the repo-wide run is CI's.
- [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: its generated artifacts are
regenerated and committed (none were stale).
- [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.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01ADHkmT9tAeotFczB7jYquW)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 3e05ed1 commit 50df124
6 files changed
Lines changed: 180 additions & 13 deletions
File tree
- .changeset
- docs/audits
- packages/spec/src
- integration
- migrations/entries/semantic
| 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 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
16 | | - | |
| 16 | + | |
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1376 | 1376 | | |
1377 | 1377 | | |
1378 | 1378 | | |
1379 | | - | |
| 1379 | + | |
1380 | 1380 | | |
1381 | 1381 | | |
1382 | 1382 | | |
| |||
0 commit comments