Repository navigation
Commit f3d2845
feat(platform-objects): the identity actions an agent may reach declare it — additive acts exposed, destructive lifecycle acts behind confirmation, credential and ownership acts human-only (#28270)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DHbeHcgBQuozCHjdGsnhPGD)_
domain:server#3 · os-elon-musk · session_016WV4czk9umSFBVoz5cijzX
Fixes #27900
Ruling-ref: 6523087116 (letter A+)
Tier: H (`docs/adr/0011-actions-as-ai-tools.md`): a contract review,
then an authorised approval by a GOVERNED_APPROVER, before the queue.
Clause-②: no (values on an existing metadata key, platform-owned; no
export, answer member, status name or error code is added, and the MCP
doors' code is unchanged: their existing per-action opt-in now admits
the actions that declare it)
Metadata-protocol: none
Skills-docs: updated here — `content/docs/ai/connect-mcp.mdx` gains one
bullet (which identity actions an MCP agent sees, and what an
OAuth-connected agent gets today); `grep -rniE
"sys_(user|member|invitation|team|organization|session|api_key|oauth|sso|two_factor|account)\b.{0,80}(ai|agent|mcp|exposed)|(ai\.exposed|exposed).{0,120}sys_"
skills content/docs` found no skill or doc naming which platform
identity actions are agent-exposed.
## 维护者速读
- 改了什么:12 个身份对象上的 58 个具名动作按裁决 A+ 分三档写声明。E 档 21 个写 `ai: { exposed: true,
description }`(给模型看的说明);C 档 17 个另加 `requiresConfirmation: true`,MCP 不带
`confirm: true` 就 428 拒绝、什么都不执行;H 档 20 个不写 `ai` 块,不在列表里,按名拒绝。ADR-0011 新增
2026-10-30 附记,列全三档名单和 H 档规则。没有新增任何自动检查。
- 计数更正:卡里档头写 H 19 / E 22,按卡里列的名字数是 H 20 / E 21,所以是 38 个 `ai` 块而不是 39
个。名字为准。
- 未评估:`sys_invitation.copy_invitation_link`
是评估之后才加的(复制邀请链接,链接本身就是凭据),保持不声明,等您点名裁。
- 请您看一眼:两个 E 档动作 `sys_user.enable_two_factor` /
`sys_two_factor.enable_two_factor` 要用户当前密码作参数,返回认证器 URI 和一次性备用码。暴露给
agent 就意味着 agent 向用户要密码、并拿到新的登录因子,这接近 H 档的「铸造凭据」。按卡原样落了;您一句话可以把它们移到
H(删两个块 + ADR 一行)。
- OAuth agent 今天的实际情况:列表能看到 E/C 动作,C 未确认照样 428;但执行一到身份路由就
401、什么都没改(这些路由只认登录会话)。会话调用同一个动作是成功的。让 OAuth agent 真正跑通要等 #28178 的裁决,本 PR
不碰凭据边界。
- 需要你做的:Tier H,合同复审通过后需 os-zhuang 或 hotlong 批准。
## What changes
**Declarations (`packages/platform-objects/src/identity/*.object.ts`, 12
files).** Each action the ruling places in bucket E gains `ai: {
exposed: true, description }`, with a description written for the model
(what it does, its params, when to call it). Each bucket C action gains
the same plus `requiresConfirmation: true`, and its description ends by
asking the model to confirm with the human first. Bucket H actions are
untouched and carry no `ai` block. Each block sits last in its action,
under a one-line comment naming the bucket.
| Bucket | Count | Actions |
|:--|:--|:--|
| E | 21 | the four `invite_user` mirrors (`sys_user`, `sys_invitation`,
`sys_organization`, `sys_member`), `sys_invitation.resend_invitation`,
`sys_invitation.accept_invitation`, `sys_member.add_member`,
`sys_team_member.add_team_member`, `sys_team.add_team_member`,
`sys_team.create_team`, `sys_team.update_team`,
`sys_organization.create_organization`,
`sys_organization.update_organization`,
`sys_organization.rename_organization`,
`sys_organization.set_active_organization`,
`sys_oauth_application.enable_oauth_application`,
`sys_user.enable_two_factor`, `sys_two_factor.enable_two_factor`,
`sys_user.update_my_profile`, `sys_user.resend_verification_email`,
`sys_sso_provider.request_domain_verification` |
| C | 17 | `sys_user.ban_user`, `sys_user.unban_user`,
`sys_user.unlock_user`, `sys_user.set_user_manager`,
`sys_api_key.revoke_api_key`, `sys_session.revoke_session`,
`sys_session.revoke_my_other_sessions`,
`sys_invitation.cancel_invitation`, `sys_invitation.reject_invitation`,
`sys_member.remove_member`, `sys_team_member.remove_team_member`,
`sys_team.remove_team`,
`sys_oauth_application.disable_oauth_application`,
`sys_oauth_application.delete_oauth_application`,
`sys_sso_provider.delete_sso_provider`,
`sys_organization.leave_organization`, `sys_account.unlink_account` |
| H | 20 | no `ai` block (listed in the ADR addendum) |
**ADR-0011 addendum (2026-10-30).** Records the ruling, the rubric, all
three name lists, the H rule (no `ai.exposed` on an H action without a
maintainer ruling naming it), the count correction,
`copy_invitation_link` as not assessed, the struck automatic check, how
the declarations reach an agent (pointing at ADR-0066 D4's 2026-10-30
note for the mechanism and naming these 38 as the platform `sys_*`
actions that declare it), and what an OAuth-connected agent gets today.
This keeps the two ADR notes consistent, per the skills note on the card
(6523989016): ADR-0066's note states the per-action mechanism; this
addendum says which actions use it.
**Docs.** One bullet in `content/docs/ai/connect-mcp.mdx` under the
exposure policy.
**Changeset.** `@objectstack/platform-objects` patch.
## Why
Ruling 6523087116 (letter A+) on #27900 executes parameter 2 of ruling
6513869839 on #27681 as one assessment of the platform's kernel actions,
as the maintainer asked (「但是对于系统内核对象,是否应该统一评估一下。」), and settles that the
actions allowed to open get their declarations now:
「所以我说评估标准对象的意思,就是允许放开的动作,是不是就应该把申明加上」. The maintainer struck the
automatic check (「自动检查 有必要吗?…」), so the table lives only in the ADR.
## Premise re-read (dispatch step 1)
Read on `main` @ `be646ede`, then rebased onto `744ad0ad`. All 58 names
the card lists exist on the object and under the name the card gives.
Two differences from the card, reported to the seat before any code, and
the seat ruled the way on:
- `sys_invitation.copy_invitation_link` (type `script`) exists on `main`
and in no bucket; it is absent at the card's read sha `ded67bfb`. It
keeps no `ai` block, today's state; the addendum names it as not
assessed.
- The card's bucket headers (H 19, E 22) disagree with its own name
lists (H 20, E 21). The names are the record: 38 blocks, not 39.
## The MCP dogfood, one case per bucket
`packages/qa/dogfood/test/mcp-system-object-action-exposure.dogfood.test.ts`
(rewritten; booted showcase, live MCP transport, better-auth admin
plugin on, embedded OAuth authorization server on). The test-only `ai`
block it used to set on `ban_user` is gone: it reads the platform's own
declarations, and a premise check asserts one action per bucket
(`ban_user` C, `update_my_profile` E, `impersonate_user` H). It is not a
pin over the table.
The **OAuth-connected agent** is the caller the ruling exposes these
actions to. Its token is minted the way an MCP client connects: dynamic
client registration, authorize with `resource` = the MCP URL and PKCE
S256, consent, token (scopes `data:read data:write actions:execute`).
| Case | Caller | Answer on this branch |
|:--|:--|:--|
| E listed | OAuth agent | `update_my_profile` listed, `aiExposed:
true`, `requiresConfirmation: false` |
| E run | OAuth agent | **today:** `401 UNAUTHORIZED` from the identity
route (`/api/v1/auth/update-user`); the name is unchanged |
| E run, contrast | admin session | served: `ok: true`, the name changes
|
| C unconfirmed | OAuth agent | `428 ACTION_CONFIRMATION_REQUIRED`,
nothing run |
| C confirmed | OAuth agent | **today:** `401 UNAUTHENTICATED "Sign in
first"` from `/api/v1/auth/admin/ban-user`; nobody banned |
| H | OAuth agent | `impersonate_user` absent from the listing, refused
by name |
The #27681 pins stay on the administrator's session, now on the platform
declaration: `ban_user` listed and attested, `428` unconfirmed, `409
ACTION_DISABLED` with the ADR-0126 switch off, a plain member's
confirmed call refused by the target, the confirmed admin call bans. The
control moved from `unban_user` (now C, exposed) to `impersonate_user`
(H).
**What waits on #28178.** The ruled outcome of the E run (an OAuth
agent's call served) cannot be shown without changing which credentials
an identity route or the REST face accepts, which #28178 governs. Per
the dispatch, this PR does not build around it: the run case pins
today's `401` and says it waits on #28178, and the session contrast
shows the declaration itself is live. When #28178 lands, flip the two
"today" expectations. For the record, the agent's listing on `sys_user`
is exactly the eight E and C actions there (`invite_user`, `ban_user`,
`unban_user`, `unlock_user`, `set_user_manager`, `update_my_profile`,
`resend_verification_email`, `enable_two_factor`).
## Two tests whose premise the declarations changed (test-only edits,
surface amended by the seat)
-
`packages/platform-objects/src/identity/action-confirm-one-dialog.test.ts`:
it asserted that the #7309-converted actions have no `ai.description`,
so that the dialog question was never moved into the tool description.
Seven of them now carry a model-facing `ai.description` by the ruling.
The assertion now checks the intent directly: each still has its dialog
`description`, and its `ai.description` is not that question. Nothing
else in the file changed.
- `packages/runtime/src/action-execution-destructive.test.ts`: it
asserted that all 13 #7309 identity actions are excluded at the MCP
gates because none declares `ai.exposed`. Seven of the 13 are now
exposed (C: `ban_user`, `disable_oauth_application`,
`delete_oauth_application`, `unlink_account`, `delete_sso_provider`,
`remove_team_member`; E: `enable_oauth_application`). The six human-only
ones keep both exclusion pins unchanged. The seven are pinned admitted
at gates 1 and 3, with the listing's `requiresConfirmation` equal to the
declared flag. `disable_oauth_application` left the "no `ai` override,
not destructive" list because it now declares `requiresConfirmation:
true`, and a new case pins that the declared override wins (the
precedence the file already pins). No assertion was dropped for an
action whose declaration did not change.
## Verification
- The dogfood above at the final head `0bd14911`: 11/11 (five of the
file's six prior cases, plus the session contrast and the four OAuth
cases; the old `unban_user` control case became the `impersonate_user`
one). Run beside it, `api-action-door-forward.dogfood.test.ts` and
`two-factor-backup-code-reveal.dogfood.test.ts` stay green: 3 files,
25/25.
- Ablation (committed state, rebuilt `@objectstack/platform-objects`
dist, then restored and rebuilt): with `ban_user`'s
`requiresConfirmation` removed and an `ai` block added to
`impersonate_user` (premise checks taken out so the cases speak), 8 of
11 red: the session listing, the session `impersonate_user` refusal,
both `428` cases, the agent's H case, and three cascades (the switch,
the confirmed ban and the session contrast, after the unguarded calls
had run). Restored: 11/11.
- `@objectstack/platform-objects` suite: 104 files, 1474/1474.
- `packages/runtime/src/action-execution-destructive.test.ts`: 57/57.
Runtime suite: 490 files, 8854 passed, 19 skipped.
- `@objectstack/mcp` suite: 47 files, 603/603.
- Typecheck: `pnpm --filter @objectstack/platform-objects typecheck` and
`pnpm --filter @objectstack/runtime typecheck` exit 0; the dogfood
package's `tsc --noEmit` (8 GB heap) exits 0.
- Every family `dispatch-gates --commands` derives (94) ran after a
whole-tree build (`pnpm build`, 115/115) and exited 0, reconciled with
`--ran`: 94 run, 0 NOT-MEASURED. One of them,
`check-plugin-teardown-shape.mjs --self-test`, first exited 3 because
the shallow clone could not read its pinned fixture commit; after
fetching that commit it exited 0 (48 cases). `pnpm lint` exits 0.
## Acceptance notes
- The two `enable_two_factor` rows (E) take the user's current password
as a param and answer the authenticator URI and one-time backup codes.
Exposed to an agent, the agent asks for the password and receives fresh
sign-in factors, close to the rubric's H test. Landed as the card lists
them; the seat has taken the question to the maintainer.
- Every description says what the model must pass. The MCP door does not
fill `defaultFromRow` params from the record, so a C action like
`delete_sso_provider` names its `providerId` param in the description.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(Not touched.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes.
- [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
lane's maintainer summary; quoted rulings stay verbatim).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_016WV4czk9umSFBVoz5cijzX
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 91fa06d commit f3d2845
18 files changed
Lines changed: 669 additions & 78 deletions
File tree
- .changeset
- content/docs/ai
- docs/adr
- packages
- platform-objects/src/identity
- qa/dogfood/test
- runtime/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 | |
|---|---|---|---|
| |||
261 | 261 | | |
262 | 262 | | |
263 | 263 | | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
264 | 278 | | |
265 | 279 | | |
266 | 280 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
305 | 305 | | |
306 | 306 | | |
307 | 307 | | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
308 | 388 | | |
309 | 389 | | |
310 | 390 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
105 | 105 | | |
106 | 106 | | |
107 | 107 | | |
| 108 | + | |
| 109 | + | |
108 | 110 | | |
109 | 111 | | |
110 | | - | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
111 | 115 | | |
112 | 116 | | |
113 | 117 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
74 | 74 | | |
75 | 75 | | |
76 | 76 | | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
77 | 85 | | |
78 | 86 | | |
79 | 87 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
160 | 160 | | |
161 | 161 | | |
162 | 162 | | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
163 | 171 | | |
164 | 172 | | |
165 | 173 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
106 | 106 | | |
107 | 107 | | |
108 | 108 | | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
109 | 115 | | |
110 | 116 | | |
111 | 117 | | |
| |||
122 | 128 | | |
123 | 129 | | |
124 | 130 | | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
125 | 139 | | |
126 | 140 | | |
127 | 141 | | |
| |||
152 | 166 | | |
153 | 167 | | |
154 | 168 | | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
155 | 175 | | |
156 | 176 | | |
157 | 177 | | |
| |||
216 | 236 | | |
217 | 237 | | |
218 | 238 | | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
219 | 245 | | |
220 | 246 | | |
221 | 247 | | |
| |||
230 | 256 | | |
231 | 257 | | |
232 | 258 | | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
233 | 267 | | |
234 | 268 | | |
235 | 269 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
130 | 130 | | |
131 | 131 | | |
132 | 132 | | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
133 | 139 | | |
134 | 140 | | |
135 | 141 | | |
| |||
200 | 206 | | |
201 | 207 | | |
202 | 208 | | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
203 | 215 | | |
204 | 216 | | |
205 | 217 | | |
| |||
245 | 257 | | |
246 | 258 | | |
247 | 259 | | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
248 | 268 | | |
249 | 269 | | |
250 | 270 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
113 | 113 | | |
114 | 114 | | |
115 | 115 | | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
116 | 124 | | |
117 | 125 | | |
118 | 126 | | |
| |||
134 | 142 | | |
135 | 143 | | |
136 | 144 | | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
137 | 151 | | |
138 | 152 | | |
139 | 153 | | |
| |||
246 | 260 | | |
247 | 261 | | |
248 | 262 | | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
249 | 271 | | |
250 | 272 | | |
251 | 273 | | |
| |||
0 commit comments