Repository navigation
Commit f00e8d5
feat(spec,plugin-auth,runtime,cli): demoStaff, the demo accounts an application declares and the development boot creates (#25830)
<!-- claude-projects-attribution: {"github_login":"marchtian"} -->
_Requested by **田騏** · [project
thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSiY9uaapuEYV5dehBVG6QqMr)_
Fixes #25777
Clause-②: yes
Metadata-protocol: added
## 维护者速读(草稿)
**改了什么**
应用现在可以在 stack 里声明演示账号:`demoStaff: [{ name, email, position?,
permissionSets? }]`。本地开发启动(`os
dev`)时,平台在开发管理员之后,用同一条注册通道把这些账号建出来,邮箱直接记为已验证,密码统一用开发种子密码(`os dev
--admin-password`,默认
`admin123`);再在默认组织里给每个账号挂上声明的岗位和权限集;然后才跑应用的种子数据,所以种子里写 `owner_id: 'Legal
Counsel 1'`
的记录第一次启动就能指到真人。重复启动不重复建号,已有的账号原样不动,只补缺的岗位/权限集。应用自己不建号,只声明(遵循裁定"demo
accounts are the platform's to create; the app only declares them")。
**为什么改**
今天每个演示应用(HotCRM 等)都得自己写脚本建演示账号、挂岗位、重算共享规则,种子里的 `owner_id`
第一次启动还指不到人。这张卡把这件事收回平台。
**风险与代价(含回滚)**
- 这是能登录的账号,所以只在本地开发启动生效。其余启动一律不建号并说明原因(ADR-0112 码):非
`NODE_ENV=development` 或 migrate-and-exit 运行,`PERMISSION_DENIED` 403,记
`info`;开发启动下若是组织隔离(walled)姿态、或认证基址不是回环地址,`PERMISSION_DENIED`
403;库里还没有任何非演示账号能登录(演示账号会被提升成平台管理员),`PRECONDITION_REQUIRED`
428;凭据库读不了,`SERVICE_UNAVAILABLE` 503;这几种记 `warn`。每种都有带 code+status 的测试。
- `os build` 写出的 `objectstack.json`
里没有这个键,部署从产物启动时无号可建(有构建输出测试)。但应用有函数(callable)时,`objectstack-runtime.*.mjs`
会把整份 config 代码打包进去,演示账号的名字和邮箱文字会出现在这个模块里(它只导出 `functions` 和
`meta`,运行时读不到这份列表)。`data`、`devLogins` 今天也是同样情况。
- 声明了演示账号的开发启动,种子推迟到 `kernel:bootstrapped` 才跑,plugin-security 会在
`kernel:ready` 打一行 "PROVISIONAL … 1 seed source(s) were still writing" 的
WARN(与种子超时同一路径)。
- 没声明 `demoStaff` 的应用,启动、种子、构建都与之前完全一样。回滚即 revert 本
PR:无迁移,只有开发库里已建的演示账号会留着。
**席位意见**
**你要做的**
请批准或改动以下取舍(都是 shape 决定,代码已按左侧实现):
1. 键名 `demoStaff`(顶层,与 `devLogins` 并列),而不是卡上草拟的 `demo.staff[]`。
2. 不满足条件时跳过并记日志,而不是让启动失败。
3. 演示账号统一用开发种子密码,schema 拒收 `password` 字段(仓库里不落凭据)。
4. 种子推迟带来的那行 WARN 可以接受。
5. runtime 模块里带着 config 文字:接受(同 `data`/`devLogins`),还是另开卡把开发专用键从打包里剥掉。
6. walled 姿态一律拒绝。
7. 启动横幅里列出演示账号不在本 PR 范围。
本 PR 动了 spec 和 `skills/**`(Tier H),落地需要你的批准。
## What changed
**`@objectstack/spec`**
- `packages/spec/src/system/demo-staff.zod.ts` (new): `DemoStaffSchema`,
a strict entry `{ name, email, position?, permissionSets? }` with
aliases and guidance for `password` and `organization`.
- `stack.zod.ts`: `demoStaff` in the stack shape (compose disposition
`concat`, an assembled-package envelope key). `validateCrossReferences`
refuses an entry naming a position or permission set the stack does not
declare, and two entries with one name or one address
(case-insensitive): `StackCrossReferenceError`,
`STACK_CROSS_REFERENCE_INVALID` / 422.
- `kernel/unmanaged-install.ts`: `demoStaff: 'deployment'`. Generated
surfaces, reference docs and the strictness-ledger count regenerated;
the hand-kept schema counts in `llms.txt` and
`getting-started/quick-reference.mdx` follow (system 37).
**`@objectstack/plugin-auth`**
- `src/demo-staff.ts` (new): `decideDemoStaffEnvironmentGate` (pure),
`probeDemoStaffPriorLogin`, `provisionDemoStaff`.
- `auth-plugin.ts`: `AuthPluginOptions.demoStaff`; the gate is decided
in `init()` and published as the `demo-staff` service (`{ armed }`); a
`kernel:ready` hook registered right after the dev-admin hook signs each
account up through the operator provisioning ticket
(`stageOperatorProvisioning` → `signUpEmail` →
`clearOperatorProvisioning`), stamps `email_verified`, and writes
`sys_user_position` / `sys_user_permission_set` rows in
`tenancy.defaultOrgId()`. It never writes a membership (ADR-0093 D2); an
account the membership policy left outside the Default Organization is
reported in `failed` and gets no assignment (an organization-less row
applies nowhere, ADR-0131 D1). No `sys_user` row goes through datasets
(ADR-0092).
- `walled-owner-verification-path.ts`: `devSeedAdminPassword()`, shared
with `maybeSeedDevAdmin`.
**`@objectstack/runtime`**
- `app-plugin.ts`: when the `demo-staff` service reports `armed: true`,
the inline seed runs at `kernel:bootstrapped` instead of inside
`start()`. The seed source is declared as before, so the settlement
tally and `app:seeded` take the existing over-budget path.
**`@objectstack/cli`**
- `serve.ts` hands `config.demoStaff` to `AuthPlugin`; `compile.ts`
deletes it from the bundle before `objectstack.json` is written.
- `test/option-b-reader-acceptance.pin.test.ts`: `demoStaff` joins the
pinned artifact envelope keys.
**Repo**
- `scripts/check-stack-collection-maps.mjs`: `demoStaff` is a
non-collection array key (like `devLogins`).
- `skills/objectstack-platform/SKILL.md`: `demoStaff` in the top-level
key list (`check-skill-top-level-keys` requires it). Readings: the file
488 → 488 lines (23301 → 23314 bytes); the package (sum of all SKILL.md)
4383 → 4383 lines.
- Tenant-audit census: the three new elevated writes are counted (240 →
243 sites); census and its prose page updated.
- `scripts/engine-double-contract.pinned.json`: records the new test
double whose `update()` is pinned to `assertEngineUpdateDispatch`.
- `scripts/check-single-authz-resolver.mjs` (7a2e7a8): an ALLOW entry
for `packages/plugins/plugin-auth/src/demo-staff.ts`, with its reason.
The file queries both `sys_user_position` and `sys_user_permission_set`,
which the gate reads as a second authz resolver. It is not one: it reads
each grant table only to ask whether the one literal row it is about to
insert (one user, one declared position or permission set, the Default
Organization, limit 1) is already there, so a second boot adds nothing.
It aggregates no grants and builds no context; a demo account's grants
resolve through `resolveAuthzContext` like any other account's. `pnpm
check:authz-resolver` runs in lint but is not in the derived gate list,
so CI caught it, not the local run.
- `content/docs/getting-started/quick-start.mdx` (465b6c2):
`demoStaff` joins `devLogins` in the "Not in the table, and why"
paragraph, which lists every top-level key the stack-key table leaves
out, with one clause on what it does: a local `os dev` boot creates the
accounts, and `os build` leaves the key out of `objectstack.json`.
## PM assumptions, measured
- "Seeded rows carry no sharing grants until a rule is re-evaluated (the
`plugin-sharing` write hook returns early for `isSystem`)": **no longer
true.** `packages/plugins/plugin-sharing/src/rule-hooks.ts` retired the
`isSystem` materialisation skip (its header, "RETIRED"), and
`recipient-membership-recompute.ts` re-evaluates rules on a
`sys_user_position` write. So a separate "re-evaluate every sharing
rule" step is not added: the position write and the seed's own inserts
drive it. #16712 is no longer open; its work was re-cut as #20292.
- HotCRM `scripts/demo-staff.ts` four steps, as covered here: create
accounts (sign-up door), assign positions (and permission sets), sharing
re-evaluation (above), sign in as each (real-kernel test 1 signs in
through `/api/v1/auth/sign-in/email`).
## Acceptance notes
- Tests: spec `src/stack-demo-staff.test.ts` (14); plugin-auth
`src/demo-staff.test.ts` (14, every gate clause asserts code + status +
reason) and `src/demo-staff-provisioning.test.ts` (real kernel on
SQLite: first boot creates, joins the Default Organization, assigns
there, signs in; only the dev admin holds `admin_full_access`;
idempotent; an existing account untouched; no membership reported and
nothing written; production 403 at `info`; migrate-and-exit 403;
non-loopback 403; no prior login 428); runtime
`src/app-plugin.seed-demo-staff.test.ts` (6); cli
`test/build-demo-staff-artifact.test.ts` (integration tier: no key or
address in `objectstack.json`, exactly one runtime module,
`loadArtifactBundle` reads no `demoStaff` and no address outside
`functions`, positions and the callable survive as controls) and
`src/commands/serve-auth-demo-staff.contract.test.ts`.
- Smoke run on `examples/app-todo` (temporary edit, reverted): first `os
dev` boot, the seeded task "Fix Server bug" has `owner` = Todo Helper
1's id; `helper1@todo.example` signs in with `admin123`, address
verified; `os build` output has 0 hits for the key.
- Out of scope, for the maintainer: the runtime module carries dev-only
config text (`demoStaff`, `devLogins`, `data`) whenever a stack has
callables; it is not exported or loaded. No carrier yet.
- Not done here: listing the demo accounts in the `os dev` banner.
## Gates and tests
- `node scripts/pm/dispatch-gates.mjs --commands` on the final diff
derives 153 commands. All 153 ran on ed03f3f (after merging `main`),
each exit code recorded before any pipe: 153 exit 0. `--ran`
reconciliation: "153 derived famil(ies) accounted for — 153 run, 0
NOT-MEASURED (a DERIVED zero — all 153 recorded an exit code and none of
them is 3)". The build prerequisites were built first, so
`check:skill-examples`, `check:dual-build-cjs-loads` and
`check:i18n-coverage` measured instead of refusing.
- Found by earlier full runs and fixed in this PR: the hand-kept schema
counts (`check:llms-txt`, `check:quick-reference-counts`), the new test
doubles (`check:objectql-double-limit`, `check:engine-double-contract`,
with `check:where-matcher` kept clean), and the three new elevated write
sites (`check-tenant-audit-census`).
- Tests on ed03f3f: spec 721 files, 21835 passed; plugin-auth 195
files, 3704 passed (10 skipped); runtime 445 files, 7106 passed (19
skipped); cli `--project unit` 332 files, 4848 passed; cli integration
`test/build-demo-staff-artifact.test.ts` 1 passed. `pnpm typecheck` for
spec, plugin-auth, runtime and cli: exit 0. `check:generated`: exit 0.
- Dogfood battery: not run locally (no `!` in the title and no
`(narrowing)` arm).
- Where these readings come from: the gate and test counts above were
measured on ed03f3f. The commits after it (465b6c2 docs, 7a2e7a8
the ALLOW entry, and the d354551 merge of `main`) were not re-measured
locally. CI on d354551 is the current reading: all 47 check runs
completed, each with success or skipped. Success includes Lint and Repo
Gates (with `check:authz-resolver`), the TypeScript and Type Check jobs,
Build Core, Build Docs, Check Changeset, all eight Test Core shards and
the Test Core summary, Type Check UI, and Build and E2E UI. Skipped by
their own conditions: Lint UI, Test UI, Dogfood Verify CLI, the Dogfood
Regression Gate shards, Temporal Conformance, and the two opt-in jobs.
## Independent review
A full independent review of the whole diff (isolated subagent on the
`fable` model) and re-reviews of each later commit: REQUEST-CHANGES,
then APPROVE-WITH-NITS, APPROVE, APPROVE. The verdicts are quoted in a
comment on this PR. The last Fable pass read 67953e8.
Four commits came after that pass and were not Fable-reviewed: 465b6c2
(the quick-start key list), 7a2e7a8 (the `check:authz-resolver` ALLOW
entry), and the `main` merges ed03f3f and d354551. The seat's
contract review at d354551 (PASS, no must-fix, in its comment on this
PR) read them: the two PR-authored commits in full, and the net diff
against `main`, which carries what both merges resolved.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01SHjswg4gueCJQMHKteQQJ4)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent cb0a2fc commit f00e8d5
38 files changed
Lines changed: 2014 additions & 44 deletions
File tree
- .changeset
- content/docs
- getting-started
- permissions
- references
- system
- docs/audits
- 2026-07-unknown-key-strictness-ledger.counts
- packages
- cli
- src/commands
- test
- plugins/plugin-auth/src
- runtime/src
- spec
- api-surface
- authorable-surface
- declaration-map
- export-origins
- json-schema.manifest
- src
- kernel
- system
- scripts
- skills/objectstack-platform
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
85 | 85 | | |
86 | 86 | | |
87 | 87 | | |
88 | | - | |
| 88 | + | |
89 | 89 | | |
90 | 90 | | |
91 | 91 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
233 | 233 | | |
234 | 234 | | |
235 | 235 | | |
236 | | - | |
237 | | - | |
238 | | - | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
239 | 241 | | |
240 | 242 | | |
241 | 243 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
132 | 132 | | |
133 | 133 | | |
134 | 134 | | |
135 | | - | |
| 135 | + | |
136 | 136 | | |
137 | 137 | | |
138 | 138 | | |
| |||
197 | 197 | | |
198 | 198 | | |
199 | 199 | | |
200 | | - | |
| 200 | + | |
201 | 201 | | |
202 | | - | |
203 | | - | |
| 202 | + | |
| 203 | + | |
204 | 204 | | |
205 | 205 | | |
206 | 206 | | |
| |||
217 | 217 | | |
218 | 218 | | |
219 | 219 | | |
220 | | - | |
| 220 | + | |
221 | 221 | | |
222 | 222 | | |
223 | 223 | | |
224 | | - | |
| 224 | + | |
225 | 225 | | |
226 | 226 | | |
227 | 227 | | |
| |||
233 | 233 | | |
234 | 234 | | |
235 | 235 | | |
236 | | - | |
237 | | - | |
| 236 | + | |
| 237 | + | |
238 | 238 | | |
239 | | - | |
| 239 | + | |
240 | 240 | | |
241 | | - | |
| 241 | + | |
242 | 242 | | |
243 | 243 | | |
244 | 244 | | |
245 | 245 | | |
246 | | - | |
| 246 | + | |
247 | 247 | | |
248 | 248 | | |
249 | 249 | | |
250 | 250 | | |
251 | 251 | | |
252 | | - | |
| 252 | + | |
253 | 253 | | |
254 | 254 | | |
255 | 255 | | |
256 | 256 | | |
257 | | - | |
| 257 | + | |
258 | 258 | | |
259 | 259 | | |
260 | 260 | | |
| |||
307 | 307 | | |
308 | 308 | | |
309 | 309 | | |
310 | | - | |
| 310 | + | |
311 | 311 | | |
312 | 312 | | |
313 | 313 | | |
314 | | - | |
315 | | - | |
| 314 | + | |
| 315 | + | |
316 | 316 | | |
317 | 317 | | |
318 | 318 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
35 | | - | |
| 35 | + | |
36 | 36 | | |
37 | | - | |
| 37 | + | |
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
| |||
317 | 317 | | |
318 | 318 | | |
319 | 319 | | |
320 | | - | |
| 320 | + | |
321 | 321 | | |
322 | 322 | | |
323 | 323 | | |
| |||
329 | 329 | | |
330 | 330 | | |
331 | 331 | | |
| 332 | + | |
332 | 333 | | |
333 | 334 | | |
334 | 335 | | |
| |||
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
18 | 19 | | |
19 | 20 | | |
20 | 21 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
| 42 | + | |
42 | 43 | | |
43 | 44 | | |
44 | 45 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
22 | | - | |
| 22 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
36 | | - | |
37 | | - | |
| 36 | + | |
| 37 | + | |
38 | 38 | | |
39 | | - | |
| 39 | + | |
40 | 40 | | |
41 | | - | |
| 41 | + | |
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
46 | | - | |
| 46 | + | |
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
| |||
90 | 90 | | |
91 | 91 | | |
92 | 92 | | |
93 | | - | |
| 93 | + | |
94 | 94 | | |
95 | 95 | | |
96 | 96 | | |
97 | | - | |
98 | | - | |
| 97 | + | |
| 98 | + | |
99 | 99 | | |
100 | 100 | | |
101 | 101 | | |
| |||
144 | 144 | | |
145 | 145 | | |
146 | 146 | | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
147 | 150 | | |
148 | 151 | | |
149 | 152 | | |
| |||
0 commit comments