Repository navigation
Commit ea053b4
fix(cli): os build leaves devPlugins out of the artifact; os validate --target stops reading it as code (#31276)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XR4fpPzKKft83b8NgK8NESr)_
Part of #31264 — this PR does items 1 and 3 and the cli half of item 2.
The `RestrictionProfileSchema` docblock sentence (spec, `allowCode`
rule) is left to the spec seat after this lands; the exact sentence is
under "Spec docblock owed" below.
Clause-②: yes (widening) — two accept-sets widen: `os validate --target`
with `allowCode: false` now accepts a stack that declares `devPlugins`,
and an unmanaged install (`os package install --install-mode unmanaged`,
and the cloud-connection local installs) of a freshly built artifact of
such a stack is no longer refused, because that artifact no longer
carries the key. Leaving the key out of `objectstack.json` refuses
nothing (measured below), so there is no narrowing arm.
Metadata-protocol: none
## 维护者速读
- 改了什么:`os build` / `os compile` 写产物时去掉 `devPlugins`,和已经去掉的 `demoStaff`
同一处;`os validate --target` 在 `allowCode: false` 下不再把 `devPlugins` 当代码拒绝。
- 为什么改:`devPlugins` 只在本地 `--dev` 启动时从配置加载,产物里没有任何启动或安装会读它,但 `os package
publish` / `os deploy` 会把产物原样送到目标环境,于是限制配置只能把它当代码拒掉。
- 风险与回滚:只是产物少一个开发用的键;`os dev` / `os serve --dev`
仍从配置加载它;旧产物带这个键照样能装。仓内没有读产物 `devPlugins` 的代码;cloud 仓本会话够不到,未能核验。回滚即还原本
PR。
- 先看:`packages/cli/src/commands/compile.ts`(`delete
finalBundle.devPlugins`),`packages/cli/src/utils/restriction-profile.ts`(`NEVER_REACHES_TARGET`)。
- 两分钟验证:`pnpm --filter @objectstack/cli exec vitest run
src/utils/restriction-profile.test.ts
test/build-demo-staff-artifact.test.ts`,预期全绿。
- 需要你做的:无,席位落地。
## What changes
- `packages/cli/src/commands/compile.ts`: the artifact write deletes
`devPlugins` right after `demoStaff`, at the one place
`objectstack.json` is written. `os build` is an alias of this command;
`os package publish` (`dist/objectstack.json` or its argument) and `os
deploy` (`DEPLOY_ARTIFACT`, after `compileForDeploy`) both read that
file rather than the config, so dropping it at the write covers them.
- `packages/cli/src/utils/restriction-profile.ts`: `CODE_KEYS` (the keys
`UNMANAGED_INSTALL_KEY_DISPOSITIONS` classifies `code`) now excludes a
named set, `NEVER_REACHES_TARGET = {'devPlugins'}`, with the reason in
its docblock and one line in the module header. The now-unreachable
`devPlugins` branch of `codeItems` is removed. ⛔
`UNMANAGED_INSTALL_KEY_DISPOSITIONS` is unchanged: an unmanaged install
still refuses `devPlugins` as code in a bundle that still carries the
key (an artifact built before this change). A fresh build no longer
carries it, so an unmanaged install of that artifact is no longer
refused for it (the second widening on the Clause-② line).
- `content/docs/getting-started/quick-start.mdx`: the one docs sentence
that lists what `os build` leaves out of `objectstack.json` (it named
`demoStaff`) now names `devPlugins` too. The `os compile` section of
`deployment/cli.mdx` lists no dropped keys, so it is unchanged.
- `.changeset/31264-build-drops-dev-plugins.md`: `@objectstack/cli`
patch (both files ship in the cli `dist/`).
## Tests
- `test/build-demo-staff-artifact.test.ts` (the existing artifact-drop
pin for `demoStaff`): the fixture gains `devPlugins:
['@acme/legal-dev-only-plugin']`, the build runs once for both cases,
and a new case asserts the key is absent from `objectstack.json` by key
and by text, and absent from what `loadArtifactBundle` (the runtime's
own loader) returns. Control: the stack's objects survive the same
build; the `demoStaff` case still passes on the same build.
- `src/utils/restriction-profile.test.ts`: under `allowCode: false`, a
stack with `plugins` and `devPlugins` gets exactly one row, the
`plugins` one.
- `test/validate-target-restriction-profile.test.ts` (the command's
door, in-process): the fixture stack gains a `devPlugins` entry, and the
`allowCode: false` case keeps its exact four rows, so the door pins that
the dev-only plugin is not refused; the no-target and all-open controls
stay green with the key present.
- Control for "`os dev --dev` still loads them": `stackBootPlugins`
reads the config `serve` loads (`serve.ts`, `stackBootPlugins(config,
flags.dev)`), untouched here; pinned by
`src/utils/stack-collections.test.ts` and
`src/utils/schema-migration-plugins.test.ts` (`--dev composes the
config's devPlugins`), both in the cli unit tier run below.
<details><summary>Red first on origin/main 455229e</summary>
A stack with `devPlugins: ['@acme/dev-only-plugin']` and a `demoStaff`
entry, CLI from source (`bin/run-dev.js`):
- `os compile --json`: exit 0; `dist/objectstack.json` keys
`manifest,objects,positions,devPlugins`, `devPlugins:
["@acme/dev-only-plugin"]`; `demoStaff` absent.
- `os validate --target profile.json --json` (`allowCode: false`): exit
1, one row `{ rule: 'allowCode', where: "dev plugin
'@acme/dev-only-plugin'", path: 'devPlugins[0]' }`.
Same probe on the fix head: compile exit 0, keys
`manifest,objects,positions`; validate exit 0, `valid: true`, no errors.
</details>
<details><summary>Ablation, one leg per behaviour</summary>
`node scripts/ablation-replace.mjs`, each leg restored to blob == HEAD
with `git diff HEAD` empty. Both suites import the subject from `src/`
(in-process `Validate.run`, and `bin/run-dev.js` running the CLI from
source), so no `dist/` leg applies.
- Reader: `new Set(['devPlugins'])` → `new Set([])` (blob 5dde119 →
7c872933b460): 2 failed / 29 passed — the new reader case and the door's
`allowCode: false` case.
- Artifact write: `delete finalBundle.devPlugins;` deleted (blob
b65a74f → 419934d0cafd): 1 failed / 1 passed — the `devPlugins`
case red, the `demoStaff` case still green.
</details>
## Cross-repo check
`gh api -X GET search/code -f q='devPlugins org:objectstack-ai'
--paginate`: 42 hits in `objectstack-ai/objectstack`, 3 in
`objectstack-ai/hotcrm` — all three are
`content/docs/administration/sandbox-and-releases{,.zh-Hans,.zh-Hant}.mdx`,
a key list in docs prose, not a reader. **`objectstack-ai/cloud` is not
reachable from this session** (`gh api repos/objectstack-ai/cloud`
answers 403 "GitHub access to this repository is not enabled for this
session"; `add_repo` answers "you don't have access"), so whether the
cloud control plane reads `devPlugins` out of
`sys_package_version.manifest_json` is **not measured**. In this repo no
package outside `packages/cli` reads `devPlugins` off an artifact:
`packages/spec` holds the schema (`stack.zod.ts`, optional), the
dispositions table, the composition rule and the migration chain's
relevance walk (`migrations/chain.ts`, which reads a listed `devPlugins`
as "unknown" and with the key absent reads the stack's other keys);
`packages/verify/src/harness.ts` only mentions it in a comment.
**Open residual:** whether `objectstack-ai/cloud` reads `devPlugins` out
of `sys_package_version.manifest_json` stays open on #31264, for a seat
with cloud access to measure.
## Spec docblock owed (for the spec seat)
On `RestrictionProfileSchema`'s `allowCode` docblock
(`packages/spec/src/system/restriction-profile.zod.ts`), the sentence to
add:
> `devPlugins` is not judged by `allowCode`: it is loaded only by a
local development boot, and `os build` leaves it out of the artifact a
target installs, so it never reaches a target;
`UNMANAGED_INSTALL_KEY_DISPOSITIONS` still classifies it as `code` for
an unmanaged install.
## Acceptance notes
- Clause-② arm, measured: the reader change widens the accept-set of `os
validate --target` (a stack refused before is accepted). The artifact
change narrows nothing: no door refuses anything new (every stack that
compiled before still compiles), the stack schema still accepts
`devPlugins` on an artifact (an artifact built before this change still
installs and boots), and no reader in this repo loads the key from an
artifact. Hence `yes (widening)`, matching the claim, with two
widenings: the `--target` reader, and an unmanaged install of a fresh
artifact (`package-unmanaged-install.ts` judges the bundle's keys
against `UNMANAGED_INSTALL_KEY_DISPOSITIONS`, and the key is no longer
in the bundle). No dogfood battery is owed (no `(narrowing)` arm, no
`!`).
- `os dev` and `os serve --dev` keep loading `devPlugins` from the
config; `os dev --artifact` boots without a config and never loaded
them, before or after.
- The build summary's `Runtime:` row still counts `devPlugins`:
`collectMetadataStats` reads the loaded config, not the artifact.
## Verification
All on `2f7e23d754`, after `pnpm --filter '@objectstack/cli...' build`.
Head `8f2196e851` adds one prose-only commit on top (the changeset body;
`git diff --name-only 2f7e23d..HEAD` =
`.changeset/31264-build-drops-dev-plugins.md`), so no code, test or gate
input moved; `check:changeset-no-major`, `check:empty-changeset` and
`check:adr-0087-registration` were re-run on the new head (below).
- `pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2
src/utils/restriction-profile.test.ts
test/validate-target-restriction-profile.test.ts
test/build-demo-staff-artifact.test.ts`: lock VERDICT command-exit 0,
Test Files 3 passed (3), Tests 33 passed (33).
- `pnpm --filter @objectstack/cli exec vitest run --project unit
--maxWorkers=2`: lock VERDICT command-exit 0, Test Files 401 passed
(401), Tests 5664 passed (5664). The integration tier is declared to CI;
the one spawn-tier file this diff edits
(`build-demo-staff-artifact.test.ts`) ran green in the run above.
- `pnpm --filter @objectstack/cli typecheck` (tsc +
`check:test-typecheck`): exit 0, test layer OK, debt unchanged.
- eslint (`--no-inline-config`) on the five touched TS files: exit 0.
- `node scripts/pm/dispatch-gates.mjs --commands`: 93 derived families,
every one run with its exit captured before any pipe; all exit 0.
`check:skill-examples` and `check:i18n-coverage` first exited 3
(PREREQUISITE NOT MET: client SDK / example-config dist absent) and were
re-run green after building those closures. `--ran`: 「93 derived
famil(ies) accounted for — 93 run, 0 NOT-MEASURED」, exit 0.
- `node scripts/check-nul-bytes.mjs`: exit 0.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(No spec edit.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes (eslint on the touched
files; the repo run is CI's).
- [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: not touched.
- [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
repo's own convention).
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FEkQ5pBUHysxXskQoeR1p5)_
<!-- signed: domain:server#6 · os-project-manager ·
session_01FEkQ5p3T5YWhLjurajudnQ -->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent c0f88b9 commit ea053b4
7 files changed
Lines changed: 92 additions & 16 deletions
File tree
- .changeset
- content/docs/getting-started
- packages/cli
- src
- commands
- utils
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
237 | 237 | | |
238 | 238 | | |
239 | 239 | | |
240 | | - | |
241 | | - | |
| 240 | + | |
| 241 | + | |
242 | 242 | | |
243 | 243 | | |
244 | 244 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1172 | 1172 | | |
1173 | 1173 | | |
1174 | 1174 | | |
| 1175 | + | |
| 1176 | + | |
| 1177 | + | |
| 1178 | + | |
| 1179 | + | |
| 1180 | + | |
| 1181 | + | |
1175 | 1182 | | |
1176 | 1183 | | |
1177 | 1184 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
79 | 79 | | |
80 | 80 | | |
81 | 81 | | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
82 | 89 | | |
83 | 90 | | |
84 | 91 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
| 36 | + | |
35 | 37 | | |
36 | 38 | | |
37 | 39 | | |
| |||
148 | 150 | | |
149 | 151 | | |
150 | 152 | | |
151 | | - | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
152 | 165 | | |
153 | | - | |
| 166 | + | |
154 | 167 | | |
155 | 168 | | |
156 | 169 | | |
| |||
180 | 193 | | |
181 | 194 | | |
182 | 195 | | |
183 | | - | |
184 | | - | |
185 | | - | |
| 196 | + | |
| 197 | + | |
186 | 198 | | |
187 | 199 | | |
188 | 200 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
21 | 27 | | |
22 | 28 | | |
23 | | - | |
24 | | - | |
| 29 | + | |
| 30 | + | |
25 | 31 | | |
26 | 32 | | |
27 | 33 | | |
| |||
42 | 48 | | |
43 | 49 | | |
44 | 50 | | |
| 51 | + | |
45 | 52 | | |
46 | 53 | | |
47 | 54 | | |
| |||
60 | 67 | | |
61 | 68 | | |
62 | 69 | | |
| 70 | + | |
63 | 71 | | |
64 | 72 | | |
65 | 73 | | |
| |||
91 | 99 | | |
92 | 100 | | |
93 | 101 | | |
94 | | - | |
95 | | - | |
96 | | - | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
97 | 106 | | |
98 | | - | |
99 | 107 | | |
100 | 108 | | |
101 | 109 | | |
102 | | - | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
103 | 118 | | |
104 | 119 | | |
105 | 120 | | |
| |||
121 | 136 | | |
122 | 137 | | |
123 | 138 | | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
70 | 70 | | |
71 | 71 | | |
72 | 72 | | |
73 | | - | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
74 | 78 | | |
75 | 79 | | |
76 | 80 | | |
77 | 81 | | |
78 | 82 | | |
79 | 83 | | |
| 84 | + | |
80 | 85 | | |
81 | 86 | | |
82 | 87 | | |
| |||
147 | 152 | | |
148 | 153 | | |
149 | 154 | | |
150 | | - | |
| 155 | + | |
151 | 156 | | |
152 | 157 | | |
153 | 158 | | |
| |||
0 commit comments