Skip to content

feat(spec)!: oauth2 and app-credential connector instance auth; the runtime oauth2 variant retired (ADR-0143 S0) - #28213

Merged
objectstack-fleet[bot] merged 10 commits into
mainfrom
claude/project-thread-a31eac
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 10 commits into
mainfrom
claude/project-thread-a31eac

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

domain:spec#1 · os-tesla · session_01L9qibwb6hBqszPTYJobitm

Requested by Tesla · project thread

Fixes #28137

Clause-②: yes (narrowing)
Metadata-protocol: narrowed
Skills-docs: follow-up #28226

维护者速读

  • 改了什么:连接器实例的 auth 新增两种需要换取令牌的类型,oauth2(授权码 / 客户端凭证)和 app-credential(飞书、钉钉、企业微信),密钥只能写引用;旧的运行时 oauth2 写法被移除并给出改写提示。
  • 为什么改:以前对接要 OAuth 的系统在开源版里无处声明,旧写法把密钥和刷新令牌明文写进配置,也从来没人执行;ADR-0143 把这类认证纳入开源层,这是第一步(只定形状)。
  • 风险与回滚:写了 authentication: { type: 'oauth2' } 的连接器会被拒绝(有迁移提示);新类型的实例在服务端真正支持前一律标为"降级、不调用上游",数据源借用返回 422,不会出现未认证调用。回滚即 revert 本 PR。
  • 先看:packages/spec/src/shared/connector-auth.zod.ts(新形状与拒绝提示)和 packages/services/service-automation/src/plugin.ts 里新加的降级分支。
  • 两分钟验证:pnpm --filter @objectstack/spec exec vitest run src/integration/connector-auth-grant-kinds.test.ts src/shared/connector-auth.test.ts,看到每个拒绝都有错误码和位置、每种类型都有正例通过。
  • 需要你做的:无。不属于受管面,契约复审 PASS(评论 6529303069),由席位落地;技能文档与 lint 提示的更新已立卡 Teach oauth2 and app-credential connector auth once S1 serves them: skills recipe bullet, its eval, and lint steer strings still say the union has no oauth2 #28226,随 S1 落地。

What changes

@objectstack/spec

  • ConnectorInstanceAuthSchema gains two members (ADR-0143 D2.1, D2.2). Both are closed (strictObject), and both carry their secret only as credentialRef.
  • On DeclarativeConnectorEntrySchema (both authoring doors), an oauth2 instance must name tokenUrl and clientId, plus authorizationUrl on authorization_code. Each missing key is refused at auth.<key>. The one exception is provider: 'mcp' over an http transport, where the MCP authorization flow discovers the endpoints and can register the client. An mcp instance over stdio gets the same refusals as any other instance.
  • D7. ConnectorOAuth2Schema leaves ConnectorAuthConfigSchema.
    • authentication: { type: 'oauth2' } is refused at type with a prescription that names ADR-0143 D7 and the instance auth that replaces it. The union's error map fires only for that literal, the ViewDataSchema provider: 'api' precedent.
    • ResolvedConnectorAuth stays the four static kinds.
    • The schema was module-level only and never on a published entry point. The type it narrows, integration/Connector:authentication, is authorable, hence the narrowing line.
    • The ADR-0087 D3 entry connector-auth-config-oauth2-retired records the change, with its STEP18_RATIONALE fragment (order 115).
  • D4. The per-dispatch handle types move into packages/spec/src/integration/connector-provider.ts: ConnectorCredentialHandle, BorrowedConnectorCredential, BorrowedConnectorAuth and ConnectorCredentialRequest, structurally identical. ConnectorProviderContext gains readonly credentialHandle?: ConnectorCredentialHandle.
  • Token-endpoint client authentication (card item 4) has no key. The ADR leaves the method (client_secret_basic vs client_secret_post) to the runtime that calls the token endpoint, so S1 owns it. The seat accepted this as an S1 choice.
  • The header text "OAuth2 is intentionally absent" is rewritten to the open-tier reading. The six new isomorphic aliases are pinned in type-alias-convention.pin.test.ts (count 795 → 801, with its receipt).

