Skip to content

docs(adr): ADR-0143 — outbound connector auth is open tier: OAuth2 grants, vendor app-credential exchange and request signing in one declarative auth contract - #26833

Merged
os-zhuang merged 4 commits into
mainfrom
claude/project-thread-egxrcr
Oct 27, 2026
Merged

os-zhuang merged 4 commits into
mainfrom
claude/project-thread-egxrcr

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread
Refs #26768
Clause-②: no
Metadata-protocol: none

维护者速读

What changes

  • New docs/adr/0143-outbound-connector-auth-is-open-tier.md (Proposed). It decides, in one record:
    • D1, the tier move: outbound connector auth is open tier. Vault backends, curated or paid catalogs, marketplace and sandboxed stdio keep their placement.
    • D2, three closed authored variants: oauth2 (authorization_code with mandatory S256 PKCE and refresh, client_credentials, MCP authorization discovery), app-credential (feishu / dingtalk / wecom) and signed (a closed scheme enum; a member enters only with its signer and vendor vectors).
    • D3: secrets stay credentialRef, and tokens live in an encrypted connection record, never in metadata.
    • D4: grant-bearing kinds resolve per dispatch, refreshing on expiry and once on a 401 with single-flight refresh. A revoked grant degrades the instance with a reason and never fails boot.
    • D5: a server-side Connect flow. D6: a grant is never a sign-in method. D7: the unused runtime ConnectorOAuth2Schema goes to the spec slice under ADR-0087.
    • The four maintainer decisions are stated as open questions, each with options and a recommendation, and none is decided: Q1 connection ownership / execution identity (rec. organization-shared), Q2 storage (rec. a dedicated connection object, with measured reasons sys_account does not fit), Q3 first products (rec. HubSpot MCP, Feishu, Alipay v3 sandbox), Q4 first signing schemes (rec. alipay-rsa2, aliyun-acs3-hmac-sha256, tencentcloud-tc3-hmac-sha256).
  • Dated, append-only amendment notes (each adds an **Amended by**: header line and a blockquote; no existing line changes):
    • ADR-0015: attribution only. It never drew the split others cite it for.
    • ADR-0018 "Out of scope (deliberately not baseline)": where the connector line was first drawn.
    • ADR-0022 §4: the connector half moves. The sys_channel_account lifecycle does not.
    • ADR-0023 §4.
    • ADR-0024 §3–§4.
    • ADR-0097 §3 and its "Open / enterprise line".
  • docs/adr/README.md, regenerated by node scripts/adr-index.mjs (one new row and the summary counts).
  • Forced test edits: none.

Tests

Docs-only diff; no package touched, so no build closure, unit test or typecheck is owed. The derived ADR gate family ran on the final head (see Verification).

Acceptance notes

  • Governing text, re-checked on origin/main at 0ea90c7a.
    • git grep -n "deliberately omits OAuth2" origin/main -- docs/adr/0097-declarative-connector-instances.md matches line 119: "The open tier deliberately omits OAuth2 from ConnectorInstanceAuthSchema."
    • git grep -n "intentionally absent" origin/main -- packages/spec/src/shared/connector-auth.zod.ts answers nothing, but the sentence is still in the file: it wraps across lines 113-114 ("OAuth2 is intentionally" / "absent (enterprise tier, ADR-0015)."). A one-line grep cannot match it. The ADR says so and names which surface each schema serves: ConnectorOAuth2Schema (L30-40, inside the runtime union ConnectorAuthConfigSchema L79-85) vs ConnectorInstanceAuthSchema (L158-163, authored, four static kinds).
  • file:line readings behind the ADR's citations (at 0ea90c7a). The ADR itself cites path#symbol, because check:adr-symbol-anchors refuses line anchors in docs/adr/**.
    • connector-auth.zod.ts: L35 and L38 inline clientSecret / refreshToken; L99-102 ResolvedConnectorAuth.
    • credential-resolver.ts: L12, L21-22.
    • service-automation/src/plugin.ts: L1955 resolver pick, L1997 resolve at materialization, L2306-2337 resolveInstanceAuth (static kinds only).
    • connector-mcp/src/mcp-provider.ts: L63-79 applyAuthToHeaders, L115.
    • connector-openapi/src/openapi-connector.ts: L52 and L88-93 (securitySchemes typed, never read), L365 applyAuth.
    • connector-rest/src/rest-connector.ts: L89.
    • integration/connector.zod.ts: L1168-1192 DeclarativeConnectorEntrySchema (inline-secret refusal).
    • sys-account.object.ts: L183-187 (user_id required), L236-255 (token columns textarea + internal, not secret).
    • auth-schema-config.ts: L124 AUTH_ACCOUNT_CONFIG.
    • auth-plugin.ts: L557 socialProviders.
    • auth-manager-plugin-list.ts: L478-488 (oauth-provider).
    • connector-imap/src/account.ts: L60, L68-69 (MailCredentialProvider).
  • Premise finding. ADR-0015 contains neither "OAuth" nor "enterprise" (grep -c -i enterprise = 0). The enterprise placement was first drawn for connectors by ADR-0018 line 229, then repeated by 0022, 0023, 0024 and 0097. The ADR-0015 note therefore corrects the attribution and changes no decision.
  • ADR-0018 line 229 also places "OAuth2 token refresh" in the enterprise tier. The seat's claim amendment 6449956947 added that file to the surface, so it carries the sixth note, and ADR-0143 lists it under Amends.
  • Not in this PR: card item 3, the cloud-repository record with a ## Provenance pointer. This lane cannot reach the cloud repository. ADR-0143's Provenance section records that the boundary was only ever drawn in this repository (no cloud ADR citation in any of the six records) and that the cloud record is owed.
  • Tier H. This PR is a draft and waits for an approval from an account in GOVERNED_APPROVERS. The seat does not ready it, arm auto-merge or enqueue it.

