Repository navigation
Commit cbb6df1
fix(service-automation): a runAs:'user' run carries its user's posture rung, so a platform administrator's flow is served the ledger's deployment-level rows (#28098)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DTTrKLU12Z9sF3RMpMJaEeG)_
Fixes #28070
Clause-②: yes (widening) — `RunIdentityContext` (barrel-exported), and
so the data context `resolveRunDataContext` answers, gains an optional
`posture` member, and `FlowUserGrants` gains one; a `runAs: 'user'` run
seeded with `PLATFORM_ADMIN` is served the rows that rung admits.
Metadata-protocol: none
Skills-docs: updated here
(`content/docs/permissions/record-view-auditing.mdx`, one sentence
beside #26750's). `grep -rn -i "posture" skills/objectstack-automation`
and `grep -n -i "runAs" content/docs/automation/flows.mdx`: no text
states which facts a `runAs: 'user'` run carries beyond positions /
permissions / tenant, so nothing else to change.
## 维护者速读
- **改了什么**:`runAs: 'user'` 的自动化运行现在带上运行用户的姿态等级(posture rung),取自请求路径同一次
`resolveUserAuthzGrants`
解析,本包不做第二次推导。以平台管理员身份运行的流程,能读到审计账本里的部署级记录(`tenant_id` 为空),和他本人的请求一致。
- **为什么改**:#26750 之后部署级记录只给 `PLATFORM_ADMIN`,而运行上下文没有
rung,平台管理员的流程只看到组织行(收窄,不是泄露)。分诊裁定:走同一推导、不另起一套。
- **风险与回滚**:rung 只和推导出它的 grants 一起种下;门把已解析的 positions/permissions 带进来(含
agent 收窄上限)时不补 rung,不会放大。审批切换到裁决人时删掉提交人的 rung。`runAs: 'system'` 不变。回滚即
revert,无数据迁移。
-
**先看**:`packages/services/service-automation/src/run-resume.ts`(`seedRunGrants`
与裁决切换各一处)、`runtime-identity.ts`(`resolveRunDataContext` 的 user 分支)、新测试
`runas-user-posture-rung.integration.test.ts`。
- **两分钟验证**:`pnpm --filter @objectstack/service-automation exec vitest
run --maxWorkers=2 src/runas-user-posture-rung.integration.test.ts`,预期
12 条全绿。
- **需要你做的**:无,席位落地。
## What changes
- **The bridge** (`plugin.ts`). The grants bridge the automation plugin
wires already calls `@objectstack/core`'s `resolveUserAuthzGrants` for
the run's user; it now forwards that resolution's own `posture` beside
the positions, permission sets, peers and email. Nothing in this package
derives a rung.
- **The seed** (`run-resume.ts`, `seedRunGrants`). The rung is seeded
exactly when the run's positions and permission sets are resolved at
setup (the `needsGrants` branch), so it always stands beside the grants
it was derived from. A context that brought its own grants (a door's
pre-resolved envelope, an agent's narrowed ceiling) gains no rung from a
re-resolution, though it still gains its peers and email as before.
- **The decision switch** (`run-resume.ts`, `deciderSegmentContext`).
The submitter's rung is deleted with their other facts; the decider's
segment is seeded with the decider's own. Without the delete, a decider
the resolver answers nothing for would have kept a platform
administrator's rung.
- **The data context** (`runtime-identity.ts`).
`SeededRunContext.posture` and `RunIdentityContext.posture` (both typed
`AuthzPosture` from `@objectstack/spec/security`);
`resolveRunDataContext`'s user arm carries the seeded rung through
`runPosture`, which drops a value that names no rung. The system arm is
untouched.
- **Types** (`engine.ts`, two small hunks). `FlowUserGrants.posture`,
typed as `SeededRunContext['posture']` so no import hunk; the
`seedRunGrants` docblock (its contract) names the rung.
## Why
Triage `6522159967`: a `runAs: 'user'` run derives its rung through the
same derivation a request uses, for the user it runs as, with no second
derivation. Re-measured on `main` @ `549203d0`: `grep -n posture
packages/services/service-automation/src/run-resume.ts` hit only two
unrelated comments (lines 314, 820; tenancy posture), and the control
test below shows the administrator's request without the rung (the shape
a run carried) served the organization row only.
## Verification
Head `cfd3bf44`.
- `runas-user-posture-rung.integration.test.ts`, 12 tests, two layers.
Envelope (bare engine, recording node, resolver stub): the rung rides
the data context for a user-only trigger, for a member, after an
approval decision (the decider's), and is absent for carried grants, for
a value that is not a rung, for a decider the resolver answers nothing
for, and on `runAs: 'system'`. Real stack (ObjectKernel, ObjectQLPlugin,
driver-sql on better-sqlite3, identity objects, SecurityPlugin,
AuditPlugin's ledger read gate, the plugin's own bridge, `single`
posture): the platform administrator's run is served every
deployment-level `platform_admin_standing_change` row plus the
organization's one; an organization holder of `view_all_audit_log` is
served the organization row only, by run and by request, with or without
the rung; a `runAs: 'system'` run is served all, as before.
- Ablation 1, `if (posture) out.posture = posture;` disabled in
`runtime-identity.ts`: 4 red (the card's reproduction, plus three
envelope cells); the member and system pins stay green (unchanged).
- Ablation 2, `delete switched.posture;` removed in `run-resume.ts`: 1
red (the unresolved decider keeps the submitter's rung).
- `pnpm --filter @objectstack/service-automation typecheck`: exit 0.
`pnpm --filter @objectstack/service-automation test`: 228 files, 2888
tests, exit 0.
- `dispatch-gates --commands` derived 93 families; they and `pnpm lint`
are running locally. The reconciled `--ran` result goes in the os-dev
report on the card; CI runs every family here too.
## Acceptance notes
- Out of scope, card it: the REST automation door
(`packages/runtime/src/domains/automation.ts:204-205`) and the action
door (`packages/runtime/src/action-execution.ts:2077-2078`) forward the
request's `positions` / `permissions` but not `ec.posture`, so a flow
started there carries grants and no rung, and still fails closed for the
rung's readers. Forwarding it touches `AutomationContext`
(`packages/spec/src/contracts/automation-service.ts`) or the door's
extra-key shape. The changeset and the docs sentence say so.
- Request parity for the other readers of `ExecutionContext.posture` on
a run (plugin-sharing `hasPlatformAuthority`, plugin-approvals' platform
arm, observability's timing exposure): a platform administrator's run
now answers as their request does. plugin-security's tenant-wall
exemption already enforced the narrower of the carried rung and its
capability probe, so a seeded rung never widens it.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(No `packages/spec` change.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes. (Running locally with the
gates; CI reads it.)
- [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: its generated artifacts are
regenerated and committed. (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 maintainer's summary, as on the lane's other PRs.)
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_016WV4cwHfcrFZ8RzH1TiP48
Co-authored-by: Claude <noreply@anthropic.com>1 parent 8ec00cf commit cbb6df1
7 files changed
Lines changed: 415 additions & 4 deletions
File tree
- .changeset
- content/docs/permissions
- packages/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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
224 | 224 | | |
225 | 225 | | |
226 | 226 | | |
227 | | - | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
228 | 232 | | |
229 | 233 | | |
230 | 234 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
802 | 802 | | |
803 | 803 | | |
804 | 804 | | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
805 | 814 | | |
806 | 815 | | |
807 | 816 | | |
| |||
5655 | 5664 | | |
5656 | 5665 | | |
5657 | 5666 | | |
5658 | | - | |
| 5667 | + | |
| 5668 | + | |
5659 | 5669 | | |
5660 | 5670 | | |
5661 | 5671 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1457 | 1457 | | |
1458 | 1458 | | |
1459 | 1459 | | |
| 1460 | + | |
| 1461 | + | |
| 1462 | + | |
| 1463 | + | |
1460 | 1464 | | |
1461 | 1465 | | |
1462 | 1466 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
215 | 215 | | |
216 | 216 | | |
217 | 217 | | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
218 | 223 | | |
219 | 224 | | |
220 | 225 | | |
| |||
309 | 314 | | |
310 | 315 | | |
311 | 316 | | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
312 | 321 | | |
313 | 322 | | |
314 | 323 | | |
| |||
0 commit comments