Skip to content

fix(spec): connector docs locate an undeclared key's refusal at the entry - #29306

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-m64vds
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-m64vds

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread
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]):

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.

82 derived, 82 run, 0 non-zero; --ran reconcile: 82 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero)
  • 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 621a4876… 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.

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

  • 1. Schemas start as Zod; packages/spec holds no runtime logic.
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran. pnpm lint is CI-owned and not run locally (comment and string-literal text only).
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ADHkmQnpb1NhsSxWTHrV3q


Generated by Claude Code

…ntry

The connector entry and defineConnector() TSDoc said an undeclared
top-level key is refused "located at the key". zod raises one
unrecognized_keys issue at the entry itself (empty path alone,
connectors.N inside a stack) and names every key in `keys`. Say that,
and correct the same phrase in the protocol-18 upgrade note's reason
and the test file header.

Claude-Session: https://claude.ai/code/session_01ADHkmQnpb1NhsSxWTHrV3q
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tests tooling labels Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/spec/src/integration/connector.zod.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/integration/connector.zod.ts) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json c2a5be03bdd6541891c7548b3bc0e9ef7449d51e → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS

Head-sha: cdbde723 · read 2026-11-02T12:02Z

维护者速读

  • 改了什么:connector 两段注释原说未声明键的拒收"定位在该键",实际 zod 只在条目根报一条 unrecognized_keys,键名列在 keys 里;现照实写。同一句也改在尚未发布的 protocol-18 升级条目 reason 和测试文件头注释里。
  • 为什么改:作者或 agent 照文档去键的路径找错误会找不到;升级条目还没发布,现在改掉就不会把错话写进发布说明(spec(connector docblocks): "refused by name, located at the key" — zod locates unrecognized_keys at the entry root #28821)。
  • 风险与回滚:只改文字,不动 schema、错误码、路径和消息。回滚即撤销本 PR。
  • 先看: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 src/integration/connector-entry-undeclared-keys.test.ts。
  • 需要你做的:无,席位落地。

席位意见:可落地,等契约复审。

席位核对 — ACCEPT · domain:spec#1 · head cdbde723

  1. Shape: draft, base main, Fixes #28821, Clause-②: no / Metadata-protocol: none at line start in the body and the changeset (spec patch). Within the seat's amended claim on spec(connector docblocks): "refused by name, located at the key" — zod locates unrecognized_keys at the entry root #28821, which widened the surface to the unreleased ADR-0087 entry reason and the test-file header. Not governed.
  2. Diff, read on the head: the DeclarativeConnectorEntryBaseSchema and defineConnector() docblocks now say one unrecognized_keys issue at the entry root (path [], or connectors.N inside a stack) lists every undeclared key in keys. The 18.connector-entry-undeclared-key-refused reason says the same; the entry is unreleased (its changesets are still pending), so the corrected sentence is the one the protocol-18 guide will carry. The test file changes only its header comment. No assertion, schema, code, path or message moves; nothing generated moved (JSDoc, not .describe()).
  3. Premise, measured by the dev on the built spec: five probes (entry, stack door, defineConnector, retired key alone, retired + undeclared) match the new prose; no .superRefine locates an undeclared key.
  4. Readings: check-runs on cdbde723 at this reading: 55 success, 9 skipped, 0 failure, 0 running.
  5. Landing: one CONTRACT_REVIEW_TIER review (comment changes under packages/spec/src/** are the published schema face, and the ADR-0087 entry is published text), which the seat runs itself (claim line follows). Then the seat readies and queues.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS

Contract-review-claim: session_01ADHkmPi25XXKx92Xmg4viS · head cdbde72 · 2026-11-02T12:02Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: cdbde723adfcbd3db96bad5b3d5cce2b89596747
Local-runs: none
Scope: packages/spec/src/integration/connector.zod.ts, packages/spec/src/migrations/entries/semantic/18.connector-entry-undeclared-key-refused.ts + packages/spec/src/shared/strict-object.ts, packages/spec/src/shared/suggestions.zod.ts, packages/spec/src/shared/retired-key.ts, packages/spec/src/stack.zod.ts, packages/spec/src/integration/connector-entry-undeclared-keys.test.ts

Inputs: card #28821 (body, triage 6564444438 and 6572205378, claim 6574951022, os-dev-report 6576053953, claim amendment 6576120502), PR #29306 (body, four-file list, net diff against main), and the check-runs on this head. The face is the published schema (packages/spec/src/**), so this is the schema-face tier. The two files outside hits (.changeset/28821-connector-refusal-location-prose.md, the test-file header) are the lane seat's ACCEPT at TIER_DEFAULT (6576643805); the test file is read here as the pin behind the docblocks, not reviewed.

① Derived judgments

  • Accept set: unchanged — right. The diff touches no strictObject(...) call, no shape, no aliases / guidance table, no retiredKey() tombstone, no .superRefine, no .describe(). DeclarativeConnectorEntryBaseSchema, DefineConnectorSchema, defineConnector and DeclarativeConnectorEntrySchema are byte-identical outside their JSDoc. Metadata-protocol: none is the right word.
  • Public surface: unchanged — right. No export added, removed or renamed; check:api-surface and check:authorable-surface ride in TypeScript Type Check (success). Both edited docblocks are JSDoc, not .describe(), so no generated reference page moves; check:docs is in the same lane and is green. The dev's "all 14 artifacts up to date" is consistent with that lane's verdict, which is the one read here.
  • ADR-0087 registry: entry text changed, projection unchanged — right. Only the reason string of connector-entry-undeclared-key-refused moves; id, surface, replacement and acceptanceCriteria are untouched, so check:spec-changes / check:upgrade-guide project the same entry with a corrected sentence. The entry is unreleased: both changesets that register it (.changeset/harden-connector-undeclared-keys.md, .changeset/28738-define-connector-closed.md) are still in .changeset/ on this head, so the corrected sentence is the one the protocol-18 guide will carry, and no released text is amended in a code PR.
  • Gate verdicts, read off this head. All ten required contexts success: Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard, Lint UI (skipped by paths filter; the aggregate is success), Type Check UI, Test UI, Build & E2E UI, Live E2E UI. Check Changeset ran twice (the second after the label edit at 2026-11-02T12:04Z) and both completed success (2026-11-02T11:32Z, 2026-11-02T12:06Z). 0 failure, 0 in progress at my second reading; head still cdbde723, mergeable_state: clean.

② Semver level

@objectstack/spec: patch — right. TSDoc on src/**/*.zod.ts and the ADR-0087 entry text ship in the tarball, so the diff publishes and skip-changeset would be wrong; nothing an author can write, parse or receive changes, so the kind is none and the level is patch. Clause-②: no and Metadata-protocol: none stand line-initial in both the PR body and the changeset body, with no (widening) / (narrowing) arm — correct for a non-protocol prose change. The changeset body states what moved (two TSDoc sites and the unreleased upgrade-note reason) and what did not (code, path, message, accept set). Not breaking; no ADR-0087 marker owed; the gate's own reading (Check Changeset, success twice) agrees.

③ Boundary flags

  • Deviation 1 (dev): two files beyond the claim's surface — the reason of 18.connector-entry-undeclared-key-refused.ts and the header comment of connector-entry-undeclared-keys.test.ts. The seat amended the claim (6576120502) before this review. Judged right: both carry the identical false sentence; the entry is unreleased (see ①), so leaving it would ship the error into the protocol-18 guide — the card's "Done when" is met only if that sentence goes too. The test-file edit is comment-only; every assertion (['unrecognized_keys', '', keys], connectors.0, the keys arrays) is unchanged in the diff. Bounded in-place fix, no scope creep.
  • Deviation 2 (dev): the claim had L1136 / L1232 swapped (defineConnector vs the base schema). On the head the base-schema docblock is at ~L1126-1147 and defineConnector's at ~L1230-1243; both are fixed. Informational; nothing owed.
  • open_questions: none declared. None found: no behaviour, test or generated artifact depends on these sentences.
  • Governance: no governed path in the file list (Governed Surface Queue Guard success); this record is the Tier S-style certification the seat asked for on the schema face, not a Tier H matter.

④ Design judgment

Each new sentence tried against the code, on the four doors named in the brief: the entry parsed alone (DeclarativeConnectorEntrySchema.safeParse), inside a stack (ObjectStackDefinitionSchema → connectors: z.array(DeclarativeConnectorEntrySchema), stack.zod.ts L1218), defineConnector() (DefineConnectorSchema.parse, a z.preprocess pipe over the same closed base), and a retired key present beside an undeclared one.

connector.zod.ts, DeclarativeConnectorEntryBaseSchema docblock — "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 its keys and in its message."

  • One issue, at the entry: strictObject is closedObject(z.object(shape, { error }).strict()) (strict-object.ts). zod's .strict() emits a single unrecognized_keys issue per object, path = that object's path, keys = every undeclared key. markUnknownKeyRefusalTerminal flips continue to false on that issue, so the ADR-0097 .superRefine on DeclarativeConnectorEntrySchema never adds a second complaint; the test "an undeclared key beside a refined fault is the one issue" pins exactly that. TRUE.
  • Path empty alone: the test pins issue.path.join('.') === '' for both INSTANCE and DESCRIPTOR. acceptRetiredDefaultResidue is z.preprocess(strip, schema) (retired-key.ts L289) — a pipe, which adds no path segment. TRUE.
  • connectors.N in a stack: a plain z.array(...) under the connectors key; the test pins connectors.0 with keys ['apiKey']. TRUE.
  • Every key in keys and in the message: strictUnknownKeyError builds Unrecognized key(s) on ${surface}: + every key in keys joined with backticks (suggestions.zod.ts L428), unconditionally, before any rename or prescription; the test pins each key in the message for the two-key case. TRUE.
  • Retired key present: triggers is declared as a retiredKey() tombstone in the shape, so it is not an undeclared key, never lands in keys, and raises its own invalid_type at path triggers. Beside an undeclared key, the undeclared key's refusal is still one unrecognized_keys issue at the root — the sentence claims nothing about a declared tombstone's own issue, and the test pins that triggers: [] alone produces no unrecognized_keys. TRUE. (I tried to read "ONE" together with the pre-existing next sentence, "meets that one refusal, not the ADR-0097 refinements", as "exactly one issue in total"; it does not say that, and the tombstone's issue is a shape-level refusal, not a refinement. No finding.)
  • The sentence correctly lists defineConnector() among the doors; its pre-existing claim about the save door (422 INVALID_METADATA) is unchanged and not a hunk.

connector.zod.ts, defineConnector() docblock — "refused by name, in one unrecognized_keys issue located at the root (path [], not the key's) that lists every such key in keys, with the prescription the authored entry gives (... timeout → requestTimeoutMs)."

  • The helper parses the entry alone, so the issue path is the empty array; the thrown ZodError has exactly one issue (pinned by helperRefusal's toHaveLength(1) and ['unrecognized_keys', '', keys]). timeout is an aliases row on the closed base, so the message carries Did you mean \timeout` → `requestTimeoutMs`(pinned). The prescription clause is the pre-existing one, carried intact. TRUE on thedefineConnectordoor; the stack-door parity test ("gives the verdict the stack door gives") pins the samekeys` and message.

18.connector-entry-undeclared-key-refused.ts, reason — "refused now, by name, in one unrecognized_keys issue located at the entry (not at the key) that lists every undeclared key, with a prescription for the two families ..."

  • Same claim as the base docblock, same evidence; the two families (credential → auth.credentialRef, provider setting → providerConfig) are the guidance rows on the closed base and are pinned by the credentialRef / providerConfig message tests. TRUE. The acceptanceCriteria already said "no unrecognized_keys issue at its root", so the entry is now internally consistent. No tracker number enters the runtime string (check:doc-authoring is in Lint & Repo Gates, success).

Residue check: git grep "located at the key" on the head finds no remaining use in connector context; every surviving hit names a surface where the finding genuinely is at the key (lint visibility predicates, the expression-invalid rule, meta-item name checks) or sits in a RELEASE-OWNED CHANGELOG.md, which a code PR must not touch.

Findings: must-fix — none. should-fix — none.

Implemented-by: claude/project-thread-m64vds
Reviewed-by: session_01ADHkmPi25XXKx92Xmg4viS

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 2, 2026 12:11
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 2, 2026 12:11
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit bf03ebb Nov 2, 2026
73 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-m64vds branch November 2, 2026 12:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec(connector docblocks): "refused by name, located at the key" — zod locates unrecognized_keys at the entry root

1 participant