Verification

Derived gate family, final head 6948496: 21/21 exit 0, --ran reconciles 21/21, 0 NOT-MEASURED (derived zero)

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths): 8 paths, 440 changed lines (under the 3000 threshold), 21 commands. Each one ran on 69484961, with exit codes captured before any pipe:

0 | node scripts/check-adr-links.mjs
0 | node scripts/check-adr-links.mjs --self-test
0 | node scripts/check-closing-keyword-parity.mjs
0 | node scripts/check-closing-keyword-parity.mjs --self-test
0 | node scripts/check-comment-mask-corpus.mjs
0 | node scripts/ci/plan-test-core-shards.mjs --self-test
0 | pnpm --filter @objectstack/lint run check:doc-formula-expressions
0 | pnpm check:adr-anchors
0 | pnpm check:adr-index
0 | pnpm check:adr-symbol-anchors
0 | pnpm check:ci-filter-parity
0 | pnpm check:closed-ceremony-text
0 | pnpm check:cross-package-test-inputs
0 | pnpm check:doc-authoring
0 | pnpm check:driver-memory-census
0 | pnpm check:gitlink-declared
0 | pnpm check:nul-bytes
0 | pnpm check:pm-governed-merges
0 | pnpm check:pm-prior-rulings
0 | pnpm check:refd-timer-probe
0 | pnpm check:watch-hint-literal

--ran (each line command :: exit N): "21 derived famil(ies) accounted for — 21 run, 0 NOT-MEASURED (a DERIVED zero — all 21 recorded an exit code and none of them is 3)".

First-pass history:

  • On a8384e3b, check:adr-symbol-anchors was red with 24 [line-anchor] findings in ADR-0143. They were fixed in c965eb87 by moving to path#symbol.
  • 69484961 adds the ADR-0018 note (seat review round 1); the same 21 commands re-ran there, all exit 0.
  • check:doc-formula-expressions first exited 3 (prerequisite not met, nothing measured). It went green after pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint under os-verify-lock.sh (VERDICT command-exit 0).

Generated by Claude Code

…ants, vendor app-credential exchange, request signing)

Draft (Proposed) successor record moving outbound connector auth into the
open tier on the maintainer's ruling, with the four maintainer decisions
stated as open questions with options and a recommendation. Dated,
append-only amendment notes on ADR-0015 (attribution only), ADR-0022,
ADR-0023, ADR-0024 and ADR-0097; the generated docs/adr/README.md index.

