Skip to content

fix(service-datasource): migrateCredential's drop-inline refusal names an empty stored credential as empty - #29192

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-3dvabi
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-3dvabi

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread

Fixes #29069

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:数据源凭据迁移(drop-inline)遇到已绑定的 sys_secret 解密出来是空值时,拒绝理由现在说「凭据为空」,用的是 fix(service-datasource)!: the datasource admin doors refuse an empty secret instead of sealing it over the working credential #29064 连接路径同一句话;真的找不到行或密钥变了时,原来那句不变。
  • 为什么改:以前两种情况共用一句「sys_secret 行丢失或加密密钥变了」,空凭据的管理员会去查错地方。
  • 风险与回滚:极低。只改拒绝文案;结果仍是 refused、什么都不写、内联凭据保留。回滚即 revert。
  • 先看:packages/services/service-datasource/src/datasource-admin-service.ts 的 migrateCredential 里新拆出的 resolved === '' 分支。
  • 两分钟验证:pnpm --filter @objectstack/service-datasource exec vitest run src/__tests__/datasource-admin-service.test.ts,预期 Tests 39 passed (39)(main 上新用例红)。
  • 需要你做的:无,席位落地。

What changes

  • migrateCredential's drop-inline step (datasource-admin-service.ts) splits its refusal. A ref that resolves to '' is refused with: "Datasource 'NAME' already references a stored secret, but credential 'REF' is empty (its sys_secret row holds an empty value). Removing the inline credential would leave this datasource with no working credential at all." The middle clause is the connect path's sentence from PR fix(service-datasource)!: the datasource admin doors refuse an empty secret instead of sealing it over the working credential #29064 (DatasourceConnectionService.connect, the secret === '' branch), byte for byte. The remedy says to set the datasource's secret again.
  • A ref that does not resolve (undefined: a missing row or a decrypt failure) keeps its sentence and remedy unchanged.
  • Same outcome on both branches: status: 'refused', no write, the inline copy kept. No new error code, status or member; the result shape is unchanged.
  • Changeset .changeset/29069-migrate-credential-empty-refusal.md: patch on @objectstack/service-datasource.

Tests

src/__tests__/datasource-admin-service.test.ts, migrateCredential (#8155) block:

  • Forced test edit: the existing "cannot be resolved" case moved from toContain('could not be resolved') to an exact pin of the missing-row sentence, plus its remedy, so the unchanged text is held (the card asks a test to pin both texts).
  • New: a ref whose read-back is '' is refused with the empty-credential sentence exactly, the reason names neither "could not be resolved" nor "encryption key", the remedy says to set the secret again, the inline password stays and the only store call is the read.
Red on base, green on head, ablation
  • Red (tests at head, source at base fea2dbb7): Tests 1 failed | 38 passed (39); the new case received the missing-row sentence. The missing-row pin was green on base, as it should be.
  • Green (head b213c369): Tests 39 passed (39).
  • Ablation (scripts/ablation-replace.mjs, the whole resolved === '' branch replaced by the old conflated condition if (resolved == null || resolved === '') {, anchor 1 -> 0, blob 144b0a89 -> bcdb23c3): Tests 1 failed | 38 passed (39); Expected the empty sentence, Received "…but it could not be resolved or decrypted (a missing sys_secret row, or a changed encryption key)…". Restore: blob after restore 144b0a89 equals HEAD's, git diff HEAD empty.

Acceptance notes

  • The empty-credential clause is spelled in two places now (the connect path and this refusal). The claim's file surface is the admin service only, so no shared helper was extracted into datasource-connection-service.ts; each spelling is pinned by its own test.

Verification

All readings at head b213c369.

Package suite, typecheck, lint
  • pnpm --filter @objectstack/service-datasource test: Test Files 69 passed (69), Tests 1153 passed (1153), exit 0.
  • pnpm --filter @objectstack/service-datasource typecheck: exit 0.
  • eslint --no-inline-config on the two changed source files: exit 0. Repo-wide pnpm lint is CI's.
Derived gates (dispatch-gates.mjs --commands, 61 families)
  • 60 of 61 run locally, all exit 0. check-plugin-teardown-shape --self-test first exited 3 on a shallow clone; after git fetch --unshallow it passed (48 cases).
  • pnpm check:dual-build-cjs-loads needs every package built; the full build runs and the reading goes in the os-dev-report, otherwise CI's check-run is the reading.

Dogfood battery: not owed (no ! in the title, no (narrowing) arm).


Generated by Claude Code

…s an empty stored credential as empty

The refusal blamed a missing sys_secret row or a changed encryption key
when the existing ref decrypted to ''. It now names the credential as
empty in the connect path's wording; the missing-row / changed-key
branch keeps its sentence. Same refused status, same outcome.

Claude-Session: https://claude.ai/code/session_01ADHkmP66rDL21VeBrVWovN
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tests tooling labels Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 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; 101 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 — 3 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 c44c8ac4e282c8f4e8135dc90ff1d34e1615d2d6 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json c44c8ac4e282c8f4e8135dc90ff1d34e1615d2d6

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67

席位核对 · ACCEPT · PR #29192 · 2026-11-02T09:17Z

Head-sha: b213c369f60be4e644ae08c252cdc028177b555b

维护者速读(席位终稿)

核对六项

  1. 形态:draft → main;Fixes #29069,与认领 6572628429 一致;3 个文件,均在申报面内。
  2. 前提实测:席位读 diff:空值分支与 undefined 分支拆开,状态、码、结果形状不变;新句中段与连接路径一句逐字一致(dev 实测)。红先 1/39 失败、改后 39/39;消融(放回合并条件)转红并复原。
  3. 门禁:dev 本地派生门禁 61/61 exit 0,包测 1153/1153,typecheck 0。CI 在跑,入队前席位读本 head 全部 check-runs。
  4. 接受的偏差:空凭据句在两处各写一次(连接路径与本拒绝),未抽共享函数——认领文件面只含 admin service,两处各有测试钉住;dev 的 open_question 取 A(保持),接受。被迫改的测试:原「无法解析」用例由 toContain 改为整句钉住,已在正文点名。
  5. 范围外去向:无。Metadata-protocol: none。
  6. 落地条款:Clause-②: no,无契约面(无 packages/spec/src/**,不放宽也不收窄接受集),免契约复审。semver:@objectstack/service-datasource patch,与 changeset 一致。check-governed-merges --pr 29192:待入队前跑。落地授权,维护者原话:「按照 pm-dispatch 规则复核过的都授权」(2026-10-25T11:35Z)。

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/s tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

datasources: migrateCredential's drop-inline refusal blames a missing secret row when the stored credential is empty

1 participant