Skip to content

docs(skills): objectstack-data and the data-modeling guides teach external.connector, the credential a datasource borrows from a connector instance, and point plugin drivers at the conformance kit - #27954

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

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

epic:#27261#1 · marchtian · session_017ihL2eXpMhQkzjoYjCTKR6

Requested by 田騏 · project thread

Fable review owed. Written while the Fable quota is out. This PR touches skills/** (Tier H): it lands only after an independent full review at CONTRACT_REVIEW_TIER, a contract review record PASS on its head, and an approver's APPROVE. Kept in draft until then.

Fixes #27786

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么: 技能 objectstack-data 的数据源规则和三篇数据建模指南开始教 external.connector:API 型数据源(插件驱动,如 OData)可以借用一个已声明连接器实例的凭据,一次授权同时服务连接器的流程动作和数据源的记录;与 credentialsRef 二选一,内置数据库驱动和 managed 数据源上被拒。指南给出一个完整示例(OData 数据源点名连接器实例)、拒绝规则、连接时 fail-closed、凭据未设置时每次请求 503、401 刷新一次重试。virtual-objects.mdx 另说明自写插件驱动如何自己借用并出示凭据,并新增「Run the conformance kit」一节,指向 @objectstack/objectql 的 definePluginDriverConformanceSuite。
  • 为什么改: D7 裁决(6486136301)的技能与文档一半;spec(feat(spec): an API-backed datasource names a declared connector instance in external.connector and borrows its credential (ADR-0015 D7) #27783)和运行时(feat(service-datasource): a datasource naming a connector instance in external.connector borrows its credential, and its driver presents it per request (ADR-0015 D7) #27819)都已在 main,但技能和指南只教 credentialsRef,AI 作者不知道这个键。
  • 风险与回滚: 只改技能规则、文档和一个 @objectstack/skills patch changeset;不动代码与 schema。技能文件在 token 上限内(909/911,上限不动)。回滚即 revert。
  • 先看: skills/objectstack-data/rules/datasources.md 的 Credentials 一节;external-datasources.mdx 的「Borrowing a connector instance's credential」。
  • 需要您做的: Fable 复审与契约复审通过后批准(Tier H)。

What changes

  • skills/objectstack-data/rules/datasources.md — "Credentials — one channel, fail-closed": external names exactly one credential channel, credentialsRef (a secret-store handle resolved at connect) or connector (an API-backed datasource on a plugin driver such as @objectstack/driver-odata borrows the credential of a declared connectors instance with a provider and an auth). Where connector is refused (beside credentialsRef, on a built-in driver, on a managed datasource), and that an instance that cannot lend, or a stack with no automation service, leaves the datasource unconnected with the reason. The form is shown as schemaMode: 'external', external: { connector: 'erp_api' }, the instance's auth must be other than none, an undeclared instance is refused at defineStack (else at connect), and a connector credential not set yet answers 503 per request. Paid for inside the file (the field.columnName history clause, the auto-connect lead-in, the escape-hatch note and phrases that restated their neighbours shortened or dropped): 903 to 909 tokens against a ceiling of 911, which does not move.
  • content/docs/data-modeling/external-datasources.mdx — §4 Credentials gains "Borrowing a connector instance's credential": the channel, an os:check example (a rest connector instance erp_api with a bearer credentialRef, and an OData datasource naming it), how the instance's auth is presented, and the rules: one channel, API-backed datasources only, an instance that can lend (judged when the stack is validated and when a datasource is saved under Setup), fail-closed at connect, a credential not set yet answered 503 per request until it is set, rotation (a store write reaches the next request; a 401 is retried once with a fresh credential).
  • content/docs/data-modeling/odata-datasources.mdx (scope addition 6513665711) — §3 Credentials names the two channels; the existing text becomes "A secret of its own: credentialsRef", unchanged, the os serve caveat sentence included (os serve: a stack's plugin driver resolves its datasource's external.credentialsRef through the host's datasource secret binder (driver-odata on os serve) — epic #27261 P2 #27875's); a new "Borrowing a connector instance's credential" subsection shows the stack fragment with the plugin's auth left at none, how each auth type is sent, the borrow at the first request and the refresh/401 rule, and the fail-closed list. The "Check the service first" bullet and the auth option row mention the connector path.
  • content/docs/data-modeling/virtual-objects.mdx — the Credentials section says what external.connector does for a driver of your own: the connect step judges the instance but hands an already-built driver nothing, so the driver borrows from the connector-credential-source service and presents through callWithBorrowedCredential, scrubbing upstream text with withoutBorrowedCredential (a sketch, with the OData driver as the reference). New "Run the conformance kit" subsection (scope addition 6513665711): definePluginDriverConformanceSuite / runPluginDriverConformance from @objectstack/objectql, a minimal call, what the floor tier asserts, setupUnavailable, writeProbes, the opt-in direct tier, and the OData driver's own run. Floor step 3 now says the kit ships and the trust built on it does not exist yet.
  • .changeset/27786-skills-external-connector.md — @objectstack/skills patch (route 1), both declaration lines.

Why

ADR-0015 amendment D7 (ruling 6486136301 on #27298): one Connect per vendor serves the connector's flow actions and the datasource's virtual objects. The spec half (#27742, PR #27783) and the runtime half (#27746, PR #27819, ed8b2375) are on main; the datasources skill rule and the data-modeling guides still taught only credentialsRef, and no guide pointed an out-of-repo driver at the conformance kit (#27744, PR #27798).

Verification

  • The examples parse against main's schema. With @objectstack/spec built from this tree, defineStack accepts the os:check example in external-datasources.mdx and the OData fragment wrapped into a stack. Each refusal the pages name was run too and refused with the message described: both channels, connector on postgres, connector on a managed datasource, an undeclared instance, a catalog descriptor (no provider), and auth: { type: 'none' }. A disabled instance is accepted at build, as the pages say (it is refused when the datasource connects).
  • The two virtual-objects.mdx sketches type-check. The credential sketch against @objectstack/types and @objectstack/core, the conformance-kit call against @objectstack/objectql, under strict (a planted type error in the same program was reported, so the run checks).
  • Every runtime claim was read off the code at ed8b2375: packages/services/service-automation/src/connector-credential-source.ts (the lend refusals, disabled included; the handle's 503s; the generation move on a credential write), packages/services/service-datasource/src/datasource-connection-service.ts (the already-registered guard's borrow verdict; the factory path) and datasource-connector-credential.ts, packages/types/src/connector-credential-source.ts (callWithBorrowedCredential's expiry, 401 and 503 rules), packages/drivers/driver-odata/src/plugin.ts and transport.ts (first-request borrow, presentation by auth type, the fail-closed refusals), packages/objectql/src/external-driver-floor.ts (a 503 answered ERR_DATASOURCE_UNAVAILABLE), and packages/objectql/src/plugin-driver-conformance.ts (options, tiers, not-offered).
  • Local gates: node scripts/pm/dispatch-gates.mjs --commands derived 67 families from the 5 changed files; after pnpm build (exit 0) every one was run with its exit code recorded before any pipe, all 67 exit 0, and dispatch-gates --ran reads "67 derived famil(ies) accounted for — 67 run, 0 NOT-MEASURED (a DERIVED zero)". Among them: check:skill-examples (276 prose examples type-check, the new os:check block included), check:doc-anchors (the new #borrowing-a-connector-instances-credential and #run-the-conformance-kit links resolve), check-skills-token-ratchet and check:skill-identifier-liveness. Re-run in full on de6aaf6b (the should-fix commit): again 67 derived, 67 run, all exit 0, a DERIVED zero. Head de6aaf6b, origin/main merge base ed0e71db. The full battery is CI's.

Contract review 6515906243 (PASS on ecb4cd3a), should-fixes applied in de6aaf6b

No must-fix. The four should-fixes on rules/datasources.md are applied, inside the same token ceiling (909/911):

  • The one-line form. It now reads schemaMode: 'external', external: { connector: 'erp_api' }, so copying it alone no longer lands on the managed refusal.
  • auth other than none. Said in the rule, as collectDatasourceConnectorErrors and lendRefusal judge it.
  • Where an undeclared instance is refused. "naming an undeclared instance (at defineStack, else at connect)".
  • A credential not set yet. "A connector credential not set yet answers 503 per request", separate from the connect failures.
  • PR body. The model name is removed from the opening paragraph.
  • Not changed: the pre-existing "credentialsRef … resolved at connect, before the driver is built" sentence, which the review notes for the next edit of the file.

The commit only applies findings, so it owes a delta review by the same reviewer.

Acceptance notes

  • The skill rule carries no runnable example. The file had 8 tokens of headroom; the example lives in the guides, and the rule shows the one-line external: { connector: 'erp_api' } form.
  • "Setup → Connector credentials" appears in the runtime's own refusal text, but no Console page by that name was found under ui/; the guides point at the connectors page's "What credentialRef resolves through" instead of naming a Setup page.
  • Worktree name: the Claim names objectstack-issue-27786; the worktree is objectstack-27786 on the claimed branch.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (Not touched.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; no source file changed (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: not touched.
  • 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 with Claude Code

https://claude.ai/code/session_017ihL2eXpMhQkzjoYjCTKR6

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling labels Oct 30, 2026
@objectstack-fleet
objectstack-fleet Bot requested a review from marchtian October 30, 2026 05:00
@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-30T05:03Z) · head ecb4cd3

The seat's at-tier review call is refused by the model quota (Fable still out at the last probe, 2026-10-30T04:26Z), and per contract-review.md the tier is not lowered. Owed on this head: one same-shape CONTRACT_REVIEW_TIER record. Under the 2026-10-29T19:39Z order it also serves as the full review of the skills change.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK

Contract-review-claim: session_01L6bhgV4fMGJHSdSdx3u9mK · head ecb4cd3a259456cf176be05fbf62d81994aa364b · 2026-10-30T05: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: ecb4cd3a259456cf176be05fbf62d81994aa364b
Local-runs: none
Scope: skills/objectstack-data/rules/datasources.md + packages/spec/src/data/datasource.zod.ts, packages/spec/src/stack.zod.ts (collectDatasourceConnectorErrors), packages/spec/src/shared/connector-auth.zod.ts, packages/services/service-automation/src/connector-credential-source.ts, packages/services/service-datasource/src/datasource-connection-service.ts, packages/services/service-datasource/src/datasource-connector-credential.ts, packages/types/src/connector-credential-source.ts, packages/drivers/driver-odata/src/plugin.ts, packages/drivers/driver-odata/src/transport.ts, packages/objectql/src/external-driver-floor.ts, docs/adr/0015-external-datasource-federation.md (the D7 note), skills/objectstack-data/evals/hooks-security-seeds-datasources.json, scripts/check-skills-token-ratchet.mjs — every cited file read on origin/main. The remainder of the diff (content/docs/data-modeling/external-datasources.mdx, odata-datasources.mdx, virtual-objects.mdx, .changeset/27786-skills-external-connector.md) is the lane seat's ACCEPT at TIER_DEFAULT; read here for ① and ② only, not reviewed.

Inputs: card #27786 (body, scope addition 6513665711, Claim 6515043401, os-dev-report 6515382202), PR #27954 (body, five-file list, net diff against main at the head), the 49 check-runs on the head, handoff 6515416392. Under the order the handoff cites, this record also stands as the independent full review of the skills change.

① Derived judgments

  • Accept set: the diff changes no schema, no runtime and no gate. The accept set of @objectstack/spec and of every runtime package is unchanged — right, and the changeset's Clause-②: no / Metadata-protocol: none say so.
  • Public surface moved: the published skills catalog (@objectstack/skills, which packages/skills syncs from skills/**). rules/datasources.md now teaches a second credential channel; every claim it makes about the accept set is judged in ④ and holds. Right.
  • Public surface moved: three docs-site pages (no package publishes them). Their load-bearing runtime claims were met while reading the cited code for ④ and agree with it: the six build refusals (both channels, a built-in driver, managed, an undeclared instance, a provider-less descriptor, auth: none) are ExternalDatasourceSettingsSchema's and collectDatasourceConnectorErrors'; a disabled instance passes the build and fails the connect (lendRefusal, enabled === false); a credential not yet set lends a handle whose get refuses 503 per request; a 401 is answered by one refresh and retry, a second 401 by 503 (callWithBorrowedCredential); the api-key header default is X-API-Key (borrowedAuthOf); the OData plugin refuses a credentialsRef beside the connector and an auth.scheme other than none (plugin.ts); the connect verdict is reached for a plugin driver through the connection service's already-registered guard. Right as far as read; not reviewed beyond that.
  • Gate verdicts on the head: 49 check-runs, every gate-carrying job success — Lint & Repo Gates, TypeScript Type Check (its Type Check · source gates lane runs the published-skills token ratchet, whose row pins datasources.md at 911), Test Core and its shards, Build Core, Build Docs, Check Documentation Links, Check Changeset, PR-body guards, Governed Surface Queue Guard, Test UI, Live E2E UI, Dogfood Regression Gate. The skipped runs are opt-in or path-filtered jobs. No red.

② Semver level

.changeset/27786-skills-external-connector.md: '@objectstack/skills': patch, carrying Clause-②: no and Metadata-protocol: none, both line-initial and both matching the PR body. The diff publishes exactly one released package, @objectstack/skills (route 1: a skills/** edit), and nothing from any other; content/docs/** publishes nothing. No authorable key, accepted value or default moves ⇒ none ⇒ patch. Right. Check Changeset green.

Clause-②: no — matches: no accept set widens or narrows.

③ Boundary flags

  • open_questions: [] in os-dev-report 6515382202 — nothing to answer.
  • Dev flag (PR acceptance notes): the rule carries no runnable example, 8 tokens of headroom before the edit — accepted; the example lives in the guides and check:skill-examples type-checks the os:check block (green).
  • Dev flag: the runtime's refusal text names a Setup page ("Connector credentials") that was not found under ui/; the guides route around it. That text is main's runtime, outside this diff; the dev carried it as an out-of-scope finding for the epic PM (carrier named). Escalated there; not a finding on this tree.
  • Dev flag: the worktree name differs from the Claim's — cosmetic, no bearing on the diff.
  • Flag of mine: the PR body's opening paragraph names the model it was written on by its marketing name; the repository rule keeps model identifiers out of PR titles and bodies. A body edit for the owning seat before the approver reads it — should-fix, a PR-body matter, outside this verdict on the tree.
  • Landing: Tier H (skills/**). This PASS lifts nothing by itself: an authorized APPROVE from a GOVERNED_APPROVERS account follows, then the owning seat lands it; the PR stays draft until that word is spent.

④ Design judgment

skills/objectstack-data/rules/datasources.md — judged against the spec it teaches on main (the D7 note of ADR-0015, datasource.zod.ts, stack.zod.ts, the borrow seam in @objectstack/types and service-automation, the connect path in service-datasource, the OData driver) and against the skill's own datasource eval (eval 3 in hooks-security-seeds-datasources.json: a Postgres reporting table exposed as a read-only federated object).

What holds, sentence by sentence:

  • "external names one credential channel" — ExternalDatasourceSettingsSchema refuses connector beside a bound credentialsRef (CONNECTOR_WITH_CREDENTIALS_REF_REFUSED), rather than ranking them. Exact.
  • "Refused beside credentialsRef, on a built-in driver, and on a managed datasource" — the three refinements on DatasourceSchema in datasource.zod.ts, in that order: the pair; resolveDriverId(ds.driver) !== undefined (every DRIVER_VOCABULARY member); schemaMode === 'managed'. Exact, and the same three the D7 note lists.
  • "a declared connectors instance that has a provider and an auth" — collectDatasourceConnectorErrors at build and lendRefusal at connect each require a declared entry, a non-empty provider, and an auth whose type is not none.
  • "an API-backed datasource (a plugin driver such as @objectstack/driver-odata)" — the built-in-driver refusal leaves only a plugin-contributed driver able to carry connector, and driver-odata is the shipped presenter (borrowed in plugin.ts, callWithBorrowedCredential in transport.ts).
  • "If it cannot be had (no secret store, an unresolvable handle, an undeclared or disabled instance, no automation service), the datasource stays unconnected with a clear error" — no secret store and an unresolvable handle: DatasourceConnectionService.attemptConnect → handleFailure(..., 'failed-credentials', ...); undeclared or disabled instance and no automation service: borrowConnectorCredential answers a reason (the source's lendRefusal, or the no-source message naming @objectstack/service-automation) → the same handleFailure. For a plugin driver the verdict is reached through the already-registered guard, which runs the borrow before answering already-registered, so the claim is true of the OData case too.
  • The trims: the field.columnName history clause goes, the rule stays (a parse error on any object); the auto-connect lead-in loses "built into a live driver … no onEnable", while the heading still says no onEnable, the four gating conditions stand and the escape-hatch note is kept; "Do not declare a mapping you do not mean" restated the sentence before it; "With either off…" becomes "Otherwise every write…". Nothing an author needs is dropped. Eval 3's must_contain set (defineDatasource, datasource, remoteName, columnMap, schemaMode) and must_not_contain (columnName:) are still taught, and the eval's Postgres case still lands on credentialsRef: the rule says connector is refused on a built-in driver. The ratchet row stays at 911 and its gate is green.

Trying to break it:

  • An author writes exactly the one-line form shown, external: { connector: 'erp_api' }, and nothing else. schemaMode defaults to managed, so defineStack refuses with CONNECTOR_ON_MANAGED_REFUSED, whose text prescribes schemaMode: 'external'. The rule names the managed refusal in the same bullet and its table names managed as the default, so the lesson is on the page and the refusal is loud and prescriptive — should-fix: spell the form schemaMode: 'external', external: { connector: 'erp_api' }.
  • An author reads "an auth" and declares auth: { type: 'none' } for an open vendor. Refused at build and again at borrow, with text that says to remove external.connector when the upstream needs no credential — should-fix: "an auth other than none".
  • "an undeclared … instance … stays unconnected". For a defineStack author an undeclared, provider-less or auth-less instance is refused at build by collectDatasourceConnectorErrors and never reaches a connect; the connect-time refusal is the door for a defineDatasource file judged outside its stack and for a Setup-created datasource (defineDatasource is DatasourceSchema.parse alone and sees no connectors). The rule names the later, weaker door; not false, and the earlier one is louder — should-fix: "refused at defineStack; at connect otherwise".
  • A connector credential declared but not yet set. The connect succeeds (borrow judges the declaration and resolves no value) and each request answers 503 ERR_DATASOURCE_UNAVAILABLE until the credential is set. The rule's fail-closed list does not claim otherwise, but an author may read "an unresolvable handle" across both channels — should-fix, one clause: a credential not set yet answers 503 per request, it does not fail the connect. The guides carry it already.
  • "credentialsRef … resolved at connect, before the driver is built" is main's own sentence and true of a factory-built driver; the OData plugin resolves its credentialsRef at the first request (plugin.ts). Pre-existing, not moved by this diff; noted for the next edit of the file.

No must-fix. The rule states nothing the tree contradicts: every refusal it names is one the schema, the stack check or the connect path makes, and every trim leaves the rule it belonged to standing.

Implemented-by: claude/project-thread-fprnjl
Reviewed-by: session_01L6bhgV4fMGJHSdSdx3u9mK

VERDICT: PASS


Generated by Claude Code

…ixes

Shows the connector form with schemaMode 'external', says the instance's
auth must be other than none, names the defineStack refusal of an
undeclared instance, and says a credential not set yet answers 503 per
request rather than failing the connect.

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

Copy link
Copy Markdown
Contributor Author

epic:#27261#1 · marchtian · session_017ihL2eXpMhQkzjoYjCTKR6

Delta-review-request: head de6aaf6b applies contract review 6515906243's four should-fixes to skills/objectstack-data/rules/datasources.md (the one-line form with schemaMode: 'external'; auth other than none; undeclared instance refused at defineStack, else at connect; a credential not set yet answers 503 per request) and nothing else besides the changeset's description of them. 909/911 tokens, ceiling unmoved. The derived gate list re-run in full on de6aaf6b: 67 derived, 67 run, all exit 0. The PR body no longer names a model.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK

Contract-review-claim: session_01L6bhgV4fMGJHSdSdx3u9mK · head de6aaf6bf64ad9cf32282e31aa5c9d44b099db16 · 2026-10-30T06:06Z


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: de6aaf6bf64ad9cf32282e31aa5c9d44b099db16
Local-runs: none
Scope: skills/objectstack-data/rules/datasources.md + packages/spec/src/data/datasource.zod.ts, packages/spec/src/stack.zod.ts (collectDatasourceConnectorErrors, validateCrossReferences, defineStack), packages/services/service-automation/src/connector-credential-source.ts, packages/services/service-datasource/src/datasource-connection-service.ts, packages/services/service-datasource/src/datasource-connector-credential.ts, packages/types/src/connector-credential-source.ts, packages/drivers/driver-odata/src/plugin.ts, packages/objectql/src/external-driver-floor.ts, skills/objectstack-data/evals/hooks-security-seeds-datasources.json, scripts/check-skills-token-ratchet.mjs — every cited file read on origin/main, and each is byte-identical between the PR's base ed0e71db94a73acc02512ff2ee0655106d02e585 and origin/main. The remainder of the diff (content/docs/data-modeling/external-datasources.mdx, odata-datasources.mdx, virtual-objects.mdx, .changeset/27786-skills-external-connector.md) is the lane seat's ACCEPT at TIER_DEFAULT; the changeset is read here for ② only, not reviewed.

Delta review. The previous record on this PR is 6515906243, PASS on head ecb4cd3a259456cf176be05fbf62d81994aa364b. The move since is one commit, the head itself, touching two files (git log and git diff between the two heads, both present locally): skills/objectstack-data/rules/datasources.md (11 lines out, 12 in) and .changeset/27786-skills-external-connector.md (two bullets reworded). No main merge rides along: the PR's base is still ed0e71db94a73acc02512ff2ee0655106d02e585, and the three docs pages are unchanged since the previous head. Inputs: card #27786 (body, scope addition 6513665711, Claim 6515043401, os-dev-report 6515382202), PR #27954 (body, five-file list, net diff against main at the head), the delta-review request 6516065016, the 48 check-runs on the head. Under the order handoff 6515416392 cites, this record also stands as the independent full review of the skills change on this head.

① Derived judgments

  • Accept set: the move changes no schema, no runtime and no gate; the diff as a whole still changes none. The accept set of @objectstack/spec and of every runtime package is unchanged — right, and Clause-②: no / Metadata-protocol: none still say so.
  • Public surface moved: the published skills catalog (@objectstack/skills), as judged in 6515906243; the move edits the same rule file and nothing else that publishes. Every claim the moved sentences make about the accept set is judged in ④ and holds. Right.
  • Public surface moved: the three docs-site pages — untouched by the move; stand as judged in 6515906243.
  • Gate verdicts on the head: 48 check-runs, every one completed, none in_progress; every gate-carrying job success — Lint & Repo Gates (its Lint · repo & tooling gates lane carries check:skill-identifier-liveness), TypeScript Type Check (its Type Check · source gates lane runs the published-skills token ratchet, whose row pins datasources.md at 911 and is not moved by this diff), Test Core and its three shards, Build Core, Build Docs, Check Documentation Links, Check Changeset (both runs), PR-body guards (both runs), Governed Surface Queue Guard, Spec property liveness, Test UI, Live E2E UI, Dogfood Regression Gate. The skipped runs are opt-in or path-filtered jobs (the UI matrix, the dogfood shards, the 18.x harness, the packed-tarball smoke, the schedule gate). No red.

② Semver level

.changeset/27786-skills-external-connector.md: '@objectstack/skills': patch, carrying Clause-②: no and Metadata-protocol: none, both line-initial and both matching the PR body. The move rewords the changeset's two prose bullets to describe the rule as it now reads: the auth other than none, the one-line form with schemaMode: 'external', the undeclared-instance refusal at defineStack or else at connect, the disabled instance and the missing automation service as connect failures, the 503 per request for a credential not set yet, and the three dropped phrases named by what they restated. Each of those sentences is true of the net diff (the columnMap direction is still in the example's comment; the mapping-routing tail and the write-gate tail are gone from the rule). The level is untouched and still right: one released package publishes, @objectstack/skills (route 1, a skills/** edit); content/docs/** publishes nothing; no authorable key, accepted value or default moves ⇒ none ⇒ patch. Check Changeset green.

Clause-②: no — matches: no accept set widens or narrows.

③ Boundary flags

  • open_questions: [] in os-dev-report 6515382202 — nothing to answer; the delta request 6516065016 raises none.
  • Dev flags carried in the PR body (no runnable example in the rule; the runtime's refusal text naming a Setup page not found under ui/, carried as an out-of-scope finding for the epic PM; the worktree name) — unchanged by the move; stand as judged in 6515906243.
  • Flag of mine from 6515906243 (the PR body's opening paragraph named the model it was written on): the opening paragraph no longer names it. Closed; a PR-body matter, outside this verdict on the tree either way.
  • The move is declared as applying the previous record's should-fixes and nothing else. Judged on the diff between the two heads: the four should-fixes land in the four sentences they named, the changeset describes them, and the only other edits are three trims inside the same rule file (metadata file → file; the (remoteColumn → localField) parenthesis; instead of silently resolving the default store), each paying tokens for the added text and each judged in ④. Nothing outside the review's findings is added.
  • Landing: Tier H (skills/**). This PASS lifts nothing by itself: an authorized APPROVE from a GOVERNED_APPROVERS account follows, then the owning seat lands it; the PR stays draft until that word is spent.

④ Design judgment

skills/objectstack-data/rules/datasources.md — the moved sentences judged afresh against the spec the rule teaches on main (datasource.zod.ts, stack.zod.ts, the lending source in service-automation, the connect path in service-datasource, the OData plugin, the engine floor) and against the skill's own datasource eval (eval 3 in hooks-security-seeds-datasources.json). Sentences the move does not touch stand as judged in 6515906243.

What the move changes, sentence by sentence:

  • "a connectors instance with a provider and an auth other than none" — collectDatasourceConnectorErrors (stack.zod.ts) refuses an entry whose auth is absent or whose auth.type === 'none', and lendRefusal (connector-credential-source.ts) judges the same two conditions again at connect. Exact; the previous record's second should-fix, applied. The dropped "declared" is carried by the new undeclared-instance clause; the dropped "(one Connect serves both)" was motive, not rule.
  • The one-line form schemaMode: 'external', external: { connector: 'erp_api' } — ExternalDatasourceSettingsSchema is a closed shape whose other keys are optional or defaulted, so { connector: 'erp_api' } parses (erp_api matches the ^[a-z_][a-z0-9_]*$ pattern); schemaMode: 'external' satisfies the requires 'external' settings refinement and sidesteps CONNECTOR_ON_MANAGED_REFUSED; on a plugin driver (resolveDriverId undefined) the built-in-driver refusal does not fire. Copied alone into a datasource on a plugin driver, the form now reaches defineStack's connector check and nothing earlier. The first should-fix, applied.
  • "Refused … naming an undeclared instance (at defineStack, else at connect)" — defineStack runs validateCrossReferences whenever strict is not false (its default), and that function pushes collectDatasourceConnectorErrors before the object-count early return, so an undeclared name is a build refusal for every strict stack. For a datasource that never passed defineStack (a defineDatasource file judged alone, a Setup-created or strict: false one), DatasourceConnectionService.connect borrows before building the driver, and the already-registered guard borrows before answering already-registered for a plugin driver; lendRefusal's first arm is the undeclared instance, and borrowConnectorCredential returns it as the reason of a failed-credentials outcome. Both doors exist, in that order. The third should-fix, applied.
  • "No secret store, an unresolvable handle, a disabled instance or no automation service leaves the datasource unconnected with a clear error" — the first two are the credentialsRef arm of attemptConnect (handleFailure(..., 'failed-credentials', ...) for a missing resolver, a throw, or a null or empty value); the disabled instance is lendRefusal's fourth arm (enabled === false), which collectDatasourceConnectorErrors deliberately does not judge, so it is a connect-time refusal and only that; no automation service is borrowConnectorCredential's own reason when no source is registered. All four reach the same handleFailure, for a factory-built driver and, through the already-registered guard, for the OData plugin's. The list no longer says "undeclared", which now lives in the refusal bullet with its earlier door named. Exact.
  • "A connector credential not set yet answers 503 per request." — borrow judges the declaration and resolves no value, so the connect succeeds; the handle's get runs fill, whose resolve answers a missing resolution with connectorCredentialRefusal(..., 'SERVICE_UNAVAILABLE', 503); a failed fill leaves held unset, so every later get resolves again and refuses again until the credential is set, when the next get resolves it with no reconnect. The OData transport throws it and the engine floor answers ERR_DATASOURCE_UNAVAILABLE (503) with the cause in the server log. The fourth should-fix, applied, and placed as its own sentence after the connect-failure list so it is not read as a connect failure.
  • The three trims: "*.datasource.ts metadata file" → "file" (the file is still named; nothing lost); the (remoteColumn → localField) parenthesis on the columnMap bullet (the example's own comment on columnMap still reads remoteColumn → localField, so the direction is still on the page); "instead of silently resolving the default store" (the rule still says a query against a mapped object throws, which is the lesson; the dropped clause named the behaviour that does not happen). Nothing an author needs is dropped.
  • Unchanged, and still true of a factory-built driver: "credentialsRef — a secret-store handle, resolved at connect, before the driver is built." The OData plugin resolves its credentialsRef at the first request; noted in 6515906243 for the next edit of the file, pre-existing on main, not moved by this diff.

Eval 3 (a Postgres reporting table exposed as a read-only federated object): the must_contain set (defineDatasource, datasource, remoteName, columnMap, schemaMode) is still taught, columnName: is still refused in the rule's own words, and the eval's case still lands on credentialsRef because the rule says connector is refused on a built-in driver. The ratchet row stays at 911 and its gate is green on the head.

Trying to break the moved text:

  • An author copies the one-line form into a datasource on a built-in driver. connectorOnBuiltinDriverRefusal fires at parse and prescribes credentialsRef; the rule's refusal bullet names the built-in driver in the same sentence. Holds.
  • An author reads "(at defineStack, else at connect)" and expects a provider-less or auth: none instance to be refused only at connect. Both are refused at defineStack too, by the same collector, and again at connect by lendRefusal. The rule attaches the two doors to the undeclared case alone and names the other two conditions as requirements of the instance; it states nothing false, and the louder door is the one an author meets first. Nothing to fix.
  • An author declares the instance with enabled: false and expects a build refusal. The rule puts the disabled instance in the connect-failure sentence, not the refusal bullet, which is where the tree puts it. Holds.
  • An author sets the connector credential after boot and expects to reconnect. The rule says 503 per request, not a connect failure, and the handle's cache logic makes the next request read with it. Holds.
  • Token headroom: the rule now runs 2 tokens under its ceiling by the dev's measure; the ratchet gate on the head is the verdict (green), and the ceiling does not move.

No must-fix. The move states nothing the tree contradicts: each of the four applied should-fixes now reads exactly as the schema, the stack check, the lending source or the connect path behaves, the trims leave every rule they belonged to standing, and nothing beyond the review's findings is added.

Implemented-by: claude/project-thread-fprnjl
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 #27954 @ de6aaf6bf64ad9cf32282e31aa5c9d44b099db16 · 2026-10-30T06:14Z

维护者速读

席位意见:ACCEPT。契约复核 PASS 在案:6515906243(CONTRACT_REVIEW_TIER,ecb4cd3a),以及 delta 复核 6516298495 PASS(当前 head de6aaf6b,四条 should-fix 已采纳)。Fable 额度仍未恢复,按 2026-10-29 19:39Z 指示,这份记录兼作 skills 改动的完整复核。无 must-fix。

席位核对

  1. Shape: +265/−28 across 5 files: the skill rule, three content/docs/data-modeling pages, and a patch changeset on @objectstack/skills.
  2. Premise measured: on main the rule names only credentialsRef. external.connector shipped in feat(spec): an API-backed datasource names a declared connector instance in external.connector and borrows its credential (ADR-0015 D7) #27783 (spec) and feat(service-datasource): a datasource naming a connector instance in external.connector borrows its credential, and its driver presents it per request (ADR-0015 D7) #27819 (runtime), both merged.
  3. Gate check-runs on de6aaf6b: 48 runs: 34 success, 14 skipped, 0 failed. Local: 67 derived gates, all exit 0.
  4. Accepted deviations: none. The four should-fixes in 6515906243 are applied in de6aaf6b: the full schemaMode: 'external' form, "an auth other than none", "refused at defineStack; at connect otherwise", and that an unset credential answers 503 per request. The model name is gone from the PR body.
  5. Out of scope, noted: existing runtime text says "Setup → Connector credentials", and no Console page has that name. The dev noted it; it is not filed and not this PR's.
  6. Landing: governed, Tier H (check-governed-merges --pr 27954: skills/** ×1; 293 lines ≤ 3000). It stays draft with needs-user-decision, and reviewers are requested. After an APPROVE, the seat clears the label, readies it and arms automerge.

@os-zhuang
os-zhuang marked this pull request as ready for review October 30, 2026 07:09
…-fprnjl

# Conflicts:
#	content/docs/data-modeling/odata-datasources.mdx
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK

Regen-provenance: 6516298495 · de6aaf6bf64ad9cf32282e31aa5c9d44b099db16 → d3bf069d014e3ad4289627618283692adb62a610 · diff <(git diff ed0e71db de6aaf6b -- . ':(exclude)content/docs/**' ':(exclude).changeset/**') <(git diff 46943657 d3bf069d -- . ':(exclude)content/docs/**' ':(exclude).changeset/**') → (empty)

The move is one merge of origin/main (second parent 4694365). The governed rule file skills/objectstack-data/rules/datasources.md carries byte-identical PR-side hunks before and after the hop; the only PR-side hunks that changed are on prose faces outside the review surface (content/docs/data-modeling/*.mdx and the changeset, where the merge resolution now says "the host's secret store" for the OData plugin's credentialsRef resolution, matching main). So record 6516298495 keeps governing this head. Tier H: an authorized approval still follows before landing.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit e9caebe Oct 30, 2026
47 checks passed
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 tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

skills + docs: teach external.connector (D7) beside credentialsRef in the datasources skill rule and the two data-modeling guides — epic #27261 P2

3 participants