Claude-Session: https://claude.ai/code/session_01ADHkmP2wqmTYar443bTqhK
Co-authored-by: Claude <noreply@anthropic.com>
check:adr-symbol-anchors refuses a path:line citation in docs/adr/**;
every code citation is now path#symbol (or file-level where the cited
spot has no declaration), so the gate keeps each one checked.

Claude-Session: https://claude.ai/code/session_01ADHkmP2wqmTYar443bTqhK
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 27, 2026
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation labels Oct 27, 2026
ADR-0018's "Out of scope" paragraph is where OAuth2 token refresh was
first placed in the enterprise tier for connectors. It now carries the
same dated, append-only note as the other five, and ADR-0143 lists it
under Amends instead of as an owed follow-up.

Claude-Session: https://claude.ai/code/session_01ADHkmP2wqmTYar443bTqhK
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:skills#1 · session_01ADHkmSrRBdu9MdC6fc33Uu · head 69484961 · 2026-10-27T18:20Z

  1. 形态 — Tier H (docs/adr/**): new ADR-0143 (Proposed) moves outbound connector auth into the open tier — oauth2 (authorization_code + S256 PKCE + refresh, client_credentials, the MCP authorization flow), app-credential (feishu / dingtalk / wecom), signed (closed scheme enum) — with per-dispatch resolution (D4), a connection record outside metadata (D3), a server-side Connect flow (D5) and "a grant is never a sign-in" (D6). Dated, append-only amendment notes on ADR-0015, -0018, -0022, -0023, -0024, -0097; README index regenerated. No spec, runtime or console change.
  2. 前提实测 — on origin/main 0ea90c7a: ADR-0097:119 "deliberately omits OAuth2 from ConnectorInstanceAuthSchema"; connector-auth.zod.ts:158–163 unions none / bearer / api-key / basic; L113–114 still say "intentionally absent (enterprise tier, ADR-0015)" (wrapped — the claim's grep missed it, corrected in amendment 6449956947). ADR-0018:229 is the sixth enterprise placement; round 2 gave it a note.
  3. 门禁 check-runs — dev ran the 21 derived commands on the head, all exit 0 (--ran 21/21, 0 NOT-MEASURED). check:adr-symbol-anchors refuses file:line in docs/adr, so the record cites path#symbol; the file:line readings are in the PR body. CI on the head is read again before landing.
  4. 接受的偏差 — Refs #26768 sits on body line 3, after the harness attribution block. The four maintainer decisions (Q1 connection ownership, Q2 grant storage, Q3 first products, Q4 first signing schemes) stay open with recommendations — answered by the maintainer in the file before the merge, never by the seat.
  5. 范围外去向 — card item 3, the cloud-repository ## Provenance record (Prime Directive 13): this lane cannot reach objectstack-ai/cloud; ADR-0143's Provenance section names it as owed; the seat routes it to the maintainer. Contract review at CONTRACT_REVIEW_TIER, effort ≥ xhigh (triage 6448154988): requested from the director seat.
  6. 落地条款 — governed, Tier H: stays draft until a GOVERNED_APPROVERS approval with Q1–Q4 answered; then it lands through the queue. 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 and 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 leave pm:blocked after it lands.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 694849615009fa10029d77321d261fe5b60f8398
Local-runs: none
Scope: docs/adr/0143-outbound-connector-auth-is-open-tier.md + docs/adr/0015-external-datasource-federation.md, docs/adr/0018-unified-node-action-registry.md, docs/adr/0022-connectors-vs-messaging-channels.md, docs/adr/0023-openapi-to-connector-generator.md, docs/adr/0024-mcp-connectors.md, docs/adr/0097-declarative-connector-instances.md, docs/adr/README.md, AGENTS.md, packages/spec/src/shared/connector-auth.zod.ts, packages/spec/src/integration/connector.zod.ts, packages/services/service-automation/src/plugin.ts, packages/services/service-automation/src/credential-resolver.ts, packages/connectors/connector-mcp/src/mcp-provider.ts, packages/connectors/connector-openapi/src/openapi-connector.ts, packages/platform-objects/src/identity/sys-account.object.ts, docs/adr/0073-automation-execution-identity.md

Director seat (summon #52, ledger objectstack#12708), summoned by the domain:skills#1 seat; effort ≥ xhigh per triage 6448154988. Read-only at the head above: the diff, card #26768 and its thread (claim 6449307005, amendment 6449956947, dev report 6449922632), the seat's ACCEPT 6450167841, the parent cards #26702 and #26710 for the quoted rulings, every file the ADR cites on origin/main 0ea90c7a, AGENTS.md Prime Directives #12–#14 and Route & surface ownership rule 5, the head's check runs. docs/adr/** is a governed-register row ⇒ the rules face, CONTRACT_REVIEW_TIER; Tier H, lands by the maintainer's hand with Q1–Q4 answered, not on this record. Served at CONTRACT_REVIEW_TIER. The seat records; it does not approve, ready, queue or merge.

① Derived judgments

  • The rulings are quoted verbatim and the record executes them, no more. 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's body carries 「OAuth 放开源层,先起草 ADR 修订」, 「平台基于 better-auth ,怎么会有没有 oauth」, 「创建issue 就行」; 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's body carries 「三条行动建议 立卡」 → 「建议 1+2立卡」 → 「3 不考虑」 and says of itself "This card is recommendation 2", so "recommendation 2 is request signing" is read, not inferred. D1–D7 cover exactly the two cards' item 1; recommendation 3 is listed under "does not decide" (「3 不考虑」). The four maintainer decisions are left open with options and a recommendation each — the seat decided none of them.
  • Every current-state claim is true on main. connector-auth.zod.ts: ConnectorOAuth2Schema inlines clientSecret (L35) and refreshToken (L38) and sits in the runtime union ConnectorAuthConfigSchema (L79–85), which is ConnectorSchema.authentication (connector.zod.ts:864); ResolvedConnectorAuth Extracts the four static kinds (L99–102) and its docblock still places OAuth2 in "the enterprise tier (ADR-0015)"; the "OAuth2 is intentionally / absent (enterprise tier, ADR-0015)" sentence wraps L113–114 exactly as the ADR says; ConnectorInstanceAuthSchema unions none / bearer / api-key / basic (L158–163). DeclarativeConnectorEntrySchema refuses any non-none authentication on every published entry (connector.zod.ts:1178–1192). ConnectorOAuth2Schema has no runtime consumer — only its own tests parse it (D7 holds). resolveInstanceAuth resolves once and switches on the three static secret kinds only (plugin.ts:2306–2337), picked at materialization (:1955, :1997). applyAuthToHeaders folds the four static kinds (mcp-provider.ts:63–79); normalizeTransport exists (:83). OpenApiSecurityScheme types oauth2 and securitySchemes has one occurrence in the file — the declaration — so "typed, never read" is measured. sys_account.user_id is required: true (sys-account.object.ts:183–187); its token columns are Field.textarea + internal: true, not the secret channel (:236–255). The IMAP MailCredentialProvider.credentialFor seam and its "the provider's job, not the connector's" sentence are at connector-imap/src/account.ts:60–69. ADR-0073's 2026-07-28 amendment (user-less data ops refused) exists.
  • The attribution finding is right. ADR-0015 on main contains neither "OAuth" nor "enterprise" (both counts 0); its §10 speaks of credentialsRef into a secrets service (L698–699). The five records that place OAuth2 refresh in the enterprise tier each do so "per ADR-0015": ADR-0018 L229 ("on the ADR-0015 precedent"), ADR-0022 L112, ADR-0023 L114, ADR-0024 L111, ADR-0097 L71 and L119. So the ADR-0015 note changes attribution only, and each other note sits under the paragraph it amends. ADR-0024's note is also right that the record "names no OAuth" (count 0) and that §3 L102 says "we do not model the upstream's auth in ConnectorSchema".
  • The notes are append-only, as Prime Directive [WIP] Add Chinese version of the documentation #13 requires. Every hunk on the six existing records is a pure insertion: one **Amended by**: header line (the ADR-0005 convention) and one dated blockquote (the ADR-0092 / ADR-0099 "Amendment note (…)" convention). No existing line changes. Each note names what moves and what keeps its placement (vault backend, catalogs, marketplace, sandboxed stdio, the sys_channel_account lifecycle), matching D1 word for word.
  • Provenance follows Prime Directive [WIP] Add Chinese version of the documentation #13's shape. The directive puts a commercial-boundary decision in objectstack-ai/cloud and has this repo carry the mechanism half with a ## Provenance section. The six tier-placing records are all in this repository and none cites a cloud record, so the ADR correctly records that the cloud half is owed and that the cloud record decides the commercial boundary where they disagree. D5 cites Route & surface ownership rule 5 ("declares its CLOSED query-parameter set on the day it lands") — that rule exists with that wording. D2's "closed enum, refused at publish when unknown" is Prime Directive Add comprehensive test suite for Zod schema validation #12's reject-at-authoring rule, cited correctly.
  • The index is the generator's. docs/adr/README.md moves 143 → 144 records, Proposed 17 → 18, one ADR-0143 row; 0143 is free on main (0142 the newest). Lint · repo & tooling gates (home of check:adr-index) and Lint · docs & code guards (home of check:adr-symbol-anchors) are green on the head, so the row and every path#symbol anchor resolve.

② Semver level

skip-changeset: docs/adr only, no package touched. Correct — an ADR publishes nothing. The slices it names each carry their own changeset when they land.

③ Boundary flags

  • Governed path (docs/adr/**, Tier H): no review yet; draft; requested_reviewers hotlong, os-zhuang. This seat's account is not a GOVERNED_APPROVERS member; this record is not an approval and does not lift Tier H. Per its own Status line the maintainer's merge, with Q1–Q4 answered in the file, is the acceptance act — the seat may not answer them.
  • Should-fix before the merge (text, in the file the maintainer edits anyway): the ADR's own count is inconsistent after round 2 added the ADR-0018 note. L5 says "The five amendment notes this record names land in the same PR" — there are six (ADR-0015, -0018, -0022, -0023, -0024, -0097; L17 itself says "All six carry a dated amendment note"). L36 says "the six records that put outbound OAuth2 in the enterprise tier — ADR-0018, -0022, -0023, -0024 and -0097" — that list is five, and ADR-0015 is not one of them by the ADR's own finding. Neither changes a decision; both would be quoted wrong by the next reader. The seat should push the two words before the maintainer's edit, or the maintainer fixes them in the same stroke as Q1–Q4.
  • Check runs on 69484961 at this record: 22 success (every Lint job, Check Documentation Links, Governed Surface Queue Guard), 16 expected skips (docs-only), Test Core (1/8) and Type Check · workspace still in_progress. Nothing red; a red re-opens this record.
  • Card item 3 (the cloud-repository record with a ## Provenance pointer back to ADR-0143) is not in this PR and cannot be: the lane has no reach into objectstack-ai/cloud. The ADR names it as owed; the seat routed it to the maintainer. That is the right disposition; it is also the one standing item Steve already has open from RBAC + 组织架构权限:真实 UI 测试结论 — 后端判定全部正确,但「记录范围(readScope/writeScope)」在 Setup/Studio 无法配置与查看,另有 7 项管理体验缺口(待 PM 确认排期) #26059's ruling (no seat here can file in cloud).
  • Dev open questions (6449922632): channel accounts stay out (A) — right, the cards and rulings are about connectors and no channel transport is built; app-credential as its own discriminant (A) — right, the vendor exchanges are not RFC 6749 grants and folding them into oauth2 would make one key mean two things; notes written in force though Proposed (A) — right, the Status line says the notes take effect with the merge that accepts the record, and a conditional note would be rewritten on that day anyway.
  • Not owed a note: ADR-0023 L29's "managed OAuth2 refresh" sits in the Context comparison table (activepieces vs. us), a description of the other project, not a tier placement; the ADR's Amends list is right to leave it.
  • Dev deviations: path#symbol in place of file:line (the symbol-anchor gate refuses line anchors in docs/adr/**; the line readings are in the PR body and re-measured here); Refs #26768 on body line 3 after the harness attribution block; main moved past the base — nothing since touches docs/adr/'s six records or the index inputs beyond what the green index gate already proves.

④ Design judgment

  • The record as a contract, against the code it will govern: D2's three variants are each closed where the code today is open-by-omission, and every secret stays a credentialRef — the one invariant DeclarativeConnectorEntrySchema already enforces is extended rather than relaxed. D3 keeps tokens out of sys_metadata, the discovery route and logs; D4 names the two failure classes with ADR-0097's own split (operational CONNECTOR_UPSTREAM_UNAVAILABLE on backoff vs. a revoked grant that degrades with a reason and never fails boot), which is the degrade-don't-die rule ADR-0097 §"Upstream availability" already landed. D6 is the right guard for the one thing Q2's option A would otherwise make easy: a connector grant becoming a sign-in row. The Q2 analysis is measured, not argued — three of D3/D6's requirements fail on sys_account's actual columns (user_id required, tokens not on the secret channel, a row keyed as a linked sign-in), so the recommendation of a dedicated object follows from the file, not from taste. Q1's recommendation (organization-shared now, per-user additive later) is consistent with ADR-0073's user-less refusal and with Metadata-protocol: added being the cheaper later move. Q4's enum rule (a scheme enters with its signer and vendor vectors, ADR-0049) is the only honest shape for security-critical canonicalisation. Holds; nothing to break.
  • The amendment notes as amendments: each reverses one row and names what stays, under the paragraph that drew the line, with the ADR-0015 note confined to attribution. That is the narrowest edit Prime Directive [WIP] Add Chinese version of the documentation #13 allows and still leaves a future reader with the right pointer. Holds, with the two count words above corrected.

Implemented-by: claude/project-thread-egxrcr
Reviewed-by: session_01ADHkmQr732toX135tyo5zs

VERDICT: PASS


Generated by Claude Code

After ADR-0018 joined, the record lands six amendment notes, and five
records (not six) place OAuth2 refresh in the enterprise tier.

Claude-Session: https://claude.ai/code/session_01ADHkmP2wqmTYar443bTqhK
Co-authored-by: Claude <noreply@anthropic.com>
@os-zhuang
os-zhuang marked this pull request as ready for review October 27, 2026 22:52
@os-zhuang
os-zhuang enabled auto-merge October 27, 2026 22:53
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit 75abd91 Oct 27, 2026
39 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-egxrcr branch October 27, 2026 23:43
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/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants