Skip to content

Commit bf03ebb

Browse files
fix(spec): connector docs locate an undeclared key's refusal at the entry (#29306)
<!-- claude-projects-attribution: {"github_login":"os-steve"} --> _Requested by **Steve** · [project thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9S7KaP2SFvaTnEFHUw1gUB7)_ Fixes #28821 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:connector 两处 TSDoc 原说未声明键的拒收"定位在该键",实际 zod 只在条目根报一条 `unrecognized_keys`,键名在 `keys` 里;现照实写。同句也改在尚未发布的 protocol-18 升级说明 reason 和测试文件头注释里。 - 为什么改:作者或 agent 照文档去键的路径找错误会找不到;升级说明还没发布,现在改掉就不会把错话写进发布说明。 - 风险与回滚:只改文字,不动 schema、错误码、路径和消息;回滚就是 revert 这一个提交。 - 先看:`packages/spec/src/integration/connector.zod.ts`(两段 docblock)、`packages/spec/src/migrations/entries/semantic/18.connector-entry-undeclared-key-refused.ts`(reason 一句)。 - 两分钟验证:`pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/integration/connector-entry-undeclared-keys.test.ts`,预期全绿,用例本身就钉着 path 为空、`connectors.0`、`keys` 列出每个键。 - 需要你做的:无,席位落地。 ## What changes - `packages/spec/src/integration/connector.zod.ts`, the `DeclarativeConnectorEntryBaseSchema` docblock: "refuses it by name, located at the key" now says the refusal is ONE zod `unrecognized_keys` issue located at the entry, not at the key: its path is the entry's own (empty when the entry is parsed alone, `connectors.N` inside a stack), and it names every undeclared key in `keys` and in its message. - Same file, the `defineConnector()` docblock: "refused by name, located at the key" now says one `unrecognized_keys` issue at the root (path `[]`, not the key's) lists every such key in `keys`. The prescription clause is kept. - Bounded in-place fix, same sentence in two more places (declared as a deviation; the claim's file surface named only the two docblocks): - `packages/spec/src/migrations/entries/semantic/18.connector-entry-undeclared-key-refused.ts`, `reason`: "refused now, by name and located at the key" now says one `unrecognized_keys` issue located at the entry lists every undeclared key. This entry is not released yet (its changesets `harden-connector-undeclared-keys.md` and `28738-define-connector-closed.md` are still pending), so the false sentence would otherwise ship in the protocol-18 upgrade guide. - `packages/spec/src/integration/connector-entry-undeclared-keys.test.ts`, the file-header comment only ("located at the key" → "in one issue located at the entry root"). No assertion changes; the cases already pin path `''`, `connectors.0` and `keys`. - `.changeset/28821-connector-refusal-location-prose.md`: `@objectstack/spec` patch. No forced test edits. Nothing generated moved: both docblocks are JSDoc, not `.describe()`, and `check:generated` reports all 14 artifacts up to date. ## Why The phrase came from #28688 and PR #28815 carried it into `defineConnector()`. Measured on the built spec at `cdbde723` (`DeclarativeConnectorEntrySchema` / `defineConnector` / `ObjectStackDefinitionSchema`, issues as `[code, path, keys]`): ```text entry, two undeclared keys [["unrecognized_keys","",["token","vendorRegion"]]] stack door [["unrecognized_keys","connectors.0",["apiKey"]]] defineConnector, timeout [["unrecognized_keys","",["timeout"]]] entry, retired key only [["invalid_type","triggers",null]] entry, retired + undeclared [["invalid_type","triggers",null],["unrecognized_keys","",["vendorRegion"]]] ``` No `.superRefine` adds a located issue for an undeclared key: the refusal is terminal (`strictObject`), so the ADR-0097 refinements do not run. The only issue located at a key is a retired key's (`triggers`), which is declared as a tombstone and so is not an undeclared key. ## Tests - `pnpm --filter @objectstack/spec test` (under os-verify-lock, at `cdbde723`): Test Files 790 passed (790), Tests 23370 passed, 1 todo. VERDICT command-exit 0. - `pnpm --filter @objectstack/spec typecheck`: VERDICT command-exit 0. - `pnpm --filter @objectstack/spec check:generated` after `pnpm --filter @objectstack/spec build`: all 14 generated artifacts up to date, VERDICT command-exit 0. - No ablation: no tested behaviour changes (prose only). ## Acceptance notes - Prose only. The refusal, its code, its path, its message and the accept set are unchanged. No consumer tests are owed (no contract face moves); the claim names none. ## Verification Local gate subset, derived by `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `cdbde723` (4 paths vs merge base `f6a04e3af`), each run with stdin closed and its exit captured before any pipe. <details><summary>82 derived, 82 run, 0 non-zero; --ran reconcile: 82 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero)</summary> - First pass: 78 exit 0, 4 exit 3 (PREREQUISITE NOT MET): `check-plugin-teardown-shape --self-test` (pinned commit outside the shallow clone), `@objectstack/lint check:doc-formula-expressions` (formula dist), `check:dual-build-cjs-loads` and `check:lean-entry-closure` (package dists). - Prerequisites met: `git fetch --depth=1 origin 621a487…` and `pnpm turbo run build --concurrency=2 --filter='./packages/**'` under os-verify-lock (78/78 tasks, VERDICT command-exit 0). - Second pass on the built tree: the 4 above plus the 10 gates whose first run overlapped the build (`check:published-files` through `check:spec-parsed-alias`), all 14 exit 0. - Changeset declaration lines checked offline: `node scripts/check-changeset-no-major.mjs --base origin/main --event FILE` with this body: exit 0, `LEVEL AXIS: … clause-② no and metadata-protocol none … a non-protocol change at patch`. </details> CI families outside the derived total (lint, the type-check lanes, Test Core and the rest) are read from this PR's check-runs; none was re-run here. ## 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` is CI-owned and not run locally (comment and string-literal text only). - [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. - [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 with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01ADHkmQnpb1NhsSxWTHrV3q --- _Generated by [Claude Code](https://claude.ai/code/session_01ADHkmQnpb1NhsSxWTHrV3q)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4771869 commit bf03ebb

