Repository navigation
feat(spec): an oauth2 authorization_code connector instance can take its OAuth client from the installer — clientSource: 'installer' - #28920
Conversation
… from the installer — `clientSource: 'installer'` ADR-0143 D2.1 kept the client in the definition: a packaged `clientId`, or none on `provider: 'mcp'` over http, where the provider registers one. The administrator of a deployment the publisher never saw had no way to bring the client the vendor accepts for that deployment's callback. `clientSource: 'installer'` declares that the administrator of each deployment registers their own client and enters it on the instance's connection. The definition then names no `clientId` and no `credentialRef`, on any provider; the endpoints stay declared (or discovered over MCP http). A packaged `clientId` without the key stays accepted for private apps, so no existing input changes its result. `client_credentials` refuses the key with guidance: it keeps no connection. Claude-Session: https://claude.ai/code/session_01ADHkmPS39tCwZRCvghgPQf Co-authored-by: Claude <noreply@anthropic.com>
…e for `clientSource` Each grant's `clientId` row now says only where else a client may come from for that grant. Claude-Session: https://claude.ai/code/session_01ADHkmPS39tCwZRCvghgPQf Co-authored-by: Claude <noreply@anthropic.com>
…supply the OAuth client Appends a dated note under D2: an `authorization_code` instance that declares `clientSource: 'installer'` takes its client from the administrator of each deployment, kept on the instance's connection beside a dynamically registered client. A packaged `clientId` is kept for private apps; `client_credentials` is unchanged; a per-account API host is a follow-up. Adds the changeset. Claude-Session: https://claude.ai/code/session_01ADHkmPS39tCwZRCvghgPQf Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 168 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 0ebaf2d9135eabf4f92124a25f7e3fb5aede142e && git checkout 0ebaf2d9135eabf4f92124a25f7e3fb5aede142e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin cae71fedfc8f04d35694da55ab74f9d680da0c5f 82e993b57cac93ec12c5949c4163dde87528c45c && git checkout -B drift-repro cae71fedfc8f04d35694da55ab74f9d680da0c5f && git merge --no-ff 82e993b57cac93ec12c5949c4163dde87528c45c
node scripts/docs-audit/affected-docs.mjs --json cae71fedfc8f04d35694da55ab74f9d680da0c5f
|
|
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS Head-sha: 维护者速读
席位意见:可落地,等键名批准、契约复审和受管批准。 席位核对 — ACCEPT ·
|
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Contract-review-claim: Taken on the lane's Generated by Claude Code |
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Contract reviewServed-tier: Inputs read: card #28864 body and all three comments (claim 6567679603, triage, dev report 6568922117); PR #28920 body, file list, the three PR comments (docs-drift, seat ACCEPT 6568941768, this seat's claim), the net diff against Check-runs on ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Security. No security-relevant finding. The widening opens no inline-secret path (D3 holds on both shapes), the published spec stays strictly narrower than or equal to the runtime on every door, and the interim divergence on MCP-over-http substitutes a dynamically registered client, not a credential exposure. Must-fix list
Should-fix list
Implemented-by: VERDICT: FAIL Generated by Claude Code |
… server slice reads it Contract review 6569108322 on #28920: - The key's describe carries `[PLANNED — not enforced]` and says what an instance that declares it does meanwhile (held degraded off MCP over http; dynamic registration on MCP over http). It names ADR-0143 D2.1, not the tracker id, because check:doc-authoring refuses an issue id in customer-facing spec prose; the adjacent comment names #28866. Reference regenerated. - The liveness ledger's `connector/auth` row records the key as `planned` until #28866: the walker does not descend into the auth union, so that row is the only ledger coordinate over it. - A test pins the marker, the ADR reference, no tracker id in the describe, and the ledger sentence, so #28866 drops them together. - The authorization_code `credentialRef` describe names the installer form. - ADR-0143: a pointer under D3's persisted list to the 2026-11-02 D2.1 note, and the note's Slices bullet states the interim behaviour. - The changeset names the marker and the ledger row. Claude-Session: https://claude.ai/code/session_01ADHkmPS39tCwZRCvghgPQf Co-authored-by: Claude <noreply@anthropic.com>
|
domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS Head-sha: 维护者速读
席位意见:可落地,等契约复审、键名批准和受管批准。 席位核对 — ACCEPT (round 2) ·
|
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Contract-review-claim: The delta re-review of this seat's FAIL (6569108322, head Generated by Claude Code |
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Contract reviewServed-tier: This record continues this seat's FAIL on head Inputs read: card #28864 body and all three comments (claim 6567679603, triage, dev report 6568922117 — no new comment since the FAIL); PR #28920 body, file list (unchanged, 9 files), all five PR comments (docs-drift, seat ACCEPT 6568941768, this seat's claim, this seat's FAIL 6569108322, seat ACCEPT round 2 6570275724), the net diff against Check-runs on ① Derived judgments
② Semver levelUnchanged: ③ Boundary flags
④ Design judgment
Security. No security-relevant change in the delta: describe prose, a ledger note, a test, ADR prose, a changeset sentence and a regenerated reference. The previous record's security judgment stands. Must-fix list
Should-fix list
Landing gates, not review findings: (a) the maintainer's approval of the key name Implemented-by: VERDICT: PASS Generated by Claude Code |
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Maintainer ruling: the public key name
|
Requested by Steve · project thread
Fixes #28864
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: follow-up owed —
skills/objectstack-automation/references/integration-recipes.md(its e-signature recipe pinsclientId: 'objectstack-prod', the publisher-owned client ruling 6566728470 rules out for a listing) andcontent/docs/automation/connectors.mdx(theoauth2section). Both should teachclientSource: 'installer'once the server slice (#28866) serves it: taught today, the key sends an author to an instance the service holds degraded. The skill file is governed (Tier H).Ruling-ref: 6566728470
维护者速读
oauth2的authorization_code连接器实例新增可选键clientSource: 'installer',声明「OAuth 客户端由安装方自带」:每个部署的管理员自己到厂商处登记客户端(回调填本部署的地址),把 client id 和 secret 填在连接上;定义里不写clientId、credentialRef,两者同写会被拒。ADR-0143 D2.1 追加一条修订说明。clientId只在发布者登记过的部署上能连上。[Decision] #28277 DocuSign app: where an open-edition OAuth listing's client id comes from, whenclientIdis fixed by the package and the redirect URI is per deployment #28702 裁决 B(「同意」)要求开源版 OAuth 应用不带官方客户端、由安装方自带,DocuSign 应用等这一步。clientId的私有应用照旧可用。服务端还不读这个键(automation: asys_connector_connectionstores and uses the installer-supplied OAuth client for anauthorization_codeinstance (#28702 B, server slice) #28866 再做):非 MCP 实例声明它会被标为降级,绝不会无客户端发请求。回滚即撤掉本 PR。packages/spec/src/integration/connector.zod.ts的 ADR-0143 D2.1 规则块(两处installerClient);packages/spec/src/shared/connector-auth.zod.ts的clientSource与上方 docblock;docs/adr/0143-outbound-connector-auth-is-open-tier.mdD2 末尾的新修订说明。pnpm --filter @objectstack/spec exec vitest run src/integration/connector-auth-grant-kinds.test.ts,应看到 29 条全过(其中 9 条是本次新增)。clientSource(值'installer')。ADR 受管,合并要等你的批准。What changes
clientSourceon theauthorization_codeinstance shape (ConnectorInstanceOAuth2AuthorizationCodeAuthSchema,@objectstack/specand@objectstack/spec/integration): an optional closed enum with one member,'installer'. The administrator of each deployment registers their own client with the vendor, against that deployment's callback, and enters its id and secret on the instance's connection. The definition then names no client and no secret.clientIdbeside it is refused at publish,custom @ auth.clientId, on every provider: the definition would name two clients. The rule is inDeclarativeConnectorEntrySchema's ADR-0143 D2.1 block (claim amended to include it), beside the other client-key rules.provider: 'mcp'over http, the installer form no longer needsclientId.tokenUrlandauthorizationUrlare still required there: the installer owns the client, not the vendor's authorization server.credentialRefwithoutclientIdstays refused atauth.credentialRefby the shape's existingdependentRequired; its message now names the installer form.grant: 'client_credentials'the key is refused as an unknown key with a guidance entry: that grant keeps no connection record, so there is nowhere to enter a client.clientIdrow of each grant now says only where else a client may come from for that grant (oauth2SharedShape(clientIdNote)).oauth2entry off MCP-over-http was refused atauth.clientIdwheneverclientIdwas absent. After, the same holds unless the entry isauthorization_codewithclientSource: 'installer'. A new refusal fires only for an entry that carriesclientSource, a key every earlier input was refused for (unrecognized_keys). No input that parsed before is refused now, and no input refused before is accepted unless it carriesclientSource.clientIdis kept,client_credentialsunchanged, and the per-account API host left as a follow-up.content/docs/references/integration/connector.mdx(regenerated),packages/spec/authorable-surface/integration.jsongainsConnectorInstanceOAuth2AuthorizationCodeAuth:clientSource. No export moved (check:api-surfacegreen).docs/adr/README.mddoes not move (adr-indexOK)..changeset/28864-oauth2-installer-client.md,@objectstack/spec: minor.Forced test edits
packages/spec/src/integration/connector-credential-redaction.test.ts: the fail-closed censusDECLARED_NOT_A_SECRETlistsclientSource. The census reds on any newly declared instance-auth key until it is confirmed not to be a secret;clientSourceis a closed enum value.Why
Ruling 6566728470 on #28702 (letter B, maintainer 「同意」 2026-11-02): an open-edition OAuth connector app ships no ObjectStack-owned
clientIdor secret, and the administrator of each deployment registers their own integration key against that deployment's callback and enters it on the connection. Today the client is a literal of the definition (clientId), so whoever publishes the package fixes it, and the vendor's consent page refuses it on every deployment whose callback that client's owner never registered. This is the spec slice; #28866 (server) and #28869 (ui) areBlocked-byit, and #28277 (DocuSign) waits on all three.Tests
packages/spec/src/integration/connector-auth-grant-kinds.test.ts, a new describe with 9 tests, each refusal pinned bycode @ path:openapi; the installer form onmcpover http with no endpoints; a packagedclientIdwithoutclientSource(the accept set does not move).clientIdbesideclientSource(alone, withcredentialRef, and onmcpover http) atauth.clientId;credentialRefwith the installer form atauth.credentialRef; the endpoints still required off MCP-over-http (auth.tokenUrl,auth.authorizationUrl, and noauth.clientId); an unknown value atauth.clientSource(invalid_value);clientSourceonclient_credentials(unrecognized_keys).authorizationUrl,clientSource,grant,scopes,tokenUrl,type: no client, no secret.Acceptance notes
clientIdchoice: kept, for private apps. Measured onorigin/main76bf6c50: no example, packaged app orconnector-*source authors anoauth2instance; the authors are test fixtures (spec, service-automation, connector-openapi, lint, ui app-shell), the docs example incontent/docs/automation/connectors.mdx, the skill recipe above, and ADR-0143 Q3's HubSpot case (a pre-registered client on an MCP server without dynamic registration). Refusing a packagedclientIdwould refuse every one of those inputs, a narrowing beside this widening, and the schema cannot tell a private app (whose author registers every callback) from a listing. So it stays accepted, and the ruling's listing rule is held where listings are reviewed (suggested carrier: [Epic] Marketplace and connector auto-shelf — E1 platform prerequisites (openapi/mcp hardening, outbound OAuth, request signing), E2 administrator credential hand-off, E3 author recipes and handbook, E4 the first shelf of official connector apps #27293).clientIdexclusion is on the entry, not the shape. The closed list inshared/refinement-projection.tshas no arm for "this key excludes that one", so a shape-level.refinewould add a dropped-refinement site to every published schema that embeds the auth shape. The entry'ssuperRefineis already marked as not stated (integration/DeclarativeConnectorEntryoutindropped-refinements.baseline.json), holds the other provider-dependent client-key rules, and is what both authoring doors parse. A standaloneConnectorInstanceAuthSchema.parseaccepts the pair; no runtime door parses the auth shape alone.sys_connector_connectionstores and uses the installer-supplied OAuth client for anauthorization_codeinstance (#28702 B, server slice) #28866. The automation service does not readclientSource. Off MCP,oauth2NotServableReason(packages/services/service-automation/src/plugin.ts) holds such an instance degraded for a missingclientId, never called without a client. Onmcpover http,mcpDiscoveryServerUrlseesclientIdundefined and runs the MCP authorization flow, registering a client where the server allows it. The changeset says both.sys_connector_connectionstores and uses the installer-supplied OAuth client for anauthorization_codeinstance (#28702 B, server slice) #28866, read on this head):plugin.ts#oauth2NotServableReasonand#mcpDiscoveryServerUrl(treat the installer form as supplying the client),#mcpAuthorizationClient(kept.registered === (auth.clientId === undefined), and no registration for the installer form), the connection door's begin step (take the client id and secret),oauth2-grant/grant-handle.ts#createOAuth2GrantHandle(declaredClient), andsys-connector-connection.object.ts'sregistered_clientdescription. UI slice (console: the connector connection form asks the administrator for the OAuth client (client id + secret) when the instance takes an installer-supplied client (#28702 B, ui slice) #28869):ui/packages/app-shell/src/views/setup/connectorConnections.tsandConnectorConnectionsPage.tsx(the form, with the deployment's redirect URI).Verification
All readings on head
23acaa6a(this branch withorigin/mainb15b634cmerged in) unless a line says otherwise; every heavy run went throughos-verify-lock.sh, whoseVERDICT command-exitline is quoted.Package suites and type checks (packages/spec and every consumer that parses a connector entry)
pnpm turbo run build --concurrency=2 --filter='./packages/**': Tasks 78 successful, 78 total. VERDICT command-exit 0.pnpm --filter @objectstack/spec check:generated: all 14 generated artifacts up to date (exit 0). Before the merge it namedcontent/docs/references/**stale once;gen:docsregenerated it, and the build wroteauthorable-surface/integration.json. Both are committed.pnpm --filter @objectstack/spec test: Test Files 789 passed (789), Tests 23324 passed, 1 todo. VERDICT command-exit 0.pnpm --filter @objectstack/spec typecheck(includescheck:test-typecheck): VERDICT command-exit 0.pnpm --filter @objectstack/service-automation test: Test Files 255 passed (255), Tests 3281 passed (3281).typecheck: VERDICT command-exit 0.pnpm --filter @objectstack/connector-NAME test: imap 42/42, mcp 102/102, openapi 109/109, rest 57/57, slack 10/10, each VERDICT command-exit 0. Theirtypecheck(on497c4a04, before the merge, which did not touch them): all five exit 0.497c4a04):@objectstack/lintsrc/lint-flow-credential-literals.test.ts17/17;@objectstack/objectqlsrc/protocol-meta.test.ts93/93;@objectstack/metadatasrc/serializers/typescript-serializer-annotation.test.ts7/7;uipackages/app-shell/src/views/metadata-admin/clientValidation.optOuts.test.tsandpackages/app-shell/src/views/setup/ConnectorConnectionsPage.test.tsx32/32 (run fromui/, as its vitest guard requires), each VERDICT command-exit 0.eslint --no-inline-configover the four changed.tsfiles: exit 0.Ablations (each landed on disk through ablation-replace and restored to the HEAD blob)
connector.zod.ts, the exclusion guardif (installerClient && entry.auth.clientId !== undefined) {→if (false && …) {:connector-auth-grant-kinds.test.ts1 failed | 28 passed ("refuses a packagedclientIdbesideclientSource"). Restored: blobeebb0a85== HEAD.connector.zod.ts, the relaxationif (entry.auth.clientId === undefined && !installerClient) {→ without&& !installerClient: 4 failed | 25 passed (the openapi CONTROL accept, thecredentialRefrefusal's single issue, the endpoints-still-required list, the declared-keys pin). Restored: blob == HEAD.packages/services/service-automationtypedConnectorInstanceAuthfrom@objectstack/spec/integration:clientSource: 'installer'compiles,clientSource: 'publisher'is TS2322 ('"publisher"' is not assignable to type '"installer"'), so the consumer reads the rebuilt.d.ts. Probe deleted;git statusclean.Ablation and probe readings were taken on
497c4a04; the merge brought no change to either file.Derived gate families (dispatch-gates)
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat23acaa6a: 107 commands, change set 8 paths vs merge baseb15b634c1, 280 changed lines (+249 / -31). Same list as at497c4a04.23acaa6a(and all 107 on497c4a04). On the first runcheck-plugin-teardown-shape.mjs --self-testexited 3: its pinned fixture commit was outside the shallow clone. Aftergit fetch --depth=1 origin 621a4876…it reported 48 cases pass, exit 0.dispatch-gates --ran: "107 derived famil(ies) accounted for — 107 run, 0 NOT-MEASURED (a DERIVED zero …)".check-changeset-no-major.mjs --base origin/main --event(this body, offline): exit 0, LEVEL AXISyes (widening)+added,@objectstack/spec: minor.check:adr-symbol-anchors(2977 anchors resolve),check:adr-links,check:adr-anchors,check:adr-index(README current): all exit 0.check:skill-frame-freshnessexit 0.check:dev-prereqsexit 1 is a box precondition (35ui/workspace dists not built in this container; thepackages/**build is 78/78), not a reading about this diff.!in the title and no(narrowing)arm.Human Minimum
packages/specholds no runtime logic.pnpm lintpasses.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code