@objectstack/types: connector-credential-source.ts re-exports the four handle types from @objectstack/spec/integration under the same names. The service-datasource typecheck and its 1069 tests are unchanged and green.

@objectstack/service-automation (seat ruling A). Widening the union made two switches non-exhaustive, so the minimal exhaustive handling is:

  • plugin.ts: an oauth2 or app-credential instance is held degraded through the existing failDegradedConnectorInstance path, with a reason that names its kind. Its factory is never called, so it never reaches the upstream unauthenticated.
  • connector-credential-source.ts: a datasource borrow of such an instance is refused with 422 VALIDATION_ERROR, naming the kind and the instance.

Docs and fixtures

  • content/docs/automation/connectors.mdx: "Where is oauth2?" is replaced by "Grant-bearing kinds: oauth2 and app-credential", plus a note on the retired runtime variant and a failure-modes row.
  • packages/spec/docs/SYNC_ARCHITECTURE.md: the L3 example moves off the retired variant. The auth table drops jwt and saml, which were never variants.
  • variant-docs.json: re-keyed for the three unions.
  • The frozen fixture packages/qa/downstream-contract DcConnector changes per its README, because the break is intended and documented here: authentication is now bearer, with a comment citing the D7 entry.

S1 replaces these lines

These are the stand-in lines S1 (#26702) replaces when per-dispatch resolution lands. Every one of them is in packages/services/service-automation/src/:

  • plugin.ts, the GrantBearingInstanceAuth type and grantBearingNotServedReason() (lines 402–417 at head).
  • plugin.ts, the narrowed resolveInstanceAuth(auth: Exclude<ConnectorInstanceAuth, GrantBearingInstanceAuth> | undefined, …) signature and its doc sentence "A grant-bearing kind never reaches here either…" (lines 424–431).
  • plugin.ts, the reconcile branch if (entry.auth?.type === 'oauth2' || entry.auth?.type === 'app-credential') { this.failDegradedConnectorInstance(…); continue; } with its three-line comment (lines 2269–2283).
  • connector-credential-source.ts, Lendable.auth: Exclude<ConnectorInstanceAuth, { type: 'none' | 'oauth2' | 'app-credential' }> (line 84).
  • connector-credential-source.ts, the lendRefusal branch if (auth.type === 'oauth2' || auth.type === 'app-credential') { return … } with its comment (lines 118–126).
  • The tests that pin the stand-in: connector-materialization.test.ts describe "ADR-0143 D4 — a grant-bearing instance is held degraded…", and connector-credential-source.test.ts describe "[ADR-0143 D4] createConnectorCredentialSource — a grant-bearing kind is refused, not lent". Their CONTROL cases for the four static kinds stay.
  • content/docs/automation/connectors.mdx: the "A server that does not yet run the per-dispatch token resolution…" paragraph and the failure-modes row for a grant-bearing kind.

What S1 needs that this slice leaves out:

  • A provider that reads ConnectorProviderContext.credentialHandle. The member is typed here and nothing sets it yet.
  • The token-endpoint client-auth method choice (basic vs post).
  • The connection record that holds an authorization_code grant.
  • The mcp client_credentials path through dynamic client registration, when clientId is omitted.
  • The skill bullet named under Skills-docs: above.

Deviations from the dispatch order

  • The order said "Do not touch server packages". Widening ConnectorInstanceAuth turned the service-automation typecheck red (6 errors), so I reported it to the seat. The seat ruled A: touch only connector-credential-source.ts and plugin.ts, minimal and exhaustive, as above.
  • The order's lines were Clause-②: yes (widening) and Metadata-protocol: added. D7 removes an authorable value (integration/Connector:authentication type: 'oauth2'), so the seat ruled yes (narrowing) / narrowed, with a BREAKING changeset and the registered ADR-0087 entry. The lines are corrected here and in the changeset.
  • Beyond the expected landing, these files changed on the producer side: integration/connector.zod.ts (the entry rule and re-exports), shared/connector-auth.test.ts and integration/connector.test.ts (retired-variant tests), type-alias-convention.pin.test.ts (ADR-0122 gate), variant-docs.json, content/docs/automation/connectors.mdx, packages/spec/docs/SYNC_ARCHITECTURE.md (its L3 example is compiled by connector-author-shape.test.ts) and the qa fixture.

Verification

Head 4d12a2786bc03dfb6d046d7a31f9761cec235616 (includes a merge of origin/main).

Tests

Command Result
vitest run src/integration src/shared src/migrations src/type-alias-convention.pin.test.ts (spec) 54 files, 1335 tests, green
pnpm --filter @objectstack/spec typecheck (incl. test layer) exit 0
pnpm --filter @objectstack/types typecheck && test exit 0 · 825/825
pnpm --filter @objectstack/service-automation typecheck && test exit 0 · 2926/2926 (merged head)
pnpm --filter @objectstack/service-datasource typecheck && test exit 0 · 1069/1069
pnpm --filter @objectstack/downstream-contract typecheck + test/contract.test.ts exit 0 · 13/13

consumer-specifier-ledger.test.ts refuses locally because @objectstack/cli has no dist in this box. That is a prerequisite refusal, not a finding, and CI builds it.

Gates. dispatch-gates.mjs --commands derived 109 commands. I ran each one with </dev/null and recorded <command> :: exit <code> on disk. All 109 exit 0.

--ran reconciliation: "109 derived, 109 run, 0 NOT-MEASURED, 0 UNRUN". Four commands first refused for local reasons and were re-run green:

  • check-plugin-teardown-shape --self-test, on a shallow clone, after fetching its pinned commit.
  • The .d.ts-reading spec gates, against a stale dist after the rebase, after a rebuild.
  • check:skill-examples, with no client-react dist, after building it.
  • check:dual-build-cjs-loads, with no package dists, after a packages build.

One real finding, check:spec-parsed-alias (ADR-0122, 6 new aliases), is fixed by the pins. check-platform-checklist --changed-from origin/main: OK.

Ablations. I used scripts/ablation-replace.mjs. Each mutation landed (anchor 1 → 0) and was restored (blob == HEAD):

  • A1, the mcp-http exception removed: 3 red in connector-auth-grant-kinds.test.ts, the two mcp CONTROLs and the secret-without-client-id case.
  • A2, the D7 prescription removed: 2 red in connector-auth.test.ts.
  • A3, client_credentials no longer requiring credentialRef: 1 red.
  • A4, the plugin.ts degrade branch disabled: 2 red in connector-materialization.test.ts, one per kind.
  • A5, the lend refusal disabled: 2 red in connector-credential-source.test.ts, one per kind.

Dogfood battery. Full @objectstack/dogfood on the final head 4d12a2786, run under os-verify-lock as 6 vitest shards, each VERDICT command-exit 0. Result: Test Files 359 passed, 3 skipped (362). Tests 2891 passed, 20 skipped (2911). No failures. It was also green on the pre-merge head 4609d0cb6.

Acceptance notes

  • Located errors. Every refusal is pinned by code @ path. Each of the two members has a CONTROL accept, and so does provider: 'mcp' on http with the endpoints and clientId omitted (connector-auth-grant-kinds.test.ts):
    • a missing endpoint: custom @ auth.tokenUrl / auth.clientId / auth.authorizationUrl
    • an unknown grant: invalid_union @ auth.grant
    • an inline secret, token or pkce: unrecognized_keys @ auth with the key named
    • credentialRef without clientId, or client_credentials without a secret: custom @ auth.credentialRef
    • an unknown vendor: invalid_value @ auth.vendor
    • a missing app secret reference: invalid_type @ auth.credentialRef
    • signed / oauth1: invalid_union @ type
  • An inline secret is refused on both new members (ADR Acceptance).
  • ResolvedConnectorAuth is unchanged (four static kinds). @objectstack/types keeps re-exporting the moved handle types, and the datasource borrow path's importers typecheck and test green.
  • The changeset is .changeset/28137-connector-grant-bearing-auth.md: BREAKING, with <!-- adr-0087: registered connector-auth-config-oauth2-retired -->. check-adr-0087-registration and check-changeset-no-major are green.
  • A contract review at CONTRACT_REVIEW_TIER is owed before the queue. The triage seat runs it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01L9qibvmENgz1tZkajp5SuJ


Generated by Claude Code

…e oauth2 variant retired (ADR-0143 D2.1, D2.2, D7) [wip]

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibvmENgz1tZkajp5SuJ
…aded and refuse to lend it until per-dispatch resolution lands (ADR-0143 D4)

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibvmENgz1tZkajp5SuJ
…cs, ledger, fixture and changeset [wip]

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibvmENgz1tZkajp5SuJ
… reference docs and strictness counts; SYNC_ARCHITECTURE example off the retired variant [wip]

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibvmENgz1tZkajp5SuJ
…h pins (ADR-0122) [wip]

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibvmENgz1tZkajp5SuJ
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibvmENgz1tZkajp5SuJ
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation tests tooling labels Oct 30, 2026
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/downstream-contract, @objectstack/service-automation, @objectstack/spec, @objectstack/types, touching 48 documentable anchor(s). ⚠️ 8 changed file(s) yielded no anchor (packages/spec/api-surface/integration.json, packages/spec/authorable-surface/integration.json, packages/spec/declaration-map/integration.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

24 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 3a2f4c008d5897137c5e1f4e9ceec85f630d9aa7.

⛔ 6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 8 changed file(s) yielded no anchor (packages/spec/api-surface/integration.json, packages/spec/authorable-surface/integration.json, packages/spec/declaration-map/integration.json, …) — pages documenting those are invisible to this run
  • 12 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 58 of 210 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 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.
  • 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 — 170 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 3a2f4c008d5897137c5e1f4e9ceec85f630d9aa7 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from ebbe79274c5666500079a04572823ac7e62a984d — the merge of head c9b16a83881491e0566788582025c012aefad06a into base 3a2f4c008d5897137c5e1f4e9ceec85f630d9aa7, 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 ebbe79274c5666500079a04572823ac7e62a984d && git checkout ebbe79274c5666500079a04572823ac7e62a984d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3a2f4c008d5897137c5e1f4e9ceec85f630d9aa7 c9b16a83881491e0566788582025c012aefad06a && git checkout -B drift-repro 3a2f4c008d5897137c5e1f4e9ceec85f630d9aa7 && git merge --no-ff c9b16a83881491e0566788582025c012aefad06a

node scripts/docs-audit/affected-docs.mjs --json 3a2f4c008d5897137c5e1f4e9ceec85f630d9aa7

⚠️ 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 3a2f4c008d5897137c5e1f4e9ceec85f630d9aa7 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract-review-claim: session_01L9qibw58ehQ2gRHP2h2Puz · head 4d12a2786bc03dfb6d046d7a31f9761cec235616 · 2026-10-30T17:05Z

Contract review lane (Fable, CONTRACT_REVIEW_TIER): schema + narrowing face per record-recognisers.mjs --template --paths; reading the card, the PR diff and the head's check-runs only. The record follows on this head once its check-runs complete.

…restored to main

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibvmENgz1tZkajp5SuJ
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract-review-claim: session_01L9qibw58ehQ2gRHP2h2Puz · head 2ee1b72333a45afacf8d758b9b72a822c5371641 · 2026-10-30T17:11Z

Re-claim at the moved head: the claim at 4d12a278 (6528626788) lapsed when commit 2ee1b723 re-anchored ADR-0143's two ConnectorOAuth2Schema citations at the ADR-0087 record, clearing the check-adr-symbol-anchors red. The delta is that one prose file; the full ①②③④ record on the schema + narrowing face follows once this head's check-runs complete.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract-review-claim: session_01L9qibw58ehQ2gRHP2h2Puz · head c9b16a83881491e0566788582025c012aefad06a · 2026-10-30T17:14Z

Re-claim at the moved head: the claim at 4d12a278 (6528626788) lapsed when the head moved twice — 2ee1b723 re-anchored ADR-0143 (Tier H by check-governed-merges), then c9b16a83 restored the ADR byte-for-byte and kept the ConnectorOAuth2Schema token as a string literal in the D7 prescription, so both ADR anchors resolve and docs/adr/** has no diff. The diff against main is the one read at 4d12a278 plus that two-line prescription change (27 files, +1301/-306). The full ①②③④ record on the schema + narrowing face follows once this head's check-runs complete.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c9b16a83881491e0566788582025c012aefad06a
Local-runs: none
Scope: packages/spec/src/integration/connector-provider.ts, packages/spec/src/integration/connector.zod.ts, packages/spec/src/migrations/entries/semantic/18.connector-auth-config-oauth2-retired.ts, packages/spec/src/migrations/registry.ts.template, packages/spec/src/shared/connector-auth.zod.ts + docs/adr/0143-outbound-connector-auth-is-open-tier.md (read on main, the ruling the schema implements), packages/services/service-automation/src/plugin.ts and src/connector-credential-source.ts (the first consumers of the widened union, seat ruling A), packages/types/src/connector-credential-source.ts (the re-export of the moved handle types), packages/connectors/connector-mcp/src/mcp-provider.ts (read: the providerConfig.transport.kind vocabulary the mcp-http exception keys on), packages/qa/downstream-contract/src/additional-domains.fixtures.ts with its README (the frozen fixture the narrowing breaks), .changeset/28137-connector-grant-bearing-auth.md, and — the narrowing-face floor — every other path of the diff outside the prose faces (the five regenerated spec surfaces, variant-docs.json, the four test files, the pin test), all read.

Inputs: card #28137 body and its four comments (triage 6524666067, claim 6524922758, claim correction 6525266851 with the seat's ruling A and the narrowing re-declaration, os-dev-report 6528499617); PR #28213 body, 27-file list and the diff against main (+1301/-306); the check-runs on c9b16a83 (read 2026-10-30T17:37Z): 50 runs over 50 names, all completed, 41 success, 9 skipped, 0 failure; among the successes Auto Label, Build & E2E UI, Build Core, Build Docs, Build UI Deps, Check Changeset, Check Documentation Links, Check PR Size, Dogfood Regression Gate, Flag docs affected by code changes, Governed Surface Queue Guard, Lint & Repo Gates, Lint · CI, release & engine gates, Lint · ESLint, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped · query-options erasure, Lint · scoped · slot lookup & corpus, Lint · scoped · tooling self-tests, Live E2E UI, PR-body guards (claim · duplicate · Part-of · single-writer path), Plan Test Core shards, Spec property liveness, Test Core, Test UI, Type Check UI, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Type Check · workspace, TypeScript Type Check, filter, Test Core 1/8-8/8. Author user.login is objectstack-fleet[bot], so no login note is owed. The reviewer is not the drafter (session_01L9qibvmENgz1tZkajp5SuJ). Head history: the report's head 4d12a278 was read in full and went red on check-adr-symbol-anchors (ADR-0143 lines 88 and 237 anchored the deleted ConnectorOAuth2Schema); 2ee1b723 re-anchored the ADR and check-governed-merges put docs/adr/** at Tier H; the seat ruled the ADR restored byte-for-byte with the token kept as a string literal in the prescription, which is the head reviewed here (delta read: 4d12a278..c9b16a83 is two commits whose net diff is two lines of connector-auth.zod.ts, the CONNECTOR_AUTH_CONFIG_OAUTH2_RETIRED prescription now opening with the retired schema's name; docs/adr/** has no diff against main).

① Derived judgments

  • D2.1 oauth2 holds against ADR-0143 D2 item 1 key for key: grant closed to the two RFC 6749 grants (ConnectorOAuth2GrantSchema); one strictObject per grant; authorizationUrl only on authorization_code (refused by name on client_credentials); tokenUrl, scopes, resource (the ADR's "optional resource / audience indicator", with audience as a strict-shape alias); clientId a plain string; the client secret only as credentialRef, optional on authorization_code (a public client) and required together with clientId on client_credentials (dependentRequired, projectable, so the JSON Schema carries both rules). No PKCE key, and pkce is refused with the D2.1 "no switch" sentence. The endpoint and client requirement rides on DeclarativeConnectorEntrySchema because it needs the provider: refused at auth.tokenUrl / auth.clientId / auth.authorizationUrl unless provider: 'mcp' with providerConfig.transport.kind === 'http'. That is the spelling connector-mcp's mcp-provider.ts validates (transport.kind must be 'stdio' or 'http'), so the exception fires for exactly the transport that runs the MCP authorization flow, and a stdio mcp instance meets the ordinary refusals (pinned).
  • D2.2 app-credential holds: vendor closed to feishu | dingtalk | wecom, appId non-secret with the vendors' own spellings (appKey, corpId, clientId) as aliases, credentialRef required; appSecret / corpSecret / secret and the token keys refused by name. D2.3: no signed member; the closed-union test pins that signed is refused at type.
  • D3 at the publish door: every secret or token key an author could reach for on either kind is refused by name with the credentialRef prescription (unrecognized_keys @ auth), the ADR Acceptance's "inline secret on any variant refused at publish with a located error"; the keys that remain (clientId, appId, endpoints, scopes, resource) carry no secret.
  • D7: ConnectorOAuth2Schema leaves ConnectorAuthConfigSchema; the union's error map raises the prescription only for input.type === 'oauth2' (the ViewDataSchema provider: 'api' precedent) and every other unknown discriminator keeps zod's own report (CONTROL pinned). The ADR-0087 D3 semantic entry connector-auth-config-oauth2-retired names surface, replacement, reason and acceptance criteria, with its STEP18_RATIONALE fragment; no D2 conversion is the right disposition, since the replacement lives on another surface and needs a secret moved out of the source. ResolvedConnectorAuth is unchanged (four static kinds), so ConnectorProviderContext.auth and every provider factory are untouched.
  • D4: the four handle types move into packages/spec/src/integration/connector-provider.ts structurally identical to the @objectstack/types originals (same members, same doc contracts, the 503 refusal semantics kept), and @objectstack/types re-exports them under the same names, so IConnectorCredentialSource.borrow and its drivers keep their spelling; @objectstack/types already depends on @objectstack/spec (workspace:*). credentialHandle? on ConnectorProviderContext is optional and set by no host today — typed here, served by S1.
  • First consumers under seat ruling A: the reconcile's grant-bearing branch sits before the [#27237] credential resolution and before resolveInstanceAuth, so a grant-bearing entry is held degraded with a reason naming its kind and is never materialized; resolveInstanceAuth's parameter is narrowed with Exclude<…, GrantBearingInstanceAuth>, so the type system refuses a future path that hands it one. connector-credential-source.ts refuses a datasource borrow of such an instance in lendRefusal (422) before any handle is minted. Both stand-ins are enumerated for S1 in the PR body.
  • The other union consumers (connector-mcp, connector-openapi, connector-rest, driver-odata) read ResolvedConnectorAuth / BorrowedConnectorAuth, not ConnectorInstanceAuth, so the widening cannot reach them; no ui/ reader of the instance auth kinds exists.

② Semver level

③ Boundary flags

  • Served at publish: DeclarativeConnectorEntrySchema is parsed by both authoring doors, so the new refusals land wherever an entry is authored; connector-auth-grant-kinds.test.ts pins each by code @ path with a CONTROL accept per member and for the mcp-http omission.
  • Served at runtime, until S1: a grant-bearing instance degrades through the existing failDegradedConnectorInstance (so the connectors listing and the drift check see it as any other degraded instance), and a datasource borrow answers 422 VALIDATION_ERROR naming the kind; nothing dispatches to an upstream unauthenticated.
  • Governed: docs/adr/** is untouched on this head; ADR-0143's two ConnectorOAuth2Schema anchors resolve through the string-literal token in the prescription constant (line 51 of connector-auth.zod.ts carries the token in a string literal, which symbol-anchors.mjs resolves, while every other mention is comment prose it strips). skills/** untouched: the integration-recipes.md "Not yet" OAuth bullet stays correct until S1 serves the kind (the PR's Skills-docs: line, the dev's out-of-scope finding c).
  • Tier: whole-PR diff +1301/-306 over 27 files, no docs/adr/**, skills/** or other governed row on this head ⇒ Tier S; the Tier H on 2ee1b723 was the ADR edit alone.
  • Author objectstack-fleet[bot] ⇒ no login note. Dependency: S1 (connectors: outbound OAuth in the open tier — connection-scoped authorization-code / client-credentials / app-credential tokens, refreshed per dispatch, for the openapi, mcp and rest providers #26702) replaces the stand-in lines the PR body enumerates; the skill bullet and evals/integrations-declared.json ride with S1.

④ Design judgment

  • Schema against the ruling: each D2.1 / D2.2 key maps to a sentence of ADR-0143 D2 (① above); nothing the ADR forbids is authorable (a secret, a token, a PKCE switch, signed), nothing it requires is missing (closed enums, located refusals, the mcp-http omission, a pre-registered mcp client accepted). The one D2.1 topic the shape does not carry — how a pre-registered client authenticates at the token endpoint — the ADR leaves to the runtime, and triage 6524666067 ruled it the spec author's weighing that "does not get decided in the PR"; leaving it out of the shape is the conservative reading, and S1 adds a key under a decision card only if it needs one. Holds.
  • First consumer: the only consumers today are the two stand-ins in service-automation, both fail-closed; the D4 types are consumed unchanged by the datasource borrow path. Holds.
  • The narrowing against the published spec on main: the retired value had no first-party author (the frozen qa fixture was the single hit) and no runtime ever read its secret, so the narrowing takes nothing a consumer could use and makes the spec's own refusal real (no inline secret at rest); the prescription names the replacement surface and the D3 entry carries the judgement an upgrader owes. Holds.
  • Risk read: dependentRequired({ credentialRef: ['clientId'], clientId: ['credentialRef'] }) on client_credentials also lets an mcp-http instance omit both (dynamic registration), consistent with D2.1 and pinned; resource is z.string().url(), matching RFC 8707's absolute-URI form. A grant-bearing entry can degrade only after the entry parsed, so an operator sees the named reason, never a silent skip.

Should-fix, none blocking: (S1) lint-flow-credential-literals.ts steer strings and rule-explanations.ts still name only the static kinds — right until S1 serves a grant, then they widen with the skill bullet.

Implemented-by: claude/project-thread-a31eac
Reviewed-by: session_01L9qibw58ehQ2gRHP2h2Puz

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#1 · os-tesla · session_01L9qibwb6hBqszPTYJobitm

维护者速读

  • 改了什么: 连接器实例的 auth 新增两种需要换取令牌的类型:oauth2(授权码 / 客户端凭证)和 app-credential(飞书、钉钉、企业微信)。密钥只能写引用。旧的运行时 oauth2 写法 authentication: { type: 'oauth2' } 被移除,并给出改写提示(ADR-0087 条目)。
  • 为什么改: 以前对接要 OAuth 的系统在开源版里无处声明;旧写法把密钥和刷新令牌明文写进配置,也从来没人执行。ADR-0143 把这类认证纳入开源层,这是第一步 S0,只定形状。
  • 风险与回滚: 写了旧 oauth2 的连接器会被拒绝,并附迁移提示。新类型的实例在服务端(S1,connectors: outbound OAuth in the open tier — connection-scoped authorization-code / client-credentials / app-credential tokens, refreshed per dispatch, for the openapi, mcp and rest providers #26702)真正支持前,一律标为“降级、不调用上游”,数据源借用返回 422,不会出现未认证调用。回滚即 revert。
  • 先看: packages/spec/src/shared/connector-auth.zod.ts,以及 packages/services/service-automation/src/plugin.ts 新加的降级分支。
  • 两分钟验证: pnpm --filter @objectstack/spec exec vitest run src/integration/connector-auth-grant-kinds.test.ts src/shared/connector-auth.test.ts。
  • 需要你做的: 无。不属于受管面,契约复审 PASS(评论 6529303069),由席位落地。
  • 席位意见: ACCEPT,可落地。

席位核对 · ACCEPT · domain:spec seat 1 (#6017) · 2026-10-30T17:54Z

Head c9b16a83881491e0566788582025c012aefad06a. Card #28137 (p1, security), claim 6524922758, claim correction (Clause-②: yes (narrowing), Metadata-protocol: narrowed, ruling A). The maintainer's ruling, verbatim: 「按 pm-dispatch 审过的pr你可以自主落地」.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 30, 2026 18:03
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit 3238750 Oct 30, 2026
57 of 58 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-a31eac branch October 30, 2026 19:04
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/xl tests tooling

Projects

None yet

1 participant