Repository navigation
feat(spec)!: oauth2 and app-credential connector instance auth; the runtime oauth2 variant retired (ADR-0143 S0) - #28213
Conversation
…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
…nifests [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
… [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
📓 Docs Drift CheckThis PR changes 4 package(s): 24 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 170 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # 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
|
…ADR-0087 record Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvmENgz1tZkajp5SuJ
|
domain:spec#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: Contract review lane (Fable, CONTRACT_REVIEW_TIER): schema + narrowing face per |
…restored to main Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibvmENgz1tZkajp5SuJ
|
domain:spec#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: Re-claim at the moved head: the claim at |
|
domain:spec#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: Re-claim at the moved head: the claim at |
|
domain:spec#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract reviewServed-tier: 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 ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Should-fix, none blocking: (S1) Implemented-by: VERDICT: PASS |
|
domain:spec#1 · os-tesla · session_01L9qibwb6hBqszPTYJobitm 维护者速读
席位核对 · ACCEPT ·
|
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写法被移除并给出改写提示。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,看到每个拒绝都有错误码和位置、每种类型都有正例通过。What changes
@objectstack/specConnectorInstanceAuthSchemagains two members (ADR-0143 D2.1, D2.2). Both are closed (strictObject), and both carry their secret only ascredentialRef.oauth2is a union ongrant, one strict shape per grant.authorization_codetakesauthorizationUrl,tokenUrl,scopes,resource,clientIdand an optionalcredentialRef(a public client has no secret).client_credentialshas noauthorizationUrlkey, and itscredentialRefis required.dependentRequiredrefinement, so the JSON Schema carries the rule and no dropped-refinements entry is owed.app-credentialtakesvendor(feishu|dingtalk|wecom),appIdand a requiredcredentialRef.credentialRefprescription:clientSecret,secret,accessToken,refreshToken,tokenandpkceonoauth2;appSecret,secret,corpSecret,accessTokenandtokenonapp-credential. The aliasesaudience→resourceandappKey/corpId/clientId→appIdanswer with the right key.signedmember (connectors: request-signing auth kind for the openapi and rest providers (RSA2 / HMAC canonical-request signers: Alipay, WeChat Pay, Aliyun, Tencent Cloud, e签宝) — the vendor-auth half that #26702 does not cover #26710, D2.3).DeclarativeConnectorEntrySchema(both authoring doors), anoauth2instance must nametokenUrlandclientId, plusauthorizationUrlonauthorization_code. Each missing key is refused atauth.<key>. The one exception isprovider: 'mcp'over anhttptransport, 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.ConnectorOAuth2SchemaleavesConnectorAuthConfigSchema.authentication: { type: 'oauth2' }is refused attypewith a prescription that names ADR-0143 D7 and the instanceauththat replaces it. The union's error map fires only for that literal, theViewDataSchemaprovider: 'api'precedent.ResolvedConnectorAuthstays the four static kinds.integration/Connector:authentication, is authorable, hence the narrowing line.connector-auth-config-oauth2-retiredrecords the change, with itsSTEP18_RATIONALEfragment (order 115).packages/spec/src/integration/connector-provider.ts:ConnectorCredentialHandle,BorrowedConnectorCredential,BorrowedConnectorAuthandConnectorCredentialRequest, structurally identical.ConnectorProviderContextgainsreadonly credentialHandle?: ConnectorCredentialHandle.client_secret_basicvsclient_secret_post) to the runtime that calls the token endpoint, so S1 owns it. The seat accepted this as an S1 choice.type-alias-convention.pin.test.ts(count 795 → 801, with its receipt).@objectstack/types:connector-credential-source.tsre-exports the four handle types from@objectstack/spec/integrationunder 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: anoauth2orapp-credentialinstance is held degraded through the existingfailDegradedConnectorInstancepath, 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 with422 VALIDATION_ERROR, naming the kind and the instance.Docs and fixtures
content/docs/automation/connectors.mdx: "Where isoauth2?" is replaced by "Grant-bearing kinds:oauth2andapp-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 dropsjwtandsaml, which were never variants.variant-docs.json: re-keyed for the three unions.packages/qa/downstream-contractDcConnectorchanges per its README, because the break is intended and documented here:authenticationis nowbearer, 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, theGrantBearingInstanceAuthtype andgrantBearingNotServedReason()(lines 402–417 at head).plugin.ts, the narrowedresolveInstanceAuth(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 branchif (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, thelendRefusalbranchif (auth.type === 'oauth2' || auth.type === 'app-credential') { return … }with its comment (lines 118–126).connector-materialization.test.tsdescribe "ADR-0143 D4 — a grant-bearing instance is held degraded…", andconnector-credential-source.test.tsdescribe "[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:
ConnectorProviderContext.credentialHandle. The member is typed here and nothing sets it yet.authorization_codegrant.client_credentialspath through dynamic client registration, whenclientIdis omitted.Skills-docs:above.Deviations from the dispatch order
ConnectorInstanceAuthturned the service-automation typecheck red (6 errors), so I reported it to the seat. The seat ruled A: touch onlyconnector-credential-source.tsandplugin.ts, minimal and exhaustive, as above.Clause-②: yes (widening)andMetadata-protocol: added. D7 removes an authorable value (integration/Connector:authenticationtype: 'oauth2'), so the seat ruledyes (narrowing)/narrowed, with a BREAKING changeset and the registered ADR-0087 entry. The lines are corrected here and in the changeset.integration/connector.zod.ts(the entry rule and re-exports),shared/connector-auth.test.tsandintegration/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 byconnector-author-shape.test.ts) and the qa fixture.Verification
Head
4d12a2786bc03dfb6d046d7a31f9761cec235616(includes a merge oforigin/main).Tests
vitest run src/integration src/shared src/migrations src/type-alias-convention.pin.test.ts(spec)pnpm --filter @objectstack/spec typecheck(incl. test layer)pnpm --filter @objectstack/types typecheck && testpnpm --filter @objectstack/service-automation typecheck && testpnpm --filter @objectstack/service-datasource typecheck && testpnpm --filter @objectstack/downstream-contract typecheck+test/contract.test.tsconsumer-specifier-ledger.test.tsrefuses locally because@objectstack/clihas no dist in this box. That is a prerequisite refusal, not a finding, and CI builds it.Gates.
dispatch-gates.mjs --commandsderived 109 commands. I ran each one with</dev/nulland recorded<command> :: exit <code>on disk. All 109 exit 0.--ranreconciliation: "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..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):connector-auth-grant-kinds.test.ts, the two mcp CONTROLs and the secret-without-client-id case.connector-auth.test.ts.client_credentialsno longer requiringcredentialRef: 1 red.plugin.tsdegrade branch disabled: 2 red inconnector-materialization.test.ts, one per kind.connector-credential-source.test.ts, one per kind.Dogfood battery. Full
@objectstack/dogfoodon the final head4d12a2786, run underos-verify-lockas 6 vitest shards, eachVERDICT 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 head4609d0cb6.Acceptance notes
code @ path. Each of the two members has a CONTROL accept, and so doesprovider: 'mcp'on http with the endpoints andclientIdomitted (connector-auth-grant-kinds.test.ts):custom @ auth.tokenUrl/auth.clientId/auth.authorizationUrlinvalid_union @ auth.grantpkce:unrecognized_keys @ authwith the key namedcredentialRefwithoutclientId, orclient_credentialswithout a secret:custom @ auth.credentialRefinvalid_value @ auth.vendorinvalid_type @ auth.credentialRefsigned/oauth1:invalid_union @ typeResolvedConnectorAuthis unchanged (four static kinds).@objectstack/typeskeeps re-exporting the moved handle types, and the datasource borrow path's importers typecheck and test green..changeset/28137-connector-grant-bearing-auth.md: BREAKING, with<!-- adr-0087: registered connector-auth-config-oauth2-retired -->.check-adr-0087-registrationandcheck-changeset-no-majorare green.CONTRACT_REVIEW_TIERis owed before the queue. The triage seat runs it.🤖 Generated with Claude Code
https://claude.ai/code/session_01L9qibvmENgz1tZkajp5SuJ
Generated by Claude Code