Repository navigation
fix(spec): datasource password refusals prescribe a credential shape that parses - #27656
Conversation
…that parses The top-level `password` refusal told authors to move the secret into `config`, where every driver contract refuses it; the `external.password` refusal's example `secret:warehouse/password` parses but is never resolved by the shipped binder. Both now prescribe the connection form's secret field or `external.credentialsRef` with a `sys_secret:` handle, and the drivers page's read-only section stops claiming a managed datasource cannot carry `external.credentialsRef`. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzfeVuLKjLP5sdxuajz
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 2 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 167 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 4ec6499c7a78c23e5c5e14a018f4f3fe56eb45c6 && git checkout 4ec6499c7a78c23e5c5e14a018f4f3fe56eb45c6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 0561a988cb0b8b73becf80bbac8b3a407ed16e7e 4e8f849304638aa7020addeb29d56bc6df9b5323 && git checkout -B drift-repro 0561a988cb0b8b73becf80bbac8b3a407ed16e7e && git merge --no-ff 4e8f849304638aa7020addeb29d56bc6df9b5323
node scripts/docs-audit/affected-docs.mjs --json 0561a988cb0b8b73becf80bbac8b3a407ed16e7e
|
…cret handle The optional Postgres demo taught `credentialsRef: 'secret:warehouse/password'`, a value that parses but that the shipped secret binder never resolves. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzfeVuLKjLP5sdxuajz
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract-review-claim: session_01L6bhgV4fMGJHSdSdx3u9mK · head 68b2eec · 2026-10-29T17:39Z Generated by Claude Code |
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract reviewServed-tier: ① Derived judgmentsRead on the head tree, not the PR body.
② Semver level
③ Boundary flags
④ Design judgment
Attempts to break it:
Findings:
No must-fix. The hits file holds against the code it cites and the scenario it is accepted against: each of the card's three prescriptions now leads to a shape that parses, the parse itself is unchanged, and the gates on the head are green. Implemented-by: VERDICT: PASS Generated by Claude Code |
…m to the shipped binder A host can inject its own resolver, so "only `sys_secret:` handles resolve" holds for the shipped binder alone. Same wording in the showcase comment and the changeset. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzfeVuLKjLP5sdxuajz
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract-review-claim: session_01L6bhgV4fMGJHSdSdx3u9mK · head 4e8f849 · 2026-10-29T18:18Z Generated by Claude Code |
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract reviewServed-tier: Delta review on record 6504200509 (PASS, head ① Derived judgmentsRead on the head tree, not the PR body.
② Semver level
③ Boundary flags
④ Design judgment
Attempts to break the reworded sentence:
Findings:
The hits file holds against the code it cites and the scenario it is accepted against: the parse is unchanged, each prescription still leads to a shape that parses, the one overstated clause is now scoped to the component it is true of, and the gates on the head are green. Implemented-by: VERDICT: PASS Generated by Claude Code |
|
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP 维护者速读 (席位终稿)
席位意见:接受。分诊契约复核 PASS(6504200509,head 席位核对 · ACCEPT ·
|
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
Requested by Yibo Wang · project thread
Fixes #27614
Clause-②: no
Metadata-protocol: none
Text only. No key is added, no parse is widened or narrowed, and every input that parsed before parses the same way now. Two refusal messages in
packages/spec/src/data/datasource.zod.tsand the drivers docs page change, so each prescription names a shape the parse accepts. The cross-domain docs edit is granted by triage comment 6500486793 (one PR for the guidance and the drivers page).维护者速读
password的两条拒收提示与 drivers 文档的只读一节,改为指向能通过解析的写法:连接表单的密钥字段,或external.credentialsRef写密钥库签发的sys_secret:句柄。config(驱动契约会再拒一次),示例secret:warehouse/password能解析但运行时永不解引用;文档还说托管数据源不能带external.credentialsRef,实际可以。照着提示改反而失败,AI 最容易照抄。packages/spec/src/data/datasource.zod.ts(两条passwordguidance)、content/docs/data-modeling/drivers.mdx(只读一节的示例与说明段)、packages/spec/src/data/datasource.test.ts(末尾新 describe)。pnpm --filter @objectstack/spec exec vitest run --project local src/data/datasource.test.ts,应见 51 个用例全绿;新的 3 个用例从拒收文案里取出示例句柄写回,解析通过。What changes
Each item was measured on
origin/main178b5238first, by parsing the input and then following the refusal exactly as written.1. Top-level
passwordon a datasource (DatasourceSchemaguidance).passwordmust never be inlined on a datasource. Interpolate it from the environment insideconfig, or for an external datasource reference the secrets store viaexternal.credentialsRef." Following the first half (movingpasswordintoconfigof a postgres datasource) was refused again atconfig.passwordwith the driver contract'sINLINE_CREDENTIAL_REFUSEDtext. The second half restrictedexternal.credentialsRefto external datasources, which is not so since the managed allowance.passwordmust never be inlined on a datasource, and not inconfigeither: the driver contracts refuseconfig.passwordand auser:password@URL. Bind the secret instead: the Setup → Datasources connection form's secret field encrypts it intosys_secretand stores the opaque handle atexternal.credentialsRef, or set that key to a handle the secret store minted (e.g.external: { credentialsRef: "sys_secret:9f2c" }).external.credentialsRefis valid in everyschemaMode, a managed datasource included."schemaModedefaults tomanaged).2.
passwordinsideexternal(ExternalDatasourceSettingsSchemaguidance).credentialsRef(e.g.credentialsRef: "secret:warehouse/password")." That value parses, but the shipped binder resolves onlysys_secret:handles (parseCredentialsRefinpackages/services/service-datasource/src/datasource-secret-binder.ts), andexternal-datasources.mdx§ Credentials already says a hand-written path will not dereference. The datasource is left unconnected.passwordmust never be inlined. Bind the secret instead: the Setup → Datasources connection form's secret field encrypts it intosys_secretand stores the opaque handle here, or setcredentialsRefto a handle the secret store minted (e.g.credentialsRef: "sys_secret:9f2c"). The shipped binder resolves onlysys_secret:handles; it never dereferences a hand-written path."3.
content/docs/data-modeling/drivers.mdx, § Read-only.config: { url: process.env.ANALYTICS_RO_URL }, and the paragraph after it said "external.credentialsRefis a federation key — the parse rejects anexternalblock on amanageddatasource — so a managed connection carries its credentials inconfig". Measured onorigin/main: managed +external: { credentialsRef }parses, and aconfig.urlcarryinguser:password@is refused withURL_EMBEDDED_CREDENTIAL_REFUSED.config: { host, database, username: 'analytics_ro' }plusexternal: { credentialsRef: 'sys_secret:9f2c…' }, and the paragraph says the password never goes inconfig, names both ways to bind it, sayscredentialsRefis the oneexternalkey a managed datasource may carry, and linksexternal-datasources#4-credentials. The sample shape is pinned by a parse test.externalblock whoseschemaModeismanaged" now reads "rejects every federation key,allowWritesincluded, in anexternalblock whoseschemaModeismanaged(onlycredentialsRefmay sit there)". Same false claim, same section.config: { url: process.env.… }sample gains one paragraph saying eachurlmust be credential-free at publish, the password is bound throughexternal.credentialsRef, and the user stays in the URL (mongodb://svc@host/db), which the mongo URL branch needs for the bound secret to be injected.Also, per the seat's claim amendment 6502458097: the commented-out Postgres demo in
examples/app-showcase/src/system/datasources/showcase-external.datasource.tsnow bindscredentialsRef: 'sys_secret:9f2c…'instead of'secret:warehouse/password'. It is a comment in a private package, so it needs no changeset and moves no declaration.Also in
datasource.zod.ts: theconfigJSDoc ("passwords usually interpolated from ENV") now saysconfignever carries the credential. It is a code comment and reaches no generated output.No other copy of these prescriptions exists in
packages/spec,content/docs/**orskills/**(grep below).content/docs/references/**and every other generated artifact are unchanged:check:generated --fixrebuilt spec and reported all 14 artifacts current, since guidance strings are not projected into the references.Tests
packages/spec/src/data/datasource.test.tsgainsdescribe('datasource \password` guidance — following it parses')`, one test per guidance string plus one for the docs sample:password: parse the refused input, read theexternal: { credentialsRef: "…" }example out of the refusal message, write it on the same datasource, and assert it parses asmanagedand keeps the handle. A control in the same test asserts the replaced prescription (password moved intoconfig) is still refused.external.password: read thecredentialsRef: "…"example out of the refusal, assert it starts withsys_secret:, write it, and assert it parses.Runs
pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 src/data/datasource.test.tson0dcf18c5:Test Files 1 passed (1),Tests 51 passed (51).git checkout origin/main -- packages/spec/src/data/datasource.zod.ts, same command: exit 1,Tests 2 failed | 49 passed (51), the two failures being exactly the two guidance tests. Restored withgit checkout HEAD -- …;git diff HEADempty and the file's hash equals the HEAD blob (32515dd9…).os-verify-lock,pnpm test && pnpm test:repo && pnpm typecheckinpackages/spec: localTest Files 752 passed (752),Tests 22346 passed | 1 todo; repoTest Files 86 passed (86),Tests 1433 passed (1433); typecheck clean.VERDICT command-exit 0.pnpm check:generated --fix(spec, under the lock): "All 14 generated artifacts are up to date — measured against packages/spec/dist, BUILT BY THIS RUN". Nothing written.Measurement on origin/main 178b523
A throwaway probe (deleted before the commit) parsed each shape with
DatasourceSchema.safeParse:password→unrecognized_keys, guidance "Interpolate it from the environment insideconfig…".passwordmoved intoconfig→ refused atconfig.password("passwordis a credential and is not accepted inline in driver config …").external: { password }on an external datasource →unrecognized_keys, guidance withcredentialsRef: "secret:warehouse/password"; that value written →success=true, but not asys_secret:handle, so it never resolves.external: { credentialsRef: 'sys_secret:9f2c' }→success=true(contradicts the drivers page).postgres://analytics_ro:pw@db/analyticsURL → refused atconfig.url(URL_EMBEDDED_CREDENTIAL_REFUSED).Acceptance notes
The order's grep (
packages/spec,content/docs/**,skills/**) for the same prescriptions, plus one hit outside that scope. Nothing below is edited here.skills/objectstack-data/rules/datasources.md:70(governed): "Useexternal.credentialsRefand store the secret in the secret store". True as written; it gives no example and does not say external-only. Named, not edited.docs/adr/0015-external-datasource-federation.md:625(governed): an example withcredentialsRef: 'secret:warehouse/readonly'. An ADR records a decision at its date. Named, not edited.packages/spec/CHANGELOG.mdandcontent/docs/releases/**carry the old "rejects anexternalblock on amanaged" wording in released notes. Release text is not edited.secret:warehouse/…(datasource.test.ts:685,707, objectqldatasource-def-credentials-ref.pin.ts:73, two app-shell tests) pin parse or UI behaviour for an arbitrary string, not a prescription. Left as is.drivers.mdx:332(password: process.env.DB_PASSWORD) is anew SqlDriver({ connection })constructor call, not datasource metadata, and never passes the publish door. Left as is.Verification
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 102 commands at0dcf18c5. Run serially: 95 exit 0 on the first pass, and 7 exit 3 (prerequisite only: no built dist, and the shallow clone missing the teardown self-test's pinned fixture commit):check-plugin-teardown-shape --self-test,check:doc-formula-expressions,check:doc-security-posture,check:skill-examples,check:docs-transcript-drift,check:dual-build-cjs-loads,check:lean-entry-closure. Aftergit fetch origin 621a4876…and a fullturbo run build --concurrency=2under the lock (Tasks: 115 successful, 115 total), all 7 exit 0 (e.g. "274 prose examples type-check", "112 published require entry point(s) across 69 package(s) load", "31 ObjectSchema.create example(s) … carry an os validate-clean security posture").dispatch-gates --ran: "102 derived, 102 run, 0 NOT-MEASURED, 0 UNRUN".68b2eec2(the showcase comment fix): the derivation grows to 104 (addscheck:examples-live-importsandcheck:i18n-coverage). After a full build (Tasks: 115 successful, 115 total), all 104 run serially, all exit 0;dispatch-gates --ran: "104 derived, 104 run, 0 NOT-MEASURED, 0 UNRUN".4e8f8493(contract review should-fix: thesys_secret:-only claim scoped to the shipped binder in the guidance, the showcase comment and the changeset): datasource testsTests 51 passed (51); full buildTasks: 115 successful, 115 total;check:generatedall 14 artifacts current; the same 104 derived, all exit 0,dispatch-gates --ran: "104 derived, 104 run, 0 NOT-MEASURED, 0 UNRUN".GITHUB_EVENT_NAME=pull_request node scripts/check-changeset-no-major.mjs --base origin/main --event(this body): exit 0, "LEVEL AXIS: this PR declares clause-②noand metadata-protocolnone, and it introduces no changeset entry gradedminoror above"; the one changeset grades@objectstack/specpatch.!in the title and no(narrowing)arm); noui/**file moves, so no ui scripts suite. The repo-wide CI families are on the PR's check-runs.Generated by Claude Code