Skip to content

feat(spec): an API-backed datasource names a declared connector instance in external.connector and borrows its credential (ADR-0015 D7) - #27783

Merged
os-zhuang merged 8 commits into
mainfrom
claude/project-thread-15sb9m
Oct 30, 2026
Merged

os-zhuang merged 8 commits into
mainfrom
claude/project-thread-15sb9m

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by 田騏 · project thread

Fable review owed. Developed on Opus while the Fable quota is out. This PR touches the published spec (packages/spec/src/**) and an ADR, so it lands only after a full Fable review, a CONTRACT_REVIEW_TIER contract review record PASS on the current head, and 田騏's approval (Tier H).

Fixes #27742

Clause-②: yes
Metadata-protocol: added
Skills-docs: follow-up owed — skills/objectstack-data/rules/datasources.md, content/docs/data-modeling/virtual-objects.mdx, content/docs/data-modeling/external-datasources.mdx

维护者速读

  • 改了什么:数据源的 external 块新增一个可选键 connector,写一个已声明的连接器实例名,数据源借用该实例的授权(静态、oauth2、app-credential、signed 皆可)。写错时在构建时响亮拒收:实例不存在、该连接器没有 provider(只是目录描述)、实例没有 auth;与 credentialsRef 同写、用在内置数据库驱动(postgres/mysql/sqlite/mongodb 等)上、或用在 managed 数据源上,解析时即拒收并给出改法。ADR-0015 加 D7 修订注。
  • 为什么改:D7 裁决(6486136301,字母 A):SAP、Dynamics 365 这类 OAuth2 供应商,一次 Connect 让流程动作和虚拟对象读数共用,重连一次两边都恢复。
  • 风险与回滚:只加键、不改已有键含义,credentialsRef 照旧;运行时尚未读取该键(运行时半边是 service-datasource: a datasource driver resolves a borrowed connector credential through ADR-0143 D4's per-dispatch resolver (D7 runtime seam) — epic #27261 P2 #27746,被本卡阻塞),在它落地前该键只校验不生效,两者应同版发布。回滚即 revert 本 PR。
  • 先看:packages/spec/src/data/datasource.zod.ts(新键与三条拒收);packages/spec/src/stack.zod.ts 的 collectDatasourceConnectorErrors(构建时实例核对);docs/adr/0015-external-datasource-federation.md 顶部 D7 注。
  • 两分钟验证:pnpm --filter @objectstack/spec exec vitest run src/stack-datasource-connector.test.ts src/data/datasource.test.ts,预期两个文件全绿;用例里能看到四种构建时拒收与三种解析时拒收的原文。
  • 需要你做的:Fable 复审与契约复审通过后,批准本 PR(Tier H)。

What changes

The key. ExternalDatasourceSettingsSchema gains connector (packages/spec/src/data/datasource.zod.ts): an optional snake_case name of a declared connector instance whose credential an API-backed datasource borrows (ADR-0015 amendment D7, ruling 6486136301 on #27298). Near-miss spellings (connectorName, connectorId, connectorRef, connectorInstance) are refused with the "did you mean connector" prescription, and a connector written one level too high, on the datasource itself, is pointed into the external block.

Refused at parse (DatasourceSchema's refinement, issue path external.connector, each message naming the fix):

  • beside a bound credentialsRef: the two channels are exclusive. The card allowed "mutually exclusive, or the precedence named and pinned"; exclusivity was chosen because a precedence rule leaves the loser written and ignored, the silent no-op the closed shapes in this module exist to stop. An empty-string credentialsRef is not a binding (the connect path's test is truthy), so it is not the second channel.
  • on a built-in database driver (every id in DRIVER_VOCABULARY, via resolveDriverId): a connector lends an HTTP credential; the message prescribes credentialsRef.
  • on a managed datasource with a plugin driver: the message prescribes schemaMode: 'external' (or 'validate-only'), not the generic "remove these keys" of the managed-block refusal, which no longer lists connector.

Refused at build (collectDatasourceConnectorErrors in packages/spec/src/stack.zod.ts, run by validateCrossReferences, so defineStack and os validate both refuse with STACK_CROSS_REFERENCE_INVALID / 422), judged against the stack's own connectors, like a job's mapping reference:

  • no connectors entry of that name;
  • an entry with no provider (an ADR-0097 catalog descriptor, never materialized);
  • an instance with no auth, or auth: { type: 'none' } (nothing to lend).

A disabled instance (enabled: false) is accepted: the switch turning the vendor off for its actions and its records alike is the one-grant behaviour D7 asks for.

ADR-0015. An amendment note D7 at the top and a pointer in §10, recording the second credential channel, the three exclusions, that execution identity is not reopened (ADR-0143 Q1 = A; object permissions and RLS stay the only gate), and the answer to the ruling's open reading below.

Ledgers and generated files. packages/spec/liveness/datasource.json gets a planned row for external.connector (evidence: the two refusal sites; flips to live with the runtime card), and the credential-name ratchet in metadata-credential-fields.test.ts allow-lists datasource:external.connector with its reason: its describe says "credential", but the field holds a connector name and the credential stays on the instance's own auth.credentialRef. content/docs/references/data/datasource.mdx, packages/spec/authorable-surface/data.json and packages/spec/liveness/state-counts/datasource.md are regenerated. Changeset: minor on @objectstack/spec.

The ruling's open reading: does the (organization, connector instance) binding fit an environment-level datasource?

Yes, under the posture #27230 S1 ruled. A datasource stays environment-scoped (ADR-0015 §10). The connection it borrows is selected per dispatch by the request's organization (ADR-0143 Q1 = A, D4), so a virtual-object read made in an organization uses that organization's grant, exactly as a flow dispatch does. A read that carries no organization (the boot-time drift check, a schedule) falls under D4's rule: it uses the instance's connection only when exactly one exists, and is refused otherwise, never answered from an arbitrary organization's grant. S1 ruled one organization per environment for connector credentials ("暂时不考虑 一个环境里住多个组织的情况"), so there is always exactly one connection, and the environment-level datasource and the organization-bound connection record meet with no new rule. A shared environment hosting several organizations is outside S1; there the no-organization read is refused by D4, which is fail-closed. Recorded in the ADR-0015 D7 note.

The ruling's other open reading (whether the objectql-side driver reaches the automation service's resolver without a new dependency direction) is the runtime half's question, carried by #27746; nothing in this diff decides it.

Tests

On eda26e3c, each under scripts/pm/os-verify-lock.sh:

  • pnpm --filter @objectstack/spec test: Test Files 756 passed (756), Tests 22468 passed | 1 todo (22469); command-exit 0 (4m15s).
  • pnpm --filter @objectstack/spec typecheck: command-exit 0 (1m22s).
  • New: packages/spec/src/stack-datasource-connector.test.ts (8 cases: the four build-time refusals with the STACK_CROSS_REFERENCE_INVALID / 422 envelope, one finding per borrowing datasource in a stack with objects, and three controls) and the external.connector block in packages/spec/src/data/datasource.test.ts (11 cases: acceptance in external and validate-only, the declared key, the four alias prescriptions, the name pattern, the three parse refusals with their paths and fixes, the empty-credentialsRef control, the top-level guidance, and a credentialsRef-alone control).
Ablations (both refusal halves can go red)

Mutations through scripts/ablation-replace.mjs (anchor hit 1, blob moved, restored by the tool, git diff HEAD empty afterwards):

  • stack.zod.ts: errors.push(...collectDatasourceConnectorErrors(config)); replaced by void collectDatasourceConnectorErrors; (blob c8cc7c644281 to e0bfdb0f2204): stack-datasource-connector.test.ts 5 failed, 3 passed (the five refusal cases red, the three controls green).
  • datasource.zod.ts: the if (ds.external?.connector !== undefined) { guard made false (blob e0f177402a59 to 1090eb4caaf4): the external.connector block 4 failed, 7 passed (the four parse-refusal cases red).

Acceptance notes

Verification

Local gates on eda26e3c, exit codes captured to a file before any pipe. node scripts/pm/dispatch-gates.mjs --commands derived 111 families from the 11 changed files; every one was run.

  • 105 exit 0 on the first pass, among them spec check:generated, check:docs, check:api-surface, check:authorable-surface, check:liveness, check:strictness-ledger, check:export-origins, check:llms-txt, check:spec-parsed-alias, check:spec-docblock-symbol-anchors, check:stack-collection-maps, check:doc-authoring, check:adr-links, check:adr-anchors, check:adr-index, check:issue-citations, check:nul-bytes, check:merge-driver, check:pm-prior-rulings, check:type-check-debt, check-adr-0087-registration --base origin/main, check-empty-changeset --base origin/main, check-changeset-no-major --base origin/main and the --self-test batteries.

  • 6 exit 3 (prerequisite not met) on the first pass, each re-run green after its prerequisite: check-plugin-teardown-shape --self-test (fixture commit fetched), lint check:doc-formula-expressions and check:doc-security-posture (@objectstack/lint built), spec check:skill-examples, check:dual-build-cjs-loads and check:lean-entry-closure (the published packages' dist/ built by check:type-check-debt).

  • dispatch-gates --ran: "111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN" (a derived zero).

  • pnpm --filter @objectstack/spec check:generated on the merged head after a build: 14 of 14 up to date (exit 0).

  • origin/main merged once before opening (eda26e3c); packages/spec had moved on main (analytics), so spec was rebuilt, check:generated re-run, and the full spec suite above was taken on the merged head.

  • The full battery (pnpm lint, Test Core shards, UI jobs) is CI's and was not re-run locally. No dogfood run: no ! in the title and no (narrowing) arm.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic.
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint is CI's.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the 维护者速读 block is the repo's own Chinese reader section).
  • The Before Submitting list in CONTRIBUTING.md is done.

Generated by Claude Code

@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 10 documentable anchor(s). ⚠️ 3 changed file(s) yielded no anchor (packages/spec/authorable-surface/data.json, packages/spec/liveness/datasource.json, packages/spec/liveness/state-counts/datasource.md), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/drivers.mdx (via credentialsRef (literal, a string literal in managedFederationContentKeys))
  • content/docs/data-modeling/external-datasources.mdx (via credentialsRef (literal, a string literal in managedFederationContentKeys))
  • content/docs/data-modeling/virtual-objects.mdx (via credentialsRef (literal, a string literal in managedFederationContentKeys))

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

  • content/docs/releases/v17/17-0.mdx (via DatasourceSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-1.mdx (via credentialsRef (literal, a string literal in managedFederationContentKeys))
  • content/docs/releases/v17/17-6.mdx (via DatasourceSchema (symbol, a top-level const))

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
  • 3 changed file(s) yielded no anchor (packages/spec/authorable-surface/data.json, packages/spec/liveness/datasource.json, packages/spec/liveness/state-counts/datasource.md) — pages documenting those are invisible to this run
  • 1 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 — 167 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 561b7642e1a17c6f26f887fa3250092fd0ba8e77 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 561b7642e1a17c6f26f887fa3250092fd0ba8e77

⚠️ 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 561b7642e1a17c6f26f887fa3250092fd0ba8e77 → 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

epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (HTTP 429 rate_limit, 2026-10-29T23:06Z) · head eda26e3

The seat's at-tier review call was refused by the model quota, and per contract-review.md the tier is not lowered. Owed on this head: one same-shape CONTRACT_REVIEW_TIER record (faces: published schema packages/spec/src/**, Clause-②: yes, Metadata-protocol: added, the ADR-0015 note), and the maintainer's independent full Fable review of the spec and ADR change; one record with a genuine ④ can serve both, as on #27715. After the record this PR is Tier H, so it waits on the maintainer's approval. The seat also intends it to ship with #27746 (the runtime seam), since no runtime reads the key yet.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK

Contract-review-claim: session_01L6bhgV4fMGJHSdSdx3u9mK · head eda26e3cca516ee5e832a8e12dcc668183b0c7e2 · 2026-10-29T23:33Z


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: eda26e3cca516ee5e832a8e12dcc668183b0c7e2
Local-runs: none
Scope: packages/spec/src/data/datasource.zod.ts, packages/spec/src/stack.zod.ts, docs/adr/0015-external-datasource-federation.md + packages/spec/src/integration/connector.zod.ts, packages/spec/src/shared/connector-auth.zod.ts, packages/spec/src/data/driver/config-registry.zod.ts, packages/services/service-datasource/src/datasource-connection-service.ts, docs/adr/0143-outbound-connector-auth-is-open-tier.md, docs/adr/0097-declarative-connector-instances.md, packages/spec/liveness/datasource.json, packages/spec/liveness/README.md, packages/spec/src/kernel/metadata-credential-fields.test.ts, packages/spec/src/stack-datasource-connector.test.ts, packages/spec/src/data/datasource.test.ts, .changeset/27742-datasource-external-connector.md, scripts/adr-anchors/README.md

Inputs read: card #27742 (body, both comments), PR #27783 (body, 11-file list, net diff from the files endpoint, 4 comments), the 55 check-runs on the head — every completed run success or skipped, zero failures (Check Changeset, Lint & Repo Gates, Spec property liveness, Governed Surface Queue Guard, Type Check UI, Test Core 1–8, Build & E2E UI all green). Reviewed by the reviewer tier; the dispatching seat's conclusions were not an input.

① Derived judgments

  1. ExternalDatasourceSettingsSchema.connector (datasource.zod.ts) — a new optional string with regex ^[a-z_][a-z0-9_]*$ on a published, registered metadata type (datasource backs defineDatasource, defineStack({ datasources }), /api/v1/meta/datasource and the Setup form). The accept set widens; nothing accepted before is refused, because connector was an unknown key on a .strict() shape. Right. The regex is byte-identical to DeclarativeConnectorEntrySchema.name (connector.zod.ts:830), so every declarable instance name is nameable. Right.
  2. Alias map gains connectorname / connectorid / connectorref / connectorinstance to connector, and DatasourceSchema gains top-level connector guidance pointing into external. Both spellings were refused before as unknown keys and are refused now with a prescription. No accept-set change. Right.
  3. Three new refinement issues at path external.connector (both channels; built-in driver via resolveDriverId; managed). Every refused shape was already refused (unknown key), so this is not a narrowing. resolveDriverId answers an own-key hit of the alias table or undefined (config-registry.zod.ts:383), so a plugin driver id such as odata falls through to the managed branch. managedFederationContentKeys now excludes connector, so a managed datasource gets the mode prescription once, not the generic deletion as well. Right.
  4. collectDatasourceConnectorErrors (stack.zod.ts) — module-private, not exported: the public API surface is unchanged (check:api-surface green). It runs inside validateCrossReferences over the parsed data (defineStack, after ObjectStackDefinitionSchema.safeParse), so connectors[] are DeclarativeConnectorEntryParsed (enabled defaulted to true, auth optional with no default) and datasources[] already cleared the datasource-level refusals. Placed before the object-count early return beside collectJobPullMappingErrors and collectDemoStaffErrors, riding the existing STACK_CROSS_REFERENCE_INVALID / 422 envelope. Right.
  5. .describe() edits regenerate content/docs/references/data/datasource.mdx; authorable-surface/data.json gains data/ExternalDatasourceSettings:connector. Generated output, byte-exact under check:generated and check:authorable-surface (green). Right.
  6. Liveness: a planned row for external.connector, state counts 30 / 0 / 0 / 0 / 1 = 31. planned means "Declared, intentionally not enforced yet" (liveness/README.md:77), which is the tree's state: no file outside packages/spec reads external.connector, and datasource-connection-service.ts#attemptConnect reads only record.external?.credentialsRef. Right status; the row carries no authorWarn — see ④.
  7. Ratchet allow-list: the field is a string whose first describe sentence contains "credential", so the fail-closed guard flags it; allow-listed with a reason, the same pattern as datasource:external.credentialsRef. Right.
  8. docs/adr/0015-external-datasource-federation.md edited: a governed surface, so the whole PR is Tier H. The PR is a draft with needs:contract-review on, head repo equals base repo. Right.
  9. One public claim is ahead of the tree: the new .describe() (and so the reference page and the changeset) says the borrowed credential may be "static, oauth2, app-credential and signed kinds alike". On this head ConnectorInstanceAuthSchema (connector-auth.zod.ts:158) is none | bearer | api-key | basic; the three grant-bearing kinds are ADR-0143 D2's spec slice, which has not landed (ADR-0143: "Each slice is its own card and lands after this record is accepted"). Partly wrong as a description of the published surface today; should-fix in ④, not a must-fix, because no accepted shape goes silently inert — such an instance cannot be authored at all.

② Semver level

  • .changeset/27742-datasource-external-connector.md: '@objectstack/spec': minor, body carries Clause-②: yes with no arm and Metadata-protocol: added; the PR body carries the same two line-initial declarations. A new authorable key is added, and added takes at least minor on the package the diff moves (AGENTS.md, Post-Task 3). @objectstack/spec is the only published package the diff moves. Right.
  • The Clause-②: line reads yes and that is right: the published accept set widens (an optional member on ExternalDatasourceSettings; DatasourceSchema accepts a shape it refused), nothing narrows, so no (narrowing) arm and no ADR-0087 disposition is owed. Check Changeset green.
  • The changeset's closing sentence ("until it does, the key is validated and not yet read") is truthful to the tree, as ① item 6 verifies.

③ Boundary flags

open_questions is empty. The dev's flags (os-dev-report, comment 6510596837) and the PR body's declared follow-ups, each answered or escalated:

  1. "No runtime reads external.connector until the runtime seam lands; both should ship in the same release." Answered: true on the tree. Escalated as a ④ should-fix: give the planned row authorWarn: true and an authorHint until it flips live, so os validate and os lint tell an author who writes the key today that nothing reads it yet (liveness/README.md:601–607 exists for exactly this). Carrier: service-datasource: a datasource driver resolves a borrowed connector credential through ADR-0143 D4's per-dispatch resolver (D7 runtime seam) — epic #27261 P2 #27746 and the release train.
  2. "A runtime-created (Studio) datasource never passes defineStack, so the instance-declared check must also run at the runtime's boot and save seams." Answered: right, and fail-closed today — attemptConnect refuses any driver no installed factory supports (skipped-unsupported) before it reaches credentials, and no API-backed driver ships in this repository. Carrier service-datasource: a datasource driver resolves a borrowed connector credential through ADR-0143 D4's per-dispatch resolver (D7 runtime seam) — epic #27261 P2 #27746 is correct.
  3. "The epic body §4 item 8 amendment is the epic PM's edit." Answered: right; not a file in this repository, nothing owed on this diff.
  4. Skills-docs follow-up (skills/objectstack-data/rules/datasources.md, content/docs/data-modeling/virtual-objects.mdx, content/docs/data-modeling/external-datasources.mdx; the docs-drift check names the same three pages). Answered: deferring to ride with or after service-datasource: a datasource driver resolves a borrowed connector credential through ADR-0143 D4's per-dispatch resolver (D7 runtime seam) — epic #27261 P2 #27746 is right — teaching the key before a runtime reads it advertises an undelivered capability — and the pages state nothing false today, since credentialsRef keeps working in every mode. The follow-up is named on the PR body. Accepted.
  5. "No scripts/adr-anchors/ file added." Escalated as a ④ should-fix: neither datasource.zod.ts nor stack.zod.ts has an anchor, and the only anchor naming ADR-0015 is packages__objectql__src__search-companion.ts.json. The ADR ids are in the code at both refusal sites, which is the rule's binding half; the anchor is the mechanical pin. Not a must-fix.
  6. Card task 4 (does the (organization, connector instance) binding fit an environment-level datasource) — answered in the PR body and recorded in the ADR note; judged in ④.
  7. The S1 citation ([Decision] Where a customer administrator supplies a declarative connector credential: a platform credentials page over an encrypted object, an app-shipped settings manifest, env-only as today, or the OAuth connection record #27230: one environment, one organization) is adopted as the card and the ADR note state it; [Decision] Where a customer administrator supplies a declarative connector credential: a platform credentials page over an encrypted object, an app-shipped settings manifest, env-only as today, or the OAuth connection record #27230 is outside this review's inputs and is not re-verified here.
  8. New flag raised by this review: the three grant-bearing auth kinds the describe(), changeset and ADR note name are not in ConnectorInstanceAuthSchema on this head — ④.
  9. PR author is objectstack-fleet[bot] through the relay (the claim names the thread session on the card); no other login to note.

④ Design judgment

packages/spec/src/data/datasource.zod.ts — judged against the ruling as the card states it (one optional external member naming a declared connector instance; refused at build when not declared; mutually exclusive with credentialsRef or the precedence pinned) and against its first consumers (collectDatasourceConnectorErrors, and datasource-connection-service.ts#attemptConnect for the credential path).

  • Exclusivity over precedence holds. The card allowed either; exclusivity leaves no written key silently ignored, which is this module's own invariant (its header: a key dropped in silence is the bug the shape was closed to stop). The "bound" test is truthy (if (ds.external.credentialsRef)), and the connect path's test is the same truthy if (credentialsRef), so credentialsRef: '' is a non-binding on both sides. Holds.
  • Built-in driver refusal holds. DRIVER_VOCABULARY is memory / sqlite / sqlite-wasm / postgres / mysql / mongodb / turso (config-registry.zod.ts:235–243), every one a database or in-process store, and a connector instance lends bearer, api-key or basic — HTTP credentials. The message names the canonical id, so an author who wrote postgresql reads 'postgres'; acceptable.
  • Managed refusal prescribes the mode, and the generic managed-block message no longer lists connector: both verified in the refinement and in managedFederationContentKeys. schemaMode defaults to 'managed', so a plugin-driver datasource that forgets the mode gets the one prescription that fixes it.
  • Tried to break it: connector on validate-only — accepted, right (the schema is the remote's). connector plus credentialsRef plus a built-in driver — two issues at one path, each a different fix; acceptable. A hyphenated or capitalised value — refused by the regex at external.connector, and since the connector name regex is identical no declarable instance is unreachable.
  • Should-fix (text): the .describe() promises "static, oauth2, app-credential and signed kinds alike". On this head the instance auth union is none | bearer | api-key | basic; the other three are ADR-0143 D2's unlanded spec slice. Say "every kind ConnectorInstanceAuthSchema declares; ADR-0143 D2 adds the grant-bearing kinds" or equivalent, so the published reference does not advertise an auth.type the connector door refuses today. Not a must-fix: a signed or oauth2 instance cannot be authored, so no accepted shape is silently inert, and the key works for the four kinds that exist.

packages/spec/src/stack.zod.ts — judged against ADR-0097 §1 (descriptor vs instance keyed on provider), DeclarativeConnectorEntrySchema, ConnectorInstanceAuthSchema, and the card's "refused at build when the instance is not declared".

  • Three findings, three fixes, and each arm matches the cited schema exactly: provider is optional, "Omit for a catalog-only descriptor" (connector.zod.ts:878); auth requires provider (the entry refinement), so the "no provider" arm is reachable only for a true descriptor; auth is optional with no default and type: 'none' is a declared variant, so the "nothing to lend" arm covers exactly the two shapes with no credentialRef, every other variant carrying credentialRef: z.string().min(1). Holds.
  • Resolution scope is the stack's own connectors, the precedent collectJobPullMappingErrors sets for mappings. Duplicate connector names are already refused by collectDuplicateEntryErrors (lastWinsByName('Connector', 'connectors')), so the Map's last-wins never hides a collision.
  • enabled: false accepted: defensible as the one-switch semantics D7 asks for — ADR-0097's reload tears down a newly disabled instance, and a datasource with no grant is the intended off state. Flag for service-datasource: a datasource driver resolves a borrowed connector credential through ADR-0143 D4's per-dispatch resolver (D7 runtime seam) — epic #27261 P2 #27746: the runtime half must surface that as the datasource degraded with the instance named, or the one switch becomes a silent 401. Noted for the carrier, not a finding on this diff.
  • Should-fix: no composed-artifact counterpart. validateCrossReferences has one call site (strict defineStack), composeStacks runs its own composed collectors, and ADR-0015 §4.2: the object save door and the datasource save door still accept an external binding on a managed datasource the stack does not declare (follow-up to #25168) #25211 added collectComposedExternalBindingDatasourceErrors for the very hole this check now shares: a cross-input pair is refused when the declaring input is built strict (correct per stack), but an input built with strict: false carries the dangling name to the artifact with no verdict. The PR names the scope, no producer does it today, and the runtime half fails closed on a missing instance. Follow-up of the ADR-0015 §4.2: the object save door and the datasource save door still accept an external binding on a managed datasource the stack does not declare (follow-up to #25168) #25211 shape, not a must-fix.
  • Should-fix: add scripts/adr-anchors/packages__spec__src__stack.zod.ts.json and scripts/adr-anchors/packages__spec__src__data__datasource.zod.ts.json naming ADR-0015 and ADR-0143 with the D7 invariant (AGENTS.md Prime Directive 13; the folder's README). Neither file is anchored today.
  • The two test files pin what the ADR says (8 stack cases incl. the no-objects stack and three controls; 11 datasource cases incl. the empty-credentialsRef control and the managed-built-in precedence). The ablations are the dev report's account; the check-runs are the verdict, and Test Core 1–8 are green.

docs/adr/0015-external-datasource-federation.md — judged against its first consumer (this spec half now; datasource-connection-service.ts under #27746 next) and its execution plan (the card's split: spec half here, the runtime seam #27746 Blocked-by this card, the OData driver #27741; §11's phases untouched).

  • The Amended by header gains D7 with date, ruling id and a pointer; §10's Credentials bullet gains the cross-pointer; the note takes the shape of the ADR-0143 attribution note beside it. Right.
  • Q1 = A, D4's exactly-one rule for a run with no organization, Q2 B's (organization, connector instance) record: each checked against ADR-0143 (D4 at lines 195–218, Q1 at 261–285, Q2 B at 299–300) and each is stated as that record states it. The answer to the open reading — an environment-scoped datasource meets an organization-bound connection with no new rule under a one-organization-per-environment posture, and is refused fail-closed outside it — follows from D4 as written and reopens no identity decision. Holds.
  • Execution plan: the note separates the contract (this diff) from the resolver path (the runtime half) and records what was not taken. It does not say where the runtime half lands; the liveness row does. Acceptable for an amendment note; a one-clause pointer to the connection service and its own card would make the ADR self-sufficient — folded into the should-fix below.
  • Should-fix (text): "borrow that instance's credential — static, oauth2, app-credential and signed kinds alike — through ADR-0143 D4's per-dispatch resolver" misstates D4 for static kinds: D4 opens with "the four static kinds keep resolving once, at materialization". Say the datasource takes the instance's credential source as the provider does — per dispatch for grant-bearing kinds, at materialization for static ones — and that the grant-bearing kinds arrive with ADR-0143 D2's spec slice (see the datasource.zod.ts should-fix). Neither correction changes a decision; both keep the ADR from being read as a mechanism it does not describe.
  • Tried to break it: a shared environment hosting several organizations — the note says a no-organization read is refused, and D4 says the same ("never falls back to an arbitrary connection"). An authorization_code instance never connected — D4 materializes it degraded; the datasource inherits that state, and the note's "one reconnect restores both" holds. No must-fix.

Should-fix summary (none blocks landing): (a) describe() and ADR note name auth kinds the spec does not declare yet — qualify them; (b) ADR note's "per-dispatch resolver" wording for static kinds — align with ADR-0143 D4; (c) planned liveness row — add authorWarn: true and an authorHint until #27746 flips it live; (d) composed-artifact counterpart for collectDatasourceConnectorErrors — follow-up of the #25211 shape; (e) ADR anchor files for the two schema files.

Implemented-by: claude/project-thread-15sb9m
Reviewed-by: session_01L6bhgV4fMGJHSdSdx3u9mK

VERDICT: PASS


Generated by Claude Code

…kinds that exist and how each resolves; anchor D7

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

Copy link
Copy Markdown
Contributor Author

epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
Should-fix routing for record 6511230689 (PASS on eda26e3c) · 2026-10-29T23:48Z

@github-actions github-actions Bot added size/l and removed size/m labels Oct 29, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK

Contract-review-claim: session_01L6bhgV4fMGJHSdSdx3u9mK · head 400e1b097e159708d7f0ecf468072edf74d15e0d · 2026-10-30T00:03Z


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 400e1b097e159708d7f0ecf468072edf74d15e0d
Local-runs: none
Scope: packages/spec/src/data/datasource.zod.ts, packages/spec/src/stack.zod.ts, docs/adr/0015-external-datasource-federation.md + packages/spec/src/shared/connector-auth.zod.ts, packages/spec/src/integration/connector.zod.ts, docs/adr/0143-outbound-connector-auth-is-open-tier.md, scripts/adr-anchors/packages__spec__src__data__datasource.zod.ts.json, scripts/adr-anchors/packages__spec__src__stack.zod.ts.json, scripts/adr-anchors/README.md, scripts/check-adr-anchors.mjs, content/docs/references/data/datasource.mdx, packages/spec/liveness/datasource.json, packages/spec/src/data/datasource.test.ts, packages/services/service-datasource/src/datasource-connection-service.ts

Delta review on record 6511230689 (PASS on eda26e3cca516ee5e832a8e12dcc668183b0c7e2, five should-fixes (a)–(e)). The move since that head is two single-parent commits, so no merge from main rides in the delta: 3ba8a678 (the connector describe, the ADR-0015 D7 note, three docblocks, the built-in-driver refusal string, two scripts/adr-anchors/ files) and 400e1b09 (the datasource reference regenerated). git diff eda26e3c 400e1b09: 6 files, +31 −13. The net diff against main from the files endpoint is 13 files: the 11 the previous record judged plus the two anchors. The routing comment (6511269947) said the delta would be text, generated output and anchors only; the tree agrees — no accept set, export, default or code path moves, and the one author-visible behavioural change is the kind list inside one prescription string.

Inputs read: card #27742 (body, both comments), PR #27783 (body, 13-file list, net diff from the files endpoint, all 7 comments including record 6511230689 and the routing comment 6511269947), the 48 check-runs on the head, read twice during this review. At the later read: 35 completed success, 9 skipped, 4 in_progress (Type Check UI, Test Core 5/8, Test Core 6/8, Type Check · workspace), zero failures. The four pending runs are pending, not green. Reviewed by the reviewer tier; the dispatching seat's conclusions were not an input.

Should-fixes of record 6511230689 against the tree: (a) taken — the describe and the ADR note now name the kinds that exist; (b) taken — the ADR note states D4's two resolution regimes; (e) taken — the two anchor files exist and the anchored text was added to both files; (c) declined by the seat with a reason (see ③.1); (d) filed as #27803, outside this diff.

① Derived judgments

  1. .describe() of ExternalDatasourceSettingsSchema.connector (datasource.zod.ts:366–372) — text only; the regex, optionality and accept set are unchanged, so Metadata-protocol does not move. The text names bearer, api-key and basic: exactly the three credential-bearing variants of ConnectorInstanceAuthSchema (connector-auth.zod.ts:158–163, a four-member union with none). none is left out rightly — the build check refuses it as nothing to lend. "ADR-0143 D2 adds the grant-bearing kinds": D2 (0143:134–180) names oauth2, app-credential and signed, and its spec slice is unlanded (0143:430–437, "Each slice is its own card and lands after this record is accepted"). Right.
  2. content/docs/references/data/datasource.mdx regenerated — the two rows that carry the describe (the DatasourceSchema external table and the ExternalDatasourceSettings table) match the new text byte for byte; check:docs (Type Check · source gates, lint.yml:5307) is green on the head. Generated output, read to judge, not reviewed. authorable-surface/data.json and the liveness ledger are untouched by the delta, as they should be: the key name did not move. Right.
  3. connectorOnBuiltinDriverRefusal (datasource.zod.ts:553–559) — the author-shown prescription now lists "a bearer token, an API key or basic auth" in place of "an OAuth grant or a request signer". A prescription string, no accept-set change, no tracker number in it. The test (datasource.test.ts:911–926) pins '${driver}' driver connects to a database and external.credentialsRef, both substrings unchanged, so no pending Test Core shard is at risk from this hunk. Right.
  4. Comment-only hunks — the module docblock (datasource.zod.ts:279–281), the refinement docblock (:537–540) and the collectDatasourceConnectorErrors docblock (stack.zod.ts:4375–4378). No behaviour. They are what make the ADR-0143 anchors satisfiable: at eda26e3c neither file contained the string ADR-0143 (0 and 0 hits); at the head datasource.zod.ts names it at :280, :369 and :538, stack.zod.ts at :4375 and :4377. Anchor and text are one change, as the brief asks a rule and its gate to be read. Right.
  5. Two new scripts/adr-anchors/ files — a new gate surface. check:adr-anchors (Lint · repo & tooling gates, lint.yml:685; green) now requires ADR-0015 and ADR-0143 to stay present in both files, and its fifth check (script :150–200, DECISION_CITATION at :964) letter-resolves every ADR-NNNN Dk citation in the full text of both anchored files and in the invariants. ADR-0143 D4 (:280) and ADR-0143 D2 (:369, :538) resolve to ### D2 and ### D4 in 0143. "ADR-0015 amendment D7" is outside the regex — a word sits between the number and the letter — so ADR-0015's lack of ### Dk headings is never reached. Anchoring stack.zod.ts puts its whole text under the letter check; the job is green on the head, so every letter citation already in that file resolves. Filenames derive from file as the README requires; adrs is non-empty and invariant is present and states the decision (script :1341–1353). Right.
  6. docs/adr/0015-external-datasource-federation.md edited again — governed surface; the PR stays Tier H; Governed Surface Queue Guard and PR-body guards green on the head. Right.
  7. Items 1–8 of record 6511230689's ① are untouched by the delta and stand as judged there. Its item 9 is the claim this delta retracts; one parenthetical in it was imprecise — the changeset (unchanged, .changeset/27742-datasource-external-connector.md) names only bearer in its example and never listed the three kinds, so nothing is owed on the changeset.

② Semver level

The delta moves no changeset and changes no accept set, export or default. '@objectstack/spec': minor, Clause-②: yes with no arm and Metadata-protocol: added stand as judged in record 6511230689; Check Changeset is green on the head. A describe or prescription text change is none on its own and rides the added kind already declared on the package the diff moves. The Clause-②: line reads yes and that stays right: nothing narrows.

③ Boundary flags

  1. Should-fix routing (comment 6511269947) against the tree. (a), (b), (e) taken, each verified in ④. (c), authorWarn: true and an authorHint on the planned liveness row, declined because service-datasource: a datasource driver resolves a borrowed connector credential through ADR-0143 D4's per-dispatch resolver (D7 runtime seam) — epic #27261 P2 #27746 flips the row live in the same release (claim amendment 6511235369 is cited there; it is outside this review's inputs and is adopted as stated). The decline is coherent: the row is the honest state of the tree, and an author who writes the key today reaches no inert runtime path — no API-backed driver ships in this repository and attemptConnect refuses an unsupported driver before it reaches credentials. (c) stays a should-fix and does not block. (d) is spec: composed stacks refuse a datasource whose external.connector names no declared connector instance (composed counterpart of the #27742 check, #25211 shape) — epic #27261 #27803, Blocked-by spec: an API-backed datasource's external block names a declared connector instance and borrows its credential (D7, ruling 6486136301) — epic #27261 P2 #27742, outside this diff.
  2. Residual of (b) outside the hits files: the connector row's note in packages/spec/liveness/datasource.json (unchanged by the delta) still reads "resolved per dispatch through ADR-0143 D4's resolver" for the credential generally — the wording the ADR note and describe just corrected; D4 (0143:197–198) says the static kinds resolve once at materialization. A ledger note, not a published surface. Should-fix (text): align it with D4, or rewrite it when service-datasource: a datasource driver resolves a borrowed connector credential through ADR-0143 D4's per-dispatch resolver (D7 runtime seam) — epic #27261 P2 #27746 flips the row live, since that edit rewrites the note anyway. Not a must-fix.
  3. The PR body's 维护者速读 still says the borrowed credential may be "静态、oauth2、app-credential、signed 皆可" — the claim the tree no longer makes. The body is the dev's account and is not the tree, but it is the surface the maintainer reads before the Tier H approval. Should-fix (body text): bring that line to the tree's "whichever kind the instance's auth declares; the grant-bearing kinds arrive with ADR-0143 D2".
  4. New flag for the carrier of ADR-0143 D2's spec slice (domain:spec, 0143's slice table). Three strings in this diff say "today" or "adds" and go stale the day D2 lands: the describe parenthetical (datasource.zod.ts:369), the refusal string's kind list (:554–555) and the ADR-0015 note's "bearer, api-key and basic today". Each states nothing false now; the D2 slice should re-record them when it widens ConnectorInstanceAuthSchema. Noted for that carrier, not a finding on this diff.
  5. Flags 1–9 of record 6511230689's ③ stand as answered there; its flag 8 (grant-bearing kinds named on the surface before they exist) is closed by this head.
  6. open_questions is empty on the dev report; the two delta commits raise no new dev flag.
  7. Check-runs: four in_progress at the later read — Type Check UI, Test Core 5/8 and 6/8, Type Check · workspace. Pending, not green; landing waits on them under Prime Directive 14. The delta adds no type and changes no runtime code path, and the one test-pinned string keeps its pinned substrings, so nothing in the delta puts them at risk — but this record does not call them green.

④ Design judgment

packages/spec/src/data/datasource.zod.ts — the delta's hunks judged against ConnectorInstanceAuthSchema (connector-auth.zod.ts:158–163), ADR-0143 D2 (:134–180) and D4 (:195–218), and the first consumers (collectDatasourceConnectorErrors; datasource-connection-service.ts#attemptConnect).

  • The describe: the three kinds named are exactly the credential-bearing variants that exist; none is left out rightly; "resolved the way the connector resolves it" is neutral across D4's two regimes and correct for both. Holds. Should-fix (a) is closed in the shape the previous record proposed.
  • The module docblock: "a static kind once at materialization, a grant-bearing kind per dispatch" is D4's opening sentence as written. Holds. Nit: line 281 runs 117 columns inside a wrapped comment block; Lint · ESLint is green, so cosmetic only.
  • The refinement docblock and the refusal string: the HTTP-credential premise behind the built-in-driver refusal now lists bearer, API key and basic, with D2's kinds "alike" in the docblock and absent from the author-shown string. Both true today; the string stays true, if not exhaustive, after D2 (③.4). Tried to break it: the refusal still names the canonical driver id and prescribes external.credentialsRef plus removing connector for every DRIVER_VOCABULARY id; that logic did not move, and the test pins its unchanged substrings. Holds.
  • The rest of the file stands as judged in record 6511230689.

packages/spec/src/stack.zod.ts — one docblock hunk, judged against connector.zod.ts:906 (auth: ConnectorInstanceAuthSchema.optional()) and ADR-0143 D2.

  • "the ADR-0143 instance auth contract whose credential the datasource borrows": auth is that union. ADR-0097 §3 originated it and ADR-0143 D2 governs how it grows; ADR-0015's own attribution note says to cite ADR-0143, not ADR-0015, for where connector auth sits. The citation is the right one and it is load-bearing for the auth.type === 'none' arm: every D2 variant carries a credentialRef, so none stays the only nothing-to-lend shape after D2 — the fact the ADR-0143 anchor should make a future author re-check when the union widens. Holds.
  • No code change. The function, its three arms, the stack-scoped resolution and the enabled: false acceptance stand as judged in record 6511230689, as does the composed-artifact should-fix (d), now spec: composed stacks refuse a datasource whose external.connector names no declared connector instance (composed counterpart of the #27742 check, #25211 shape) — epic #27261 #27803.

The two anchors (the gate half of should-fix (e), read as one change with the two files above):

  • Both are shaped per scripts/adr-anchors/README.md: one file each, filename derived from file, adrs naming two live records (neither 0015 nor 0143 is a tombstone or on the gate's collision list), an invariant that states what D7 decided and which refusals realize it. Neither invariant cites a tracker number. The datasource anchor's invariant matches the refinement exactly (exclusive channels, never a precedence; refused on a built-in driver and on a managed datasource); the stack anchor's matches the three build arms (undeclared name, provider-less descriptor, auth-less instance). Prime Directive 13 is satisfied for both files. Holds.

docs/adr/0015-external-datasource-federation.md — the D7 note judged afresh, since its text changed: against ADR-0143 D2 and D4, ConnectorInstanceAuthSchema, its first consumer (this spec half now; datasource-connection-service.ts under #27746 next) and its execution plan.

  • "whichever kind its auth declares (ConnectorInstanceAuthSchema: bearer, api-key and basic today; ADR-0143 D2 adds the grant-bearing oauth2, app-credential and signed)" — matches the union on this head and D2's three names. Holds.
  • "resolved as ADR-0143 D4 resolves the instance's own: a static kind once at materialization, a grant-bearing kind per dispatch" — D4's first sentence; the previous "per-dispatch resolver" misstatement for static kinds is gone. Holds.
  • "The datasource connection service hands it to the driver (the runtime half, service-datasource: a datasource driver resolves a borrowed connector credential through ADR-0143 D4's per-dispatch resolver (D7 runtime seam) — epic #27261 P2 #27746)" — the pointer the previous record asked for. The service exists at packages/services/service-datasource/src/datasource-connection-service.ts and today reads only record.external?.credentialsRef (:595), which is consistent with the planned row; service-datasource: a datasource driver resolves a borrowed connector credential through ADR-0143 D4's per-dispatch resolver (D7 runtime seam) — epic #27261 P2 #27746 is the card the claim comment names as the runtime seam. The note is now self-sufficient on its execution plan. Holds.
  • Tried to break it: the identity paragraph ("the connection it borrows is selected per dispatch by the request's organization … only when exactly one exists") now sits beside "a static kind once at materialization". No contradiction — a static-kind instance has no connection record (D3 persists only refresh tokens, registered clients and grant status), so no organization selection arises for it, and the open reading the paragraph answers, the (organization, connector instance) binding, is a grant-bearing question by construction. One clause saying so would help a reader; not a finding. "ADR-0143 D2 adds" is present tense for a decided, unlanded slice, the same phrasing the describe uses; acceptable because D2 is an accepted decision, and flagged for the D2 carrier (③.4).
  • The Amended by line, the §10 pointer, the three contract bullets, Q1 = A and the S1 answer are unchanged by the delta and stand as judged in record 6511230689.
  • No must-fix.

Should-fix summary (none blocks landing): (c) stands as declined by the seat — the planned row carries no authorWarn, covered by #27746 landing in the same release; (f) the liveness row's note still says "resolved per dispatch" for every kind — align with D4 or rewrite when the row flips live; (g) the PR body's 维护者速读 still names the three unlanded kinds — align it with the tree before the Tier H read. (a), (b) and (e) are closed by this head; (d) is #27803. Landing also waits on the four pending check-runs named in ③.7.

Implemented-by: claude/project-thread-15sb9m
Reviewed-by: session_01L6bhgV4fMGJHSdSdx3u9mK

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
ACCEPT · PR #27783 @ 400e1b097e159708d7f0ecf468072edf74d15e0d · 2026-10-30T00:17Z

维护者速读

  • 改了什么: 数据源的 external 块新增可选键 connector,点名本栈已声明的连接器实例,借用它的授权(今天是 bearer、api-key、basic 三种;oauth2 等授予型种类随 ADR-0143 D2 再加)。与 credentialsRef 互斥;用在内置驱动或托管数据源上会被拒;构建时找不到实例、实例只是描述符、或实例没有可借的授权都会被拒。ADR-0015 加 D7 修订说明。
  • 为什么改: D7 裁决(6486136301):SAP / Dynamics 365 这类接口用 OAuth2,一次授权让流程动作和虚拟对象读数共用,重连一次两边都恢复。这是 spec 一半;运行时一半是 service-datasource: a datasource driver resolves a borrowed connector credential through ADR-0143 D4's per-dispatch resolver (D7 runtime seam) — epic #27261 P2 #27746,同一版本发布。
  • 风险与回滚: 只放宽接受集(以前 connector 是未知键,一律拒收),已接受的东西都不变。运行时读这个键之前,账本行标 planned。changeset minor · Clause-②: yes · Metadata-protocol: added。还原即回滚。
  • 先看: packages/spec/src/data/datasource.zod.ts 的 connector 键及其三条拒收;packages/spec/src/stack.zod.ts 的 collectDatasourceConnectorErrors。
  • 两分钟验证: pnpm --filter @objectstack/spec exec vitest run src/data/datasource.test.ts src/stack-datasource-connector.test.ts。
  • 需要你做的: 批准(Tier H:改了 docs/adr/0015)。契约复核 PASS 记录 6511651329,Head-sha: 400e1b097e159708d7f0ecf468072edf74d15e0d。

席位意见:ACCEPT。契约复核两轮均 PASS(6511230689 于 eda26e3c,增量 6511651329 于当前 head),由分诊在 CONTRACT_REVIEW_TIER 完成,兼作 spec 与 ADR 改动的独立完整复核。无 must-fix。

席位核对

  1. Shape: +495/−7, 13 files. One optional key on a published, registered metadata type. Three refinement refusals at external.connector. A module-private stack cross-reference check that rides the existing STACK_CROSS_REFERENCE_INVALID envelope. The ADR-0015 D7 note, two ADR anchors, a planned liveness row, and generated reference and authorable-surface output.
  2. Premise measured: on main, a datasource's credentials came only from external.credentialsRef, and connector was an unknown key on the .strict() shape. The record confirms that nothing accepted before is refused now.
  3. Gate check-runs on 400e1b09: 50 runs: 41 success, 9 skipped, 0 failed or pending. mergeable_state: clean.
  4. Accepted deviations and PR-body correction: the body's 维护者速读 still names oauth2, app-credential and signed as borrowable kinds (record should-fix g). This seat record is the corrected version: today the kinds are bearer, api-key and basic, and ADR-0143 D2 adds the grant-bearing ones. The relay refuses a body patch here.
  5. Out of scope, routed: should-fix (c), authorWarn on the planned row, is declined: service-datasource: a datasource driver resolves a borrowed connector credential through ADR-0143 D4's per-dispatch resolver (D7 runtime seam) — epic #27261 P2 #27746 flips the row to live in the same release. (d), the composed-artifact counterpart, is spec: composed stacks refuse a datasource whose external.connector names no declared connector instance (composed counterpart of the #27742 check, #25211 shape) — epic #27261 #27803. (f), the liveness note's "resolved per dispatch", is rewritten by service-datasource: a datasource driver resolves a borrowed connector credential through ADR-0143 D4's per-dispatch resolver (D7 runtime seam) — epic #27261 P2 #27746 when it flips the row. Skills-docs: skills + docs: teach external.connector (D7) beside credentialsRef in the datasources skill rule and the two data-modeling guides — epic #27261 P2 #27786, Blocked-by service-datasource: a datasource driver resolves a borrowed connector credential through ADR-0143 D4's per-dispatch resolver (D7 runtime seam) — epic #27261 P2 #27746.
  6. Landing: Tier H (check-governed-merges --pr 27783: docs/adr/** ×1; 502 lines ≤ 3000). pr_ready now, which requests review from GOVERNED_APPROVERS, with needs-user-decision on the PR. After an authorized APPROVED review, the seat clears the labels, enables auto-merge and lands it through the relay.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 30, 2026 00:19
@objectstack-fleet
objectstack-fleet Bot marked this pull request as draft October 30, 2026 00:20
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
Correction to ACCEPT 6511694932, item 6 · 2026-10-30T00:21Z

The seat sent pr_ready at 00:19Z. That was wrong: a Tier H pull is readied only after an authorized approval (Prime Directive #14; check-governed-merges says "no seat flips it ready"). It was converted back to a draft at once through the relay (run 40979060050). Auto-merge was never armed. The review requests to hotlong and os-zhuang that the ready flip triggered stand, and they are the seat's hand-off. Landing after an APPROVED review by either: the seat runs the pre-landing check, clears needs-user-decision, then pr_ready and automerge_enable.

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 protocol:data size/l tests tooling

Projects

None yet

3 participants