Repository navigation
Commit 15e8311
fix(service-automation)!: a connector stored in the environment never reads the host's files (#28671)
domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XKQ4XajqQhqyHfXo3mU14Lv)_
Fixes #28594
Clause-②: no (narrowing)
Metadata-protocol: narrowed (a stored connector's `providerConfig` may
no longer name a package-relative file, and a stored stdio `mcp`
transport is refused)
Skills-docs: none — the published automation skill teaches a file ref
only on a connector the package itself declares (`defineConnector` in
the stack), which keeps reading its files; the stored-entry refusal is
documented in `content/docs/automation/connectors.mdx` (failure-modes
table)
## 维护者速读
-
**改了什么**:在环境里保存的连接器(Studio、元数据接口保存的,不论是否属于在本环境创建的包)不再能按相对路径读取主机自身的文件;只有主机代码栈自己声明的连接器才能读主机根目录。元数据列表读取时,存储行的所属包一律以数据库列为准,不再采信正文里自带的包标记。
- **为什么改**:#28534 已经堵住了已安装包这条路,这张卡补齐其余来源。托管环境的管理员在 Studio
里配置的连接器,不应凭写一个路径就读到平台自己的文件。
- **风险与回滚**:中低。受影响的只是“存储在环境里、且用相对路径引用文件或 stdio
传输”的连接器,它们现在会以配置错误不加载并在日志里说明原因;内联文档、http(s) 地址和主机自身连接器不受影响。回滚即还原本 PR。
-
**先看**:`packages/services/service-automation/src/plugin.ts`(`nonHostDeclaringPackageOf`
与新的读取器);`packages/metadata-protocol/src/protocol-stored-overlay-reads.ts`(列以列为准);`packages/qa/dogfood/test/stored-connector-package-ref.dogfood.test.ts`。
- **两分钟验证**:`pnpm --filter @objectstack/dogfood exec vitest run
test/stored-connector-package-ref.dogfood.test.ts
test/stored-connector-package-ref-restart.dogfood.test.ts`,预期两个文件 13/13
通过。
- **需要你做的**:无,席位落地。
## What changes
- **`@objectstack/service-automation` — `plugin.ts`.** #28534's one
classifier, `nonHostDeclaringPackageOf`, is extended; there is no second
rule and no second message. An entry reads the host's root only when the
host's own stack declares it:
- its stamp names the host's package (a `code` row, or a residual owner
a `code` door recorded), **and** a package registration filed that very
binding under that package (`SchemaRegistry.getArtifactItem('connector',
name, packageId)`, the ADR-0010 §3.3 artifact lookup, with the same
`provider` and `providerConfig`); or
- the host's own artifact carried it (the dev reload payload, the one
source of an edit the registry was never re-told).
Every other entry is a new `why: 'stored'`: a row with no stamp, a row
of an `authored` package, and a row claiming the host's package with a
binding no registration filed. Its `loadPackageFile` /
`resolvePackagePath` are #28534's refusals, now worded for a stored
entry (entry, its package if it has one, the ref, the remedy: inline the
document or name it by an http(s) URL; reach MCP over http). The message
interpolates only the entry name, the package id and the author's own
ref. A registry double that offers no registered layer keeps today's
reading (the doubles in the connector packages' suites), as the existing
"no registry" answer does; the real `SchemaRegistry` always offers it.
- **`@objectstack/metadata-protocol` —
`protocol-stored-overlay-reads.ts`.** The body-`_packageId` road
(`storedOverlayEntries`, ≈:165) is closed: the row's package is its
column's. A body stamp is replaced by the column's package, or dropped
when the row is bound to no package, on a copy (a cached row is never
written). See Acceptance notes for why the column wins rather than the
row being refused.
- **Boot.** A stored entry refused its ref never fails a boot: one
already saved when the platform starts is logged and skipped like one
saved while it runs, and the host's own entries beside it serve. Every
other configuration fault keeps failing the boot.
- Docs: one row in the connectors failure-modes table. Changesets:
`minor` for `@objectstack/service-automation` (the narrowing), `patch`
for `@objectstack/metadata-protocol`, both `!` with the ADR-0087
`no-migration-prescription` disposition.
## Tests
<details><summary>Red on origin/main f23e051, green at head (the real
metadata door)</summary>
`stored-connector-package-ref.dogfood.test.ts` boots the showcase, saves
connectors through the metadata door, and reads the connector listing.
Built from base (the fix reverted), its refusal cases are red: each
stored entry listed `ready`, and the list served a body's package stamp.
With the fix built: `Tests 7 passed (7)`, `os-verify-lock: VERDICT
command-exit 0`. The measurement itself is in the test file and in the
hand-back to the seat.
Controls in the same boot, green on base and at head: a stored inline
document and a stored http(s) URL list `ready` with their operation; the
showcase's own file-ref and stdio entries list `ready` with their own
actions.
</details>
<details><summary>Unit: connector-materialization.test.ts (8 new cases)
and protocol-stored-overlay-reads.test.ts (4 new cases)</summary>
- Refused, naming entry, ref and remedy, nothing read: an
environment-wide row; an `authored` package's row; a row claiming the
host's package that no registration filed; a row under the host's
registered name with another binding (reload); `resolvePackagePath('.')`
on a stored entry.
- Controls: a stored inline entry materializes with neither member
reached; the host's registered entries (a `code` package and a `code`
residual) read and resolve under the root, judged by binding, not
identity; entries the host's own artifact reload carries read their
root, stamped or not.
- The column wins: a body stamp on a package-less row is dropped; one
naming another package is replaced; an absent or agreeing stamp is
served as before; an object-shaped stored body is not written.
</details>
<details><summary>Ablation (each restored, `blob after restore == blob
at HEAD`)</summary>
Via `node scripts/ablation-replace.mjs`; both suites import `src/`
directly, so no rebuild is in the path.
- Drop the origin check (`judgesStored = false`): `5 failed | 3 passed`
of the #28594 cases, exactly the five refusals; the three controls stay
green.
- Restore the body-stamp pass-through in `storedOverlayEntries`: `3
failed | 1 passed`, the three column-wins cases.
- Drop the host-artifact reading (`carried = false`): `1 failed | 7
passed`, the reload-payload control.
- Drop the boot soft path: `6 failed | 5 passed`; force the early
authored row to `unrecorded`: `1 failed | 10 passed`.
</details>
<details><summary>Restart</summary>
`stored-connector-package-ref-restart.dogfood.test.ts`: two boots over
one database; the second starts, the refused stored rows are unlisted
with their records, and the inline control and the host's entries list
ready. Red at `43953ed3` (the second boot did not start), green at head.
</details>
<details><summary>Package suites, typecheck, dogfood battery</summary>
- `@objectstack/service-automation`: `pnpm typecheck` (incl.
`check:test-typecheck`) exit 0; `vitest run` `Test Files 251 passed ·
Tests 3196 passed` at `5c706cd3`.
- `@objectstack/metadata-protocol`: `pnpm typecheck` exit 0; `vitest
run` `Test Files 292 passed | 4 skipped · Tests 21948 passed | 26
skipped`.
- Full dogfood battery (the `(narrowing)` arm), `pnpm --filter
@objectstack/dogfood test` under the lock on the
`@objectstack/dogfood^...` closure at `5c706cd3`: `Test Files 371 passed
| 3 skipped (374) · Tests 2994 passed | 28 skipped (3022)`, `VERDICT
command-exit 0`. It includes the install-local package-ref file (PR
#28587's behaviour) and the install-local restart file (PR #28639's
behaviour).
</details>
## Acceptance notes
- **Column wins, not a refusal (the order's M1 parity).** M1 refuses at
a registration door, where an author is present and the item has not
been filed. `storedOverlayEntries` is a list read: refusing one row
there either fails every list of the type for every caller, or hides the
row from the Studio that could fix it. The save door's sibling rule,
`stripDerivedProvenance`, already states that the server states
provenance rather than reading it back, so the column winning is that
rule applied at the read. The materializer does not depend on it: a
stored row claiming the host's package is refused its refs either way,
because no registration filed its binding.
- **A save over the host's own connector name** is refused by the save
door (sealed), so that road is closed there; the unit case still pins
the materializer's answer for a row under a registered name with another
binding.
- `check:dual-build-cjs-loads` answered exit 3 (prerequisite: a full
build) and is NOT MEASURED locally; CI is the record.
`check:skill-examples` answered exit 0 (279 prose examples) after
`client-react` was built.
## Verification
Derived gate list (`node scripts/pm/dispatch-gates.mjs --commands`),
local subset run at the final head with the exit-coded record reconciled
by `--ran`; every family CI runs is read from the PR's check-runs.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01FEkQ5mBUwWJMnLUdKBYraT
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FEkQ5mBUwWJMnLUdKBYraT)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent e170d42 commit 15e8311
9 files changed
Lines changed: 950 additions & 30 deletions
File tree
- .changeset
- content/docs/automation
- packages
- metadata-protocol/src
- qa/dogfood/test
- services/service-automation/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
875 | 875 | | |
876 | 876 | | |
877 | 877 | | |
| 878 | + | |
878 | 879 | | |
879 | 880 | | |
880 | 881 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
155 | 155 | | |
156 | 156 | | |
157 | 157 | | |
158 | | - | |
| 158 | + | |
159 | 159 | | |
160 | 160 | | |
161 | 161 | | |
162 | 162 | | |
163 | 163 | | |
164 | 164 | | |
165 | | - | |
166 | | - | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
167 | 180 | | |
168 | 181 | | |
169 | 182 | | |
| |||
0 commit comments