Skip to content

feat(spec): declare mcp:connect-agent in ComponentPropsMap — undeclared keys refused; retire the mcp door-3 exemption (#12344) - #12467

Merged
huangyiirene merged 5 commits into
mainfrom
claude/issue-12344-connect-agent-props-row
Aug 26, 2026
Merged

feat(spec): declare mcp:connect-agent in ComponentPropsMap — undeclared keys refused; retire the mcp door-3 exemption (#12344)#12467
huangyiirene merged 5 commits into
mainfrom
claude/issue-12344-connect-agent-props-row

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Fixes#12344

Closure A per the 2026-08-25 retriage charter (cross-domain single PR via the exception path, spec-seat-designated): a strict ComponentPropsMap row for mcp:connect-agent, measured from the objectui renderer's real read points at the current .objectui-sha pin, then the standing door-3 exemption and its two guard pins deleted from the mcp canonical-envelope gate. Family precedent: #11575 (PR #11743), followed file-for-file.

The measurement (population + control)

Pin resolved from .objectui-sha at this branch's base: 190fbd01d0615e2e168faf9e08b8ad7844bc039d.

  • Read points: the sole registration is objectui packages/app-shell/src/console/connect/ConnectAgentWidget.tsx:335ComponentRegistry.register('mcp:connect-agent', () => <ConnectAgent />, { …, inputs: [] }). The render function discards the schema node entirely and ConnectAgent() (line 101) takes no parameters; every value it renders comes from /discovery, i18n and its own component state. Accepted key set: EMPTY — the row is strict-and-empty (emptyProps('mcp:connect-agent')), which is what refuses an undeclared key.
  • Population:git grep "mcp:connect-agent" at the pin returns exactly one registration and one component function (plus two name-list entries in packages/cli/src/utils/known-schema-types.ts and CHANGELOG rows — not read points). ConnectAgent is referenced only by its own registration and the side-effect import in app-shell/src/index.ts:258.
  • Control probe (the zero is a reading, not a missed grep): the same method on cloud:ai-model-status (CloudAiModelStatus.tsx:231, same pin) finds a registration that binds (props: CloudAiModelStatusProps) and spreads them into the component — a non-empty read set where one exists.
  • The registration's declared inputs: [] happens to agree; the row is the measurement, not the claim (spec: record:reference_rail has no ComponentPropsMap row — an entry filter parses, typechecks, validates, ships, and silently does nothing #8691/spec: record:alert / record:quick_actions / record:history have no ComponentPropsMap row — same silent no-op mechanism #8691 closed for the rail #8744 record where those diverge).

What changed

  • packages/spec/src/ui/component.zod.ts'mcp:connect-agent': emptyProps('mcp:connect-agent') row (plugin console widgets section), with the measurement recorded in place; emptyProps docblock now lists the type.
  • packages/mcp/src/canonical-expression-envelopes.test.tsexemptUnmappedTypes: ['mcp:connect-agent'][]; the props-bag-EMPTY pin and the non-vacuity pin deleted (with the exemption gone, both would pass vacuously — the machinery's description survives in the module header for the next exemption); unused walkPageComponents import removed. The door-3 EXACT assert stays and now demands the empty set.
  • packages/spec/src/ui/component.test.ts — row-defined / accepts-{} / refuses-any-key-naming-the-surface pins (the feat(spec): declare cloud-connection:panel / marketplace:installed-list in ComponentPropsMap — undeclared keys refused (#11575) #11743 shape).
  • packages/lint/src/validate-component-props.test.ts — dispatch pins: an authored key on mcp:connect-agent is one COMPONENT_PROPS_UNKNOWN_KEY finding; the shipped page's empty bag stays silent.
  • packages/spec/src/migrations/entries/semantic/18.ui-mcp-connect-agent-unknown-keys-refused.ts + regenerated registry.ts block — ADR-0087 semantic entry, protocol major 18.
  • .changeset/ui-mcp-connect-agent-unknown-keys-refused.md@objectstack/spec minor, BREAKING accept-set narrowing with FROM → TO and the registered adr-0087 marker (the feat(spec): declare cloud-connection:panel / marketplace:installed-list in ComponentPropsMap — undeclared keys refused (#11575) #11743 convention).

What changes hands (content limb): an undeclared key in this widget's props bag was previously unvalidated-pass (silent no-op — the walker skipped the unmapped type) and is now refused by the #5068 lint gate and the strict row. Nothing previously validated is re-judged; the empty bag the shipped page (connect_agent, packages/mcp/src/connect-ui.ts) authors stays accepted byte-identically. No governed surface is touched. Clause-②: no — declared=enforced restored, no accepted surface widened.

Gate evidence — all on 8886708 (final commit; clean tree)

  • mcp door-3 gate before/after: green at base with the exemption (11 tests); green after the row + cleanup (Test Files 1 passed · Tests 11 passed (11)). Reverse verification (committed-state mutation): re-adding exemptUnmappedTypes: ['mcp:connect-agent'] over the landed row reds the EXACT assert (Tests 1 failed | 10 passed, unmapped component types are EXACTLY the recorded exemptions) — the charter's "reds until they go", observed in the expected direction (the exemption is now vacuous vs unmappedTypes: []). Mutation confirmed on disk by anchored grep -c (1→0 / 0→1); no build limb (vitest reads the test source directly); restore proven by git hash-object == HEAD blob (aa84f1de…, byte-identical) and clean git diff HEAD.
  • vitest: mcp canonical-expression-envelopes.test.ts 11/11 · spec ui/component.test.ts 228/228 · lint validate-component-props.test.ts 45/45.
  • pnpm --filter @objectstack/spec check:generated — all 14 artifacts up to date (registry regenerated via gen:migration-registry; nothing else stale).
  • Full derived gate list (node scripts/pm/dispatch-gates.mjs, 7 paths vs merge-base 68c5dba): all 27 matched families run locally, exit 0 — incl. check:adr-0087-registration, check:changeset-gate-self-tests, check:cross-package-test-inputs, check:merge-driver, check:page-declaration-shape, check:type-check-coverage, check:type-check-debt (post full turbo build, 70/70), check:engine-double-contract, check:where-matcher, check:nul-bytes, docs-audit pair, release-rehearsal-clone --self-test.
  • Full pnpm lint (eslint . --no-inline-config) — whole-repo sweep, exit 0 (54s under the shared verify lock; no narrowing needed).
  • typecheck: spec / lint / mcp all green (script echo verified). Honest scope note: lint's and mcp's tsc --noEmit programs exclude **/*.test.ts (--listFiles hit count 0 for the edited test files), so their green is NOT a type measurement of the new test code — that is the repo's ledgered hidden-test steady state (check:type-check-coverage documents it; both coverage gates green). spec's tsconfig.test.json program DOES include ui/component.test.ts (--listFiles hit count 1). The vitest runs above are the executed measurement for all three files.

Deviations

🤖 Generated with Claude Code

https://claude.ai/code/session_01E5LFCYBJ3q2s6yW6oMLxwy

Generated by Claude Code


Generated by Claude Code

…e mcp door-3 exemption (#12344)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5LFCYBJ3q2s6yW6oMLxwy
…nknown-keys-refused
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5LFCYBJ3q2s6yW6oMLxwy
@github-actions

github-actionsBot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 2 documentable anchor(s).

1 release-owned page(s) name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx(via ComponentPropsMap (symbol))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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.

Coarse fallback — 126 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 76c18be8ad56400c85cde381e63c7755bbd6153apackageMentionDocs.

Which tree this was computed on

This run read content/docs from 2e7df53a2a61b7da64800d685ab12b6f93942a1b — the merge of head 3a65c5d5a2dc5c511d581d55eae7758bdce00dcc into base 76c18be8ad56400c85cde381e63c7755bbd6153a, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 2e7df53a2a61b7da64800d685ab12b6f93942a1b && git checkout 2e7df53a2a61b7da64800d685ab12b6f93942a1b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 76c18be8ad56400c85cde381e63c7755bbd6153a 3a65c5d5a2dc5c511d581d55eae7758bdce00dcc && git checkout -B drift-repro 76c18be8ad56400c85cde381e63c7755bbd6153a && git merge --no-ff 3a65c5d5a2dc5c511d581d55eae7758bdce00dcc
node scripts/docs-audit/affected-docs.mjs --json 76c18be8ad56400c85cde381e63c7755bbd6153a

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 76c18be8ad56400c85cde381e63c7755bbd6153a → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation protocol:ui tests tooling labels Aug 26, 2026
@huangyiirene
huangyiirene marked this pull request as ready for review August 26, 2026 05:24
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:uisize/mteststooling

Projects

None yet

2 participants

@huangyiirene@claude