Repository navigation
Commit a0f8654
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XL1T1xRb3yMBHrxcdDdxwnE)_
Fixes #28283
Clause-②: no — measured: the connect verdict's `reason` text changes
shape and an internal owner map releases on failure. No schema, export,
type, error code or status moves, nothing is newly refused or accepted,
and no consumer outside this package's own pins reads the sentence (`git
grep "not set on this host"` outside `service-datasource`: 0 hits).
Metadata-protocol: none
## 维护者速读
- **改了什么**:代码里声明的数据源在环境变量 `OS_DATASOURCE_NAME_PASSWORD`
没设、连接又失败时,失败原因改为「驱动自己的错误 + 结尾的 `(note:
…变量没设…)`」,不再用破折号把变量说成失败的原因;另外,一次失败的连接会释放它登记的变量,之后另一个推导出同一变量的数据源不会再被误判为冲突。
- **为什么改**:#28208 的契约评审 S1/S2:主机不可达、驱动模块缺失、TLS
失败时,运维被引去先设密码;失败的连接还一直占着冲突登记,直到 `disconnect`。
- **风险与回滚**:低。只改 `datasource-connection-service.ts` 里 env
凭据槽的两处:原因文本的拼接,和失败路径上释放本次登记。两个都活着的同变量数据源仍被拒绝。回滚即 revert。
-
**先看**:`packages/services/service-datasource/src/datasource-connection-service.ts`(`claimedVariable`
的登记与 catch 里的释放、`(note: …)`
那一句);`src/__tests__/datasource-env-credential.test.ts`(两条新 pin)。
- **两分钟验证**:`pnpm --filter @objectstack/service-datasource exec vitest
run --maxWorkers=2 src/__tests__/datasource-env-credential.test.ts`,预期
26/26 通过。
- **需要你做的**:无,席位落地。
## What changes
Both items are in `DatasourceConnectionService.connect()`'s
env-credential slot (#27687):
- **S1, the reason.** With the slot applied and the variable unset, a
failed build used to answer `DRIVER_ERROR — datasource 'x' is defined in
code and reads its credential from OS_DATASOURCE_X_PASSWORD at connect,
which is not set on this host`. The base already led with the driver's
error, so the order's hypothesis that the reason led with the variable
did not hold as written. The dash joined the sentence on as the
explanation, though, for every cause: an unreachable host, a missing
driver module, a TLS failure. It now reads `DRIVER_ERROR (note:
datasource 'x' is defined in code and reads its credential from
OS_DATASOURCE_X_PASSWORD at connect, which is not set on this host)`.
The note stays for a login refusal, so that pin still names the variable
to set. I did not take the card's other route, matching the driver's
error for "reads as an authentication refusal": that needs a per-driver
table of auth codes (pg `28P01`, mysql `1045`, mssql `ELOGIN`…) that
would drift silently, and the note is true for every cause.
- **S2, the owner.** The collision check and the claim stay one step,
before the build, with no await between them. `claimedVariable` records
that THIS attempt made the claim, and the `failed-degraded` catch
releases it before `handleFailure`, which may throw the fail-fast error.
A connect of a name that already owned the variable claims nothing new
and releases nothing. I kept the claim before the build, rather than
recording it after the build succeeds, so that two overlapping connects
of colliding names cannot both pass the check across the build's awaits.
- A `patch` changeset for `@objectstack/service-datasource`. The docs
line in `external-datasources.mdx` ("its reason names the variable to
set") stays true and is not edited.
## Tests
`src/__tests__/datasource-env-credential.test.ts`, through the real
`DatasourceConnectionService`:
- **S1 pin (new):** a code datasource, slot applied, variable unset,
whose connect throws `connect ECONNREFUSED 127.0.0.1:1` (`code:
'ECONNREFUSED'`). The reason starts with the driver's error, the rest is
exactly ` (note: … OS_DATASOURCE_WAREHOUSE_PASSWORD …)`, and the
retained connection state carries the same reason.
- **S1 control (unchanged):** "the database refusing the login answers
the variable to set" passes.
- **S2 pin (new):** `eu-sales` fails `failed-degraded` (`getaddrinfo
ENOTFOUND`), then `eu_sales`, which derives the same
`OS_DATASOURCE_EU_SALES_PASSWORD`, connects.
- **S2 control (unchanged):** "the second name is refused with both
names and the variable" (both live) passes.
<details><summary>Red first, green, and one ablation per pin</summary>
- Base, the fix not yet applied: `Tests 2 failed | 24 passed (26)`. S1:
`expected ' — datasource \'warehouse\' is define…' to match /^ \(note:
…/`. S2: `expected 'failed-credentials' to be 'connected'`.
- Head: `Tests 26 passed (26)`.
- Ablation 1, through `scripts/ablation-replace.mjs`, restores `} —
datasource '${name}' is defined`. On disk: note-count 0, dash-count 1.
Result: only the S1 pin fails (`1 failed | 25 passed`). Restored: blob
== HEAD `2753417323b5`, `git diff HEAD` empty.
- Ablation 2 deletes `if (claimedVariable !== undefined)
this.envCredentialOwners.delete(claimedVariable);`. On disk:
release-count 0. Result: only the S2 pin fails (`1 failed | 25 passed`).
Restored: blob == HEAD, `git diff HEAD` empty.
- The suite imports `../datasource-connection-service.js` from `src/`,
so no `dist/` rebuild sits in either leg.
</details>
<details><summary>Package suite, typecheck, lint</summary>
- `pnpm --filter @objectstack/service-datasource test`: `Test Files 66
passed (66)`, `Tests 1100 passed (1100)`, lock VERDICT command-exit 0.
- `pnpm --filter @objectstack/service-datasource typecheck`: `tsc
--noEmit`, VERDICT command-exit 0. `--listFiles` includes the test file.
- `eslint src --no-inline-config` in the package: exit 0.
</details>
## Acceptance notes
- Security: no credential value appears in a test, log or this body. The
pins use variable NAMES and a fixed fake value, and the existing no-leak
pin passes.
- The reason is operator-facing free text: the retained connect verdict
(Setup → Datasources `statusReason`, the saved-datasource Test answer)
and the fail-fast boot message. It is pinned only by this package's
tests, and only by `toContain` on the variable name, which still holds.
- `packages/runtime`'s `datasource-env-credential.test.ts` reads this
package from `dist/` and asserts no reason text. No export changes, so
per the local-verification scope it is left to CI's Test Core.
## Verification
Gates from `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` on this diff (60 commands), run on head
`d3ceca42` with each exit code captured before any pipe: 59 exit 0.
`pnpm check:dual-build-cjs-loads` exits 3, PREREQUISITE NOT MET (a fresh
worktree has not built every package's `dist`), so it is NOT MEASURED
locally and left to CI. `check-plugin-teardown-shape.mjs --self-test`
first exited 3 on the shallow clone, and exits 0 after fetching its
fixture commit. `dispatch-gates --ran`: "60 derived famil(ies) accounted
for — 59 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)".
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(`packages/spec` untouched.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes (scoped to the touched
package).
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: n/a.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English (the 维护者速读 block is the
seat's required Chinese summary).
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FEkQ5m1ywz2SChxvNGPb5U)_
Co-authored-by: Claude <noreply@anthropic.com>
1 parent efdddc9 commit a0f8654
3 files changed
Lines changed: 75 additions & 8 deletions
File tree
- .changeset
- packages/services/service-datasource/src
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
16 | | - | |
17 | | - | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
18 | 20 | | |
19 | 21 | | |
20 | 22 | | |
| |||
54 | 56 | | |
55 | 57 | | |
56 | 58 | | |
57 | | - | |
| 59 | + | |
| 60 | + | |
58 | 61 | | |
59 | | - | |
| 62 | + | |
60 | 63 | | |
61 | 64 | | |
62 | 65 | | |
| |||
66 | 69 | | |
67 | 70 | | |
68 | 71 | | |
| 72 | + | |
69 | 73 | | |
70 | 74 | | |
71 | 75 | | |
| |||
88 | 92 | | |
89 | 93 | | |
90 | 94 | | |
| 95 | + | |
91 | 96 | | |
92 | 97 | | |
93 | 98 | | |
94 | 99 | | |
95 | 100 | | |
96 | | - | |
| 101 | + | |
97 | 102 | | |
98 | 103 | | |
99 | 104 | | |
| |||
239 | 244 | | |
240 | 245 | | |
241 | 246 | | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
242 | 263 | | |
243 | 264 | | |
244 | 265 | | |
| |||
264 | 285 | | |
265 | 286 | | |
266 | 287 | | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
267 | 305 | | |
268 | 306 | | |
269 | 307 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
761 | 761 | | |
762 | 762 | | |
763 | 763 | | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
764 | 770 | | |
| 771 | + | |
765 | 772 | | |
766 | 773 | | |
767 | 774 | | |
| |||
777 | 784 | | |
778 | 785 | | |
779 | 786 | | |
780 | | - | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
781 | 791 | | |
782 | 792 | | |
783 | 793 | | |
| |||
898 | 908 | | |
899 | 909 | | |
900 | 910 | | |
| 911 | + | |
901 | 912 | | |
902 | 913 | | |
903 | 914 | | |
904 | 915 | | |
| 916 | + | |
| 917 | + | |
| 918 | + | |
| 919 | + | |
| 920 | + | |
905 | 921 | | |
906 | 922 | | |
907 | | - | |
908 | | - | |
| 923 | + | |
| 924 | + | |
909 | 925 | | |
910 | 926 | | |
911 | 927 | | |
| |||
0 commit comments