Repository navigation
fix(service-datasource)!: the datasource admin doors store ssl, autoConnect and description instead of dropping them, and refuse an inline ssl.key - #28648
Conversation
…ring at the datasource admin write doors POST /api/v1/datasources and PATCH /api/v1/datasources/:name projected the body onto the record's known keys, so a top-level password or connectionString vanished and the write answered success while os compile refuses the same datasource by name. Both doors now refuse either key before anything is written, with DatasourceSchema's own refusal for that key under the heading the record judgement uses. Only key names are judged, so the answer never echoes the credential. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5moUefysT3ba6SpdYY
… refuses at the datasource test, create and update doors The unsaved-draft probe (POST /api/v1/datasources/test) and the two write doors built their record from the known keys only, so a top-level password on the probe, and any other key the strict shape refuses (url one level too high, an alias such as type, a retired block, an unknown key) on all three doors, vanished without a word. The doors now refuse them 400 DATASOURCE_ADMIN_ERROR with DatasourceSchema's own root unrecognized-key refusal, derived from the body's key names at run time; values are never judged or echoed. Co-Authored-By: Claude <noreply@anthropic.com>
…el key refusal Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com> # Conflicts: # packages/services/service-datasource/src/__tests__/datasource-admin-top-level-credential.test.ts # packages/services/service-datasource/src/datasource-admin-service.ts
…vel key DatasourceSchema declares Co-Authored-By: Claude <noreply@anthropic.com>
…t for the declared-key projection Co-Authored-By: Claude <noreply@anthropic.com>
…nd refuse its private key Co-Authored-By: Claude <noreply@anthropic.com>
…age reason only Co-Authored-By: Claude <noreply@anthropic.com>
…-alusao # Conflicts: # packages/services/service-datasource/src/datasource-admin-service.ts
📓 Docs Drift CheckThis PR changes 1 package(s): 5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 3 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 bbad38ebabf8746563072bdfa92db8dca339b39a && git checkout bbad38ebabf8746563072bdfa92db8dca339b39a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6b9c1ca99144b1c96ef05041dd3b0319e43d8f4b 1599499f3dc5ad6303a7233fd1acef153f0cbe8d && git checkout -B drift-repro 6b9c1ca99144b1c96ef05041dd3b0319e43d8f4b && git merge --no-ff 1599499f3dc5ad6303a7233fd1acef153f0cbe8d
node scripts/docs-audit/affected-docs.mjs --json 6b9c1ca99144b1c96ef05041dd3b0319e43d8f4b
|
|
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH Contract reviewServed-tier: Inputs read: card #28633 (body, claim 6556196859, os-dev-report 6557222993, claim amendment 6557238812 with the seat's ruling), parent #28612, PR #28648 body, file list and net diff against Check-runs on the head, read honestly: every completed run is ① Derived judgmentsEach accept-set or public-surface change the diff implies, named right or wrong against the cited code:
② Semver levelChangeset Clause-②: yes (narrowing) — matches the amendment 6557238812 and what the diff publishes. ③ Boundary flags
④ Design judgmentThe narrowing, read against the published spec on I tried to break the alternative. Storing it (option B) puts a private key at rest in cleartext in the datasource row, read back by paths the package's redaction does not cover (③) — the exact defect #7990 ruled against for passwords — so delivering the capability as declared would reintroduce a security defect, and binding it through the secret store (C) is spec surface this card is barred from. Within the card's surface the refusal is the only safe arm, the seat has ruled it, it is loud (named, never echoed), and no producer sends the key. So the narrowing is justified at this door today, but it is incomplete as a design: the spec and the door now disagree (
Implemented-by: VERDICT: PASS |
|
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH 席位核对 · ACCEPT · PR #28648 · 2026-11-01T11:37ZHead-sha: 维护者速读(席位终稿)
核对六项
|
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH
Requested by Leehom · project thread
Part of #28633 (the card stays open for its read-path follow-up, which closes it)
Clause-②: yes (narrowing) — measured, not the claim's expected
no:POST /api/v1/datasourcesandPATCH /api/v1/datasources/NAMEnow storessl,autoConnectanddescription(accepted and dropped before),GET /api/v1/datasources/NAMEserves them (three new answer members),POST /api/v1/datasources/testhands the draft'ssslto the probe, and the exportedDatasourceDraft,StoredDatasource,ProbeInputandConnectivityBearingFieldstypes gain those members. The(narrowing)arm: one body that used to answer 201 / 200 (its wholesslblock silently dropped) is now refused with400 DATASOURCE_ADMIN_ERROR: ansslblock that carries a client private key (ssl.key). No new error code.Metadata-protocol: none
Skills-docs: updated here —
content/docs/data-modeling/drivers.mdx(the TLS bullet says the Setup doors store the block and refusekey); no page underskills/**teaches the admin request body.BREAKING accept-set narrowing at the datasource admin doors (
@objectstack/service-datasource): an inlinessl.keyis refused.Built on PR #28632 (same file, landed as
819b1017): this branch started from its headf1438f52and has mergedorigin/mainsince, so the diff is only this change.维护者速读
DatasourceSchema声明的所有顶层键(ssl、autoConnect、description以前被悄悄丢掉)。保留哪些键在运行时直接从 schema 读出,不再有手写清单;origin和 ADR-0010 保护字段(_lock*等)仍由服务端决定,不从请求体取。连接测试用的是保存时会存的同一份记录,所以测试也走同样的 TLS。编辑ssl会重建连接。ssl,autoConnectanddescription, so a datasource created there loses its TLS settings #28633:带 TLS 设置创建数据源,接口回 201,但存下来的记录没有ssl,实际连接不加密,也没有任何提示。ssl.key(客户端私钥)在三个入口都被拒(400,点名不回显值),因为这个入口没有加密存放它的通道,存下来就是明文。这是一个需要您确认的取舍,见下方 Acceptance notes 第一条。读取时永不返回已存的私钥;编辑时原样回传ssl会保留已存的私钥。控制台、packages/client、CLI 都不发送这三个键。回滚即 revert。packages/services/service-datasource/src/datasource-admin-service.ts(storedDatasourceKeys、toRecord、编辑合并、inlineSslKeyRefusal、getDatasource);src/__tests__/datasource-admin-declared-keys-kept.test.ts;src/datasource-admin-plugin.ts(探测把ssl交给驱动工厂)。pnpm --filter @objectstack/service-datasource exec vitest run src/__tests__/datasource-admin-declared-keys-kept.test.ts→ 18/18 通过。ssl.key的处理(推荐:先拒收,等有加密通道再开放);其余席位落地(收窄需先过合约评审)。What changes
One projection, derived from the schema (
datasource-admin-service.ts). The hand list intoRecord(name,label,driver,schemaMode,config,external,pool,active) becomesstoredDatasourceKeys(): every key ofDatasourceSchema.shaperead at run time, minus the keys the door deliberately does not take from the body, each named with its reason:origin— the schema's own words: "server-managed, read-only", "never accepted from client input". The door stampsruntime, as before.MetadataProtectionFields(_lock,_lockReason,_lockSource,_lockDocsUrl,_provenance,_packageId,_packageVersion) — set by a code package's loader only. The metadata save door refuses an authored lock for exactly this reason (ADR-0010 §3.5,authoredLockFamilyRefusal), so storing them here would be the way around that door. The seat asked to include them if the schema derivation covers them; it does, and this is the reason they are left out.So the stored set at this head is
name,label,driver,config,pool,ssl,description,active,autoConnect,schemaMode,external. A key the schema declares later is stored the day it lands.toRecord(input). Edit merges every patched key under the same set (namenever,externalkeeps itscredentialsRefrule). The record judgement on an edit now runs whenever the patch touches a key other thanlabel/active(the two documented exemptions), so anssledit is judged by the schema's strictsslshape before it is stored; adescription/autoConnectedit is judged too.draft = toRecord(...)and both judges and probes it:ProbeInputgainsssl, and the plugin's probe hands it tofactory.create(datasource-admin-plugin.ts), as the saved datasource's connect does throughtoSpec(ssl: record.ssl).datasourceConnectivityChangednow comparesssl(toSpecreads it), so an edit that changes the TLS block rebuilds the pool; adescriptionedit does not.getDatasourceservesssl(withoutkey),autoConnectanddescription.ssl.key.inlineSslKeyRefusalrefuses a body whosesslcarrieskey, on create and edit (throw,400 DATASOURCE_ADMIN_ERROR, under the door's existing headings) and on the test door (ok: false, like its other record refusals, nothing probed). The message namesssl.key, never the value. A stored key (a row written by another door) is withheld on read, and an edit that sends back the served block keeps it (restoreRedactedConfig's rule, applied to the one withheld member).Tests
New
src/__tests__/datasource-admin-declared-keys-kept.test.ts, same harness as #28607 / #28632 (real routes overHonoHttpServer, realDatasourceAdminServiceon in-memory stores,entitled-caller.fixture), recording the stored record, the record handed toregisterPool/reregisterPool, and the probe input; one case drives the realDatasourceAdminServicePluginprobe over a fake driver factory and reads the spec handed tofactory.create.ssl+autoConnect+description→ 201, all three stored, the record handed toregisterPoolcarriesssl; aGETreturns them; an unknownsslmember (insecure) is refused (400DATASOURCE_ADMIN_ERROR, nothing stored).sslstored and the pool rebuilt from a record carrying it;autoConnect/descriptionstored.ssl; the plugin probe hands it to the driver factory.ssl.key: create, edit and test refuse it (status +error.code+success, orresult.ok: false; the value absent from the raw answer; nothing stored, minted, opened or probed); aGETof a row already holding a key does not serve it; an edit sending back the served block keeps the stored key.name,driver,config,originas before;originis stampedruntimeand no protection field is stored; adescription-only edit does not rebuild the pool; alabel-only edit leaves a storedsslalone; a test draft withoutsslprobes without it. fix(service-datasource)!: refuse every top-level key DatasourceSchema refuses at the datasource test, create and update doors #28632's and fix(service-datasource)!: refuse a top-level password or connectionString at the datasource admin write doors #28607's suites (25 cases) stay green.Red first, green, ablation
f1438f52, fix(service-datasource)!: refuse every top-level key DatasourceSchema refuses at the datasource test, create and update doors #28632's head, test file kept):Tests 12 failed | 6 passed (18). The six passing are the controls. The "read withholds a stored key" case is red at the base because the read serves nosslat all, so it pins the served block, not a leak at the base.Tests 18 passed (18).node scripts/ablation-replace.mjs, the new file plus fix(service-datasource)!: refuse every top-level key DatasourceSchema refuses at the datasource test, create and update doors #28632's file, 43 cases; the tests import the service and plugin fromsrc/, so no build sits between mutation and run), each restore provenblob == HEADandgit diff HEADempty:toRecordback to the hand list: 6 failed (create/read/ssl-judged, both probe cases, test-door key refusal).ssl.keyrefusal removed on create / edit / test: 1 failed each, the matching refusal.sslunfiltered: 1 failed (the withheld key).sslto the factory: 1 failed.sslout ofdatasourceConnectivityChanged: 1 failed (the edit no longer rebuilds).Acceptance notes
ssl.key(also in the os-dev-report'sopen_questions). The card asks that a secret insidesslbe "redacted on read, asconfigsecrets are today". Measured: this door does not storeconfigsecrets at all (the driver contracts refuseconfig.password; the credential goes throughsecretinto the secret store and only thecredentialsRefis stored), and the platform's datasource redaction coversconfig, notssl. Storingssl.keywould put a private key at rest in cleartext in the datasource row, outside every redaction this package controls. This PR takes the conservative arm: refuse it at all three doors, withhold it on this door's read. Accepting it would need a spec-side change first (redaction ofssl.keywherever datasource rows are read, or a sealed channel for it), which is the spec lane's; the seat has the detail./metasave door does, would be a further narrowing of this door, so it is reported rather than done here.poolandschemaModefrom the spec it hands the factory, as before; neither changes whether or how the connection is encrypted, and the card asks forssl.ui/packages/app-shell/src/views/metadata-admin/datasource/) sendslabel,driver,schemaMode,config,external,secretand never these three keys;packages/clienthas no create/update/test method; the CLI datasource commands are read-only.description,sslandautoConnectamong the keys "the doors do not store"; it is not edited, and this PR's changeset carries the superseding sentence.Verification
All readings on head
1599499f(the merge oforigin/mainat68a2a216, after #28632 landed as819b1017; the diff againstmainis the seven files of this change).pnpm --filter @objectstack/service-datasource test→ Test Files 68 passed (68), Tests 1143 passed (1143).pnpm --filter @objectstack/service-datasource typecheckexit 0;tsc --listFilesincludes the new test file.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack→ 91 families (61 before the docs line; thedrivers.mdxedit adds the docs families), each run after a fullpnpm build --concurrency=2(117/117) with its exit code captured before any pipe and stdin from/dev/null: 91 × exit 0.check-plugin-teardown-shape.mjs --self-testfirst answered exit 3 (shallow clone, fixture commit absent) and exit 0 aftergit fetch --depth=1of that commit.--ran:✓ dispatch-gates --ran: 91 derived famil(ies) accounted for — 91 run, 0 NOT-MEASURED.node scripts/check-commit-card-trailers.mjs --range origin/main..HEADexit 0;node scripts/check-platform-checklist.mjs --changed-from origin/mainexit 0 (1 anchored file, 2 anchors judged, 0 lost);pnpm exec eslint --no-inline-config packages/services/service-datasourceexit 0, no findings; control-byte scan of the changed files: no hits. The repo-widepnpm lintis CI's.!and the(narrowing)arm), after the full build, under the lock,pnpm --filter @objectstack/dogfood test→ Test Files 1 failed | 366 passed | 3 skipped (370), Tests 1 failed | 2965 passed | 28 skipped (2994). The one failure is not this change and is the same one fix(service-datasource)!: refuse every top-level key DatasourceSchema refuses at the datasource test, create and update doors #28632 recorded:route-ledger-live-mount-parity.dogfood.test.tsreportsruntime/route-ledger.ts: GET /packages/:id/customizations — LEDGERED BUT NOT MOUNTED, a runtime route from feat(runtime,spec): GET /packages/:id/customizations answers a base's overrides, extensions and switched-off items in one read #28595 (519089e1) onmain. This diff touches onlyservice-datasourceand one docs page.test:repo, the lint farm, the type-check lanes) are read off this PR's check-runs by the seat.🤖 Generated with Claude Code
https://claude.ai/code/session_01FEkQ5oV7AGmgEqUAMoY7y9