4 files changed

Lines changed: 31 additions & 9 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): the connector docs say where an undeclared key's refusal is located: at the entry, with the key named in `keys`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
A connector entry refuses a top-level key it does not declare, through `DeclarativeConnectorEntrySchema` and `defineConnector()`. The TSDoc on both said the refusal was "located at the key". It is not. zod raises one `unrecognized_keys` issue at the entry itself: its path is empty when the entry is parsed alone and `connectors.N` inside a stack, and it lists every undeclared key in `keys` and in its message. An author or agent looking for an issue at the key's path found none.
11+
12+
The TSDoc now says this. The same correction is made in the reason of the protocol-18 upgrade note for undeclared connector keys (`connector-entry-undeclared-key-refused`), which is not released yet.
13+
14+
Prose only. The refusal, its code, its path, its message and what the connector schema accepts are unchanged.

‎packages/spec/src/integration/connector-entry-undeclared-keys.test.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
// An authored connector entry (`DeclarativeConnectorEntrySchema`, behind
44
// `defineStack({ connectors })`, `os validate` / `os compile`, the artifact load
55
// and `PUT /meta/connector/:name`) refuses every TOP-LEVEL key its shape does
6-
// not declare, located at the key, where it used to parse green. Every refusal
7-
// is pinned by its issue `code`, `path` and `keys`; every case has a CONTROL
8-
// accept, so a refusal that fired on everything could not pass. The open
6+
// not declare, in one issue located at the entry root, where it used to parse
7+
// green. Every refusal is pinned by its issue `code`, `path` and `keys`; every
8+
// case has a CONTROL accept, so a refusal that fired on everything could not
9+
// pass. The open
910
// runtime carrier, `ConnectorSchema`, keeps its posture, pinned as a control.
1011
//
1112
// `defineConnector()`, the helper every documented `connectors:` entry is

‎packages/spec/src/integration/connector.zod.ts‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,9 +1133,13 @@ const ENTRY_PROVIDER_CONFIG_KEY_GUIDANCE =
11331133
* parse), and every read exit serves that body. On an open shape an undeclared
11341134
* key therefore parsed green and was kept, while `defineStack`, `os validate`
11351135
* and `os compile` dropped it without a word. Closed, every door that parses
1136-
* this schema refuses it by name, located at the key: `defineConnector()`,
1136+
* this schema refuses it by name: `defineConnector()`,
11371137
* `defineStack({ connectors })`, `os validate` / `os compile`, the artifact load
1138-
* at boot and reload, and the save door with `422 INVALID_METADATA`. The ADR-0010 protection envelope
1138+
* at boot and reload, and the save door with `422 INVALID_METADATA`. The refusal
1139+
* is ONE zod `unrecognized_keys` issue located at the entry, not at the key: its
1140+
* path is the entry's own (empty when the entry is parsed alone, `connectors.N`
1141+
* inside a stack), and it names every undeclared key in its `keys` and in its
1142+
* message. The ADR-0010 protection envelope
11391143
* is declared in the shape, so a stamped, package-loaded entry still parses.
11401144
*
11411145
* The refusal is terminal ({@link strictObject}): a body with an undeclared key
@@ -1229,9 +1233,11 @@ const DefineConnectorSchema = lazySchema(() =>
12291233
* shorthand) — preferred over a bare `: Connector` literal.
12301234
*
12311235
* The parse is CLOSED: a top-level key the connector does not declare is
1232-
* refused by name, located at the key, with the prescription the authored
1233-
* entry gives (a credential-shaped key → `auth.credentialRef`, a provider
1234-
* setting → `providerConfig`, `timeout` → `requestTimeoutMs`). The def a plugin
1236+
* refused by name, in one `unrecognized_keys` issue located at the root (path
1237+
* `[]`, not the key's) that lists every such key in `keys`, with the
1238+
* prescription the authored entry gives (a credential-shaped key →
1239+
* `auth.credentialRef`, a provider setting → `providerConfig`, `timeout` →
1240+
* `requestTimeoutMs`). The def a plugin
12351241
* hands to `registerConnector` is parsed there by the open
12361242
* {@link ConnectorSchema}, whose posture this does not change.
12371243
*/

‎packages/spec/src/migrations/entries/semantic/18.connector-entry-undeclared-key-refused.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ export const entry: SemanticMigration = {
1414
reason:
1515
'The authored entry was a plain object, so an undeclared top-level key parsed green at every door and ' +
1616
'meant nothing to the runtime: no provider, the automation service or the dispatcher ever read one. It is ' +
17-
'refused now, by name and located at the key, with a prescription for the two families an author most ' +
17+
'refused now, by name, in one `unrecognized_keys` issue located at the entry (not at the key) that lists ' +
18+
'every undeclared key, with a prescription for the two families an author most ' +
1819
'often misplaces: a credential and a provider setting. There is deliberately no mechanical rewrite: an ' +
1920
'undeclared key may be a misplaced provider setting whose right home depends on the provider, or a secret ' +
2021
'whose only copy is the value written there, and where either goes next is a judgement about the ' +

0 commit comments

Comments
 (0)