Repository navigation
Commit 22663ca
docs(skills,adr): teach the code-defined datasource credential route (OS_DATASOURCE_NAME_PASSWORD behind the host switch) (#28432)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1X3FUiFyiLjMuyYfjHpVM9Ht)_
Fixes #28209
Clause-②: no — governed prose (two ADR amendment notes) and one
published skills rules file. No schema, export, type, key, value, error
code or runtime behaviour moves; nothing is newly accepted or refused.
Metadata-protocol: none
## 维护者速读
- **改了什么**:三处文字补上 PR #28208 已落地的「代码声明的数据源在连接时读
`OS_DATASOURCE_NAME_PASSWORD`」这条路:`skills/objectstack-data/rules/datasources.md`
的凭据一节(变量名规则、主机开关 `OS_DATASOURCE_ENV_CREDENTIALS` 及其默认、已声明的
`credentialsRef`/`connector` 仍优先且不回落、给运维的信任提醒);ADR-0015 的 Amended-by
行、D7 注里「对数据库仍然成立」那句的行内更正、§10 凭据条目和一则带日期的 §10 修订注;ADR-0062 D3
下一则带日期的修订注(该槽在连接解析顺序里的位置、失败文字按 PR #28325 的样子)。另加一条 `@objectstack/skills`
的 patch changeset。
- **为什么改**:卡 #28209(分诊 6528463851 / 6538723756):#28208
落地后这三处文字说少了一条路,写数据源的 agent 会按旧文字去写一个代码数据源铸不出来的 `sys_secret:` 句柄。
- **风险与回滚**:无运行时改动。skills 文件在 token 棘轮内(909 → 910,上限
911),新增文字靠删同文件里的重复句付账,没有折行凑行;删掉的每一句在包内别处仍有落点(正文列出)。回滚即 revert。
-
**先看**:`skills/objectstack-data/rules/datasources.md`(凭据一节与被删的五处);`docs/adr/0015-external-datasource-federation.md`(§10
下的修订注,含信任提醒);`docs/adr/0062-external-datasource-runtime.md`(D3 修订注)。
- **两分钟验证**:`node scripts/check-skills-token-ratchet.mjs` →
`skills/objectstack-data/rules/datasources.md is 910 tokens (ceiling
911; headroom 1)`;`pnpm check:pm-skill-id-lint` 绿(skills 文字无 `#NNNN`)。
- **需要你做的**:Tier H(`skills/**` + `docs/adr/**`):PR 保持
draft,等你本人批准后由席位落地;另请裁定「skills 改动带 patch changeset 而非
`skip-changeset`」这一处与派发令的出入(见 Acceptance notes)。
## What changes
### Measured on `origin/main` `5da3a8ce` before any edit
What PR #28208 shipped, with PR #28325's two corrections, cited by file
and line on that tree:
| Fact | Where |
| :--- | :--- |
| Variable name rule: `OS_DATASOURCE_` + the datasource name upper-cased
with every character outside `[A-Z0-9]` replaced by `_` + `_PASSWORD` |
`packages/services/service-datasource/src/datasource-env-credential.ts:55-57`
(`datasourceEnvCredentialVar`) |
| Which names never map: `default` (an `asDefault` connect never reads
the slot) |
`packages/services/service-datasource/src/datasource-connection-service.ts:772`
|
| Which names never map: a name outside the host's code-datasource set
(a Setup- or API-created datasource), or a connect whose trigger is not
`declared-auto` | `datasource-connection-service.ts:936-941`
(`envCredentialVarFor`) |
| No two schema-valid names derive one variable: the name regex is
lower-case `^[a-z_][a-z0-9_]*$`, on which upper-casing is injective |
`packages/spec/src/data/datasource.zod.ts:652` |
| The host switch name, `OS_DATASOURCE_ENV_CREDENTIALS` |
`datasource-env-credential.ts:44` |
| Its reading: unset or empty is off; `on` / `off` in any letter case;
any other value throws | `datasource-env-credential.ts:65-78`
(`resolveDatasourceEnvCredentialsSwitch`) |
| The throw refuses the boot: the admin plugin reads the switch inside
`init()` and wires the slot only when it is on |
`packages/services/service-datasource/src/datasource-admin-plugin.ts:338`
(`init`), `:421-429` (the `envCredentials` spread) |
| `os serve` stamps `on` when the operator left it unset or empty, just
before it builds the admin plugin |
`packages/cli/src/commands/serve.ts:5287-5289`;
`packages/cli/src/utils/datasource-env-credentials-default.ts:20-28` |
| `os start` and `os dev` run `serve`, so they get the stamp |
`packages/cli/src/commands/start.ts:528`,
`packages/cli/src/commands/dev.ts:848` |
| Library compositions: nothing else calls the stamp, and the service's
`envCredentials` is optional and absent by default |
`datasource-connection-service.ts:171`;
`datasource-env-credential.ts:80-89` |
| Resolution order inside `connect()`: `credentialsRef` (fail-closed) →
the D7 connector borrow (fail-closed) → the env slot, only when neither
is declared and not `asDefault` → the driver factory |
`datasource-connection-service.ts:713-738`, `:749-757`, `:770-795`,
`:805` (`factory.create`) |
| The value becomes the build's `secret`; an empty or unset variable
leaves `envCredentialVar` set so the failure can name it |
`datasource-connection-service.ts:791-793` |
| Collision refusal: a second live name deriving the same variable is
refused `failed-credentials`, naming both datasources and the variable,
before any read | `datasource-connection-service.ts:775-786` |
| A failed connect releases its claim on the variable (PR #28325) |
`datasource-connection-service.ts:911` |
| Failure text as PR #28325 left it: the driver's error, then ` (note:
datasource 'NAME' is defined in code and reads its credential from
OS_DATASOURCE_NAME_PASSWORD at connect, which is not set on this host)`;
verdict `failed-degraded` | `datasource-connection-service.ts:921-924` |
| The code-datasource set's three producers: the boot artifact,
`default`, and a package installed into the running kernel (the trust
consequence) | `packages/runtime/src/app-plugin.ts:626`,
`packages/runtime/src/default-datasource-plugin.ts:150`,
`packages/runtime/src/declared-datasources.ts:188` |
| The spec's shared sentence naming the route, appended to five
credential refusals, the refused-key description and the two `password`
guidance entries | `packages/spec/src/data/driver/common.zod.ts:103-108`
(`CODE_DEFINED_CREDENTIAL_ROUTE`); 7 uses there, 2 in
`packages/spec/src/data/datasource.zod.ts` |
### The three texts
**`skills/objectstack-data/rules/datasources.md`** (reader: the
customer's agent writing a `defineDatasource` or a package's
`*.datasource.ts`; mistake prevented: inlining a password, or writing a
`credentialsRef` handle nothing mints for a code datasource, or
expecting a library-built kernel to read the variable).
- The section is now "Credentials — fail-closed". Its opening paragraph
says: a code-declared datasource reads its password at connect from
`OS_DATASOURCE_NAME_PASSWORD` (NAME = the name upper-cased; exact for
every schema-valid name, since the only non-alphanumeric character a
valid name can hold is `_`); only while the host switch
`OS_DATASOURCE_ENV_CREDENTIALS` is `on`; `os serve` / `os start` / `os
dev` default it on, a library composition is off, and the switch takes
`on` / `off` only; the operator's caution that an installed package may
declare a datasource of any free name and so read that variable; and
`external` may instead name one channel, which wins and never falls back
to the variable. The `credentialsRef` and `connector` bullets and the
fail-closed paragraph are kept.
- Paid under the ratchet by deleting restatements in the same file, each
with a home elsewhere in the bundle:
- the "Full field reference" pointer to `datasource.zod.ts` —
`skills/objectstack-data/references/_index.md:12` carries the same path;
- the `// writable: true` comment in the example — the Writes section
two screens down states the gate;
- the "✅ / ❌ Column mapping" heading and its ✅ bullet — the example's
`// columnMap: { cust_region: 'region' }, // remoteColumn → localField`
line shows the same key with its direction; the ❌ `field.columnName`
rule stays;
- "(default: `'default'`)" on the object field —
`skills/objectstack-data/SKILL.md:87`'s field table carries that
default;
- "and its federated objects registered," in the auto-connect sentence,
"(at `defineStack`, else at connect)" on the connector refusal, and
"Otherwise every write to the object is rejected." after "read-only by
default … both gates must be on" — consequence and timing clauses whose
rule survives in the sentence they hung on.
- No re-wrap bought a line: every line that moved either lost a clause
or is new text.
- Two readings, lines and tokens (the ratchet's unit): the file 84 → 83
lines, 909 → 910 tokens against a ceiling of 911 (headroom 2 → 1); the
whole shipped bundle 219807 → 219808 tokens; the sum of every `SKILL.md`
56506 → 56506 (none edited). No ceiling moves.
- Not taught, on purpose: the collision refusal (no author can write two
valid names that collide), the unset-variable failure text (the reason
names the variable at runtime), and the per-request plugin-driver limit
(no reader among the agents this file serves). All three are recorded in
the ADR-0015 note.
**`docs/adr/0015-external-datasource-federation.md`**
- The `Amended by` header line gains the #28208 entry with the ruling
record.
- The D7 note's "§10 says credentials reach a datasource only through
`credentialsRef`. That stays true for a database." gains an inline,
dated correction in the ADR's own `*(amended …)*` form, pointing at the
§10 note.
- The §10 Credentials bullet gains a second inline `*(Amended …)*`, in
the form the D7 amendment already used there.
- A dated amendment note under §10, in the blockquote form every other
note in this ADR uses: the route, the name derivation, where the value
goes, then the guards as ruled (the host switch and its default per
host; code origin by provenance, never `origin`; a declared channel wins
and stays fail-closed; an unset variable leaves the connect as it was
and the failure text as PR #28325 left it; the collision refusal and the
released claim; the per-request plugin-driver limit), then the trust
consequence stated for operators, the spec sentence, and what was not
taken.
**`docs/adr/0062-external-datasource-runtime.md`**
- A dated amendment blockquote under D3, the runtime decision: the slot
is a second resolution source at the same point, after `credentialsRef`
and the D7 borrow and before the factory, with the five conditions under
which it is taken, the switch and its wiring at `init()`, the failure
text, the collision claim, and a pointer to ADR-0015 §10 for the
operator's caution. The normative bullets are not edited, as the ADR's
other amendments did not edit theirs.
### The changeset
`.changeset/28209-skills-code-datasource-env-credential.md`:
`@objectstack/skills` `patch`, carrying both declaration lines.
`skills/**` is copied into that package's `dist/skills` by its build and
ships in its `files[]`, so this diff publishes; see Acceptance notes for
the conflict with the order.
## Tests
No code changes; the gates below are the tests. The published surface
was built: `pnpm --filter @objectstack/skills build` under the verify
lock → `sync-catalog self-test: 9 cases pass`, `95 file(s) across 11
skill(s) copied from skills/** into dist/skills and read back
byte-identical`, `VERDICT command-exit 0`.
## Acceptance notes
- **The order ruled `skip-changeset`; the repo's rule says otherwise,
and this PR follows the repo.** AGENTS.md's Post-Task Checklist:
`skip-changeset` "is for a diff that publishes nothing from any released
package". `scripts/check-empty-changeset.mjs` (its failure text and its
GREEN 3 battery) states that a `skills/**` change ships in
`@objectstack/skills` and "owes a changeset naming that package, never
the `skip-changeset` label", and the precedent changesets for
skills-only PRs (for example
`.changeset/26475-skills-teach-queues-assignment-rules.md`) are
`@objectstack/skills: patch`. `.claude/agents/os-dev.md` says that where
an order and that file conflict, the file wins and the conflict is
named, so: a patch changeset, no `skip-changeset`, and zero label writes
at `pr_create` (the order named no other label). The seat relabels if
the maintainer rules otherwise.
- **Security.** No credential value, connection string or example
password anywhere in the diff or this body; variable names only. The
trust note is stated as an operator's caution in each text, with no
recipe.
- **Out of scope, reported, not edited:**
`skills/objectstack-data/evals/hooks-security-seeds-datasources.json`,
case id 3, still answers "A `defineDatasource` entry (`credentialsRef`,
never inline credentials)" for a code datasource exposing a Postgres
table — the same stale teaching this card corrects, in a fixture the
card and the claim's file surface do not name. Its assertions do not
require `credentialsRef`, so it stays green. Reported in the
os-dev-report for the seat to file or fold in.
- `content/docs/data-modeling/external-datasources.mdx` §4 (PR #28208)
already teaches the full route; the skills text is deliberately the
short form and names no `#NNNN`.
- `pnpm lint` scoped to touched packages: the diff touches no package
source; `@objectstack/skills` has no `lint` script and eslint reads no
`.md`, so there is nothing to scope it to. Recorded, not run.
## Verification
All on head `17988ade` (base `origin/main` `5da3a8ce`; no merge, `main`
did not move under the branch).
- `node scripts/pm/check-governed-merges.mjs --test
skills/objectstack-data/rules/datasources.md
docs/adr/0015-external-datasource-federation.md
docs/adr/0062-external-datasource-runtime.md` → exit 3,
`governed-surface predicate: 3 of 3 path(s) hit the register (13
surfaces govern objectstack). ⛔ GOVERNED — a human merge is the review
record for this PR … landing tier: H(人合)`: `docs/adr/** ×2`, `skills/**
×1`. The PR stays draft; no seat readies, queues or arms it.
- `node scripts/check-skills-token-ratchet.mjs` → exit 0:
`skills/objectstack-data/rules/datasources.md is 910 tokens (ceiling
911; headroom 1)`; `56 authored bundle file(s) within their ceilings`;
bundle total 219807 → 219808.
- `pnpm check:pm-skill-id-lint` → exit 0: `105 file(s) clean (pattern
/#[0-9]{3,}/g)`. `pnpm check:doc-authoring` → exit 0: `70 published
skill files clean — no internal issue-id references`.
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` on this diff: 49 families. Every one was run
with its exit code captured before any pipe (`timeout 900 bash -c CMD;
EXIT=$?`), and `--ran` reconciles to `49 derived famil(ies) accounted
for — 47 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)`. Three
first exited 3 (PREREQUISITE NOT MET, nothing measured). `pnpm --filter
@objectstack/lint run check:doc-formula-expressions` wanted
`@objectstack/formula` and then `@objectstack/lint` built: after `pnpm
--filter '@objectstack/formula...' build` and `pnpm --filter
'@objectstack/lint...' build` under the verify lock (VERDICT
command-exit 0, 88s and 81s) it exits 0 — `9 @example(s) judged clean
across 2321 packages/spec/src files`, `15 predicate(s) … judged clean`.
The two left to CI's full build: `pnpm check:dual-build-cjs-loads`
(needs `pnpm build`) and `pnpm check:lean-entry-closure` (needs the
built entry points, `packages/objectql/dist/core.mjs` among them);
neither reads a file in this diff. Among the 46 green:
`check:adr-anchors`, `check:adr-index`, `check:adr-symbol-anchors`,
`check-adr-links` (962 relative links resolve),
`check-adr-0087-registration`, `check-changeset-no-major`,
`check-empty-changeset` (1 declaring changeset added, none from the
merge base modified), `check:pm-governed-merges`,
`check:pm-changeset-deadline-census`, `check:pm-prior-rulings`,
`check:published-files`, `check:skill-compatibility`,
`check:skill-frame-sync`, `check:skill-identifier-liveness`,
`check:dts-closure`, `check:nul-bytes` (23088 text files, no raw control
bytes). The three diff-reading gates that ran before the commit
(`check-adr-0087-registration --base`, `check-adr-links`,
`check-changeset-no-major --base`) were re-run on the committed head:
all exit 0.
- `pnpm --filter @objectstack/skills build` under
`scripts/pm/os-verify-lock.sh` → `sync-catalog self-test: 9 cases pass`;
`95 file(s) across 11 skill(s) copied from skills/** into dist/skills
and read back byte-identical`; `VERDICT command-exit 0 · held the lock
2s`.
- `pnpm lint` scoped: nothing to scope to (no package source in the
diff; `@objectstack/skills` has no `lint` script).
- Ablation: none — no tested behaviour changes (docs and skills only).
- Dogfood battery: n/a — no `!` in the title and no `(narrowing)` arm.
## 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. (No new names;
the variable names are the shipped ones.)
- [x] 3. No formatter ran; `pnpm lint` passes. (No package source
touched; see Acceptance notes.)
- [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
required Chinese summary).
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FEkQ5phX4SAaFQKww3TfKb)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 7e3822c commit 22663ca
4 files changed
Lines changed: 96 additions & 17 deletions
File tree
- .changeset
- docs/adr
- skills/objectstack-data/rules
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
12 | 14 | | |
13 | 15 | | |
14 | 16 | | |
| |||
766 | 768 | | |
767 | 769 | | |
768 | 770 | | |
769 | | - | |
| 771 | + | |
| 772 | + | |
| 773 | + | |
770 | 774 | | |
771 | 775 | | |
772 | 776 | | |
| |||
787 | 791 | | |
788 | 792 | | |
789 | 793 | | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
| 823 | + | |
| 824 | + | |
| 825 | + | |
| 826 | + | |
| 827 | + | |
| 828 | + | |
| 829 | + | |
| 830 | + | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
| 837 | + | |
| 838 | + | |
| 839 | + | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
| 847 | + | |
| 848 | + | |
| 849 | + | |
| 850 | + | |
| 851 | + | |
| 852 | + | |
| 853 | + | |
790 | 854 | | |
791 | 855 | | |
792 | 856 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
94 | 98 | | |
95 | 99 | | |
96 | 100 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
5 | | - | |
| 4 | + | |
| 5 | + | |
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
11 | | - | |
12 | 10 | | |
13 | 11 | | |
14 | 12 | | |
| |||
29 | 27 | | |
30 | 28 | | |
31 | 29 | | |
32 | | - | |
33 | 30 | | |
34 | 31 | | |
35 | 32 | | |
36 | 33 | | |
37 | 34 | | |
38 | | - | |
39 | | - | |
40 | | - | |
41 | 35 | | |
42 | 36 | | |
43 | 37 | | |
44 | 38 | | |
45 | 39 | | |
46 | | - | |
47 | | - | |
| 40 | + | |
| 41 | + | |
48 | 42 | | |
49 | 43 | | |
50 | 44 | | |
| |||
60 | 54 | | |
61 | 55 | | |
62 | 56 | | |
63 | | - | |
| 57 | + | |
64 | 58 | | |
65 | | - | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
66 | 66 | | |
67 | 67 | | |
68 | 68 | | |
| |||
71 | 71 | | |
72 | 72 | | |
73 | 73 | | |
74 | | - | |
| 74 | + | |
75 | 75 | | |
76 | 76 | | |
77 | 77 | | |
| |||
81 | 81 | | |
82 | 82 | | |
83 | 83 | | |
84 | | - | |
0 commit comments