Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .changeset/27900-identity-actions-ai-exposure.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
---
'@objectstack/platform-objects': patch
---
Expand All @@ -7,10 +7,10 @@
Clause-②: no
Metadata-protocol: none

The `sys_*` identity objects' actions now carry the ADR-0011 `ai` block by risk, as ADR-0011's 2026-10-30 addendum records (58 actions assessed, 38 declared):
The `sys_*` identity objects' actions now carry the ADR-0011 `ai` block by risk, as ADR-0011's 2026-10-30 addendum records (59 actions assessed, 36 declared):

- **Exposed (21).** Additive or self-scoped acts declare `ai: { exposed: true, description }`: the four `invite_user` mirrors, resending and accepting an invitation, adding a member to an organization or a team, creating, editing and renaming a team or an organization, switching the active organization, re-enabling an OAuth application, enabling two-factor authentication, updating your own profile, resending the verification email, and requesting SSO domain verification.
- **Exposed (19).** Additive or self-scoped acts declare `ai: { exposed: true, description }`: the four `invite_user` mirrors, resending and accepting an invitation, adding a member to an organization or a team, creating, editing and renaming a team or an organization, switching the active organization, re-enabling an OAuth application, updating your own profile, resending the verification email, and requesting SSO domain verification.
- **Exposed, confirmation required (17).** Destructive or privilege-changing lifecycle acts also declare `requiresConfirmation: true`, so MCP `run_action` refuses them `428 ACTION_CONFIRMATION_REQUIRED`, with nothing run, until the call carries `confirm: true`: banning, unbanning and unlocking a user, setting a user's manager, revoking an API key or a session, signing out other devices, cancelling or declining an invitation, removing a member or a team member, deleting a team, an OAuth application or an SSO provider, disabling an OAuth application, leaving an organization, and unlinking a sign-in provider.
- **Human-only (20).** Acts that mint a credential or a session, change ownership, a role or a sign-in factor, or destroy an account or an organization (impersonating a user, setting a password, creating a user or an API key, transferring ownership, changing a member's role, deleting an organization, and the rest the addendum names) declare no `ai` block and are not listed over MCP. `sys_invitation.copy_invitation_link` declares none either; it was not part of the assessment.
- **Human-only (23).** Acts that mint a credential or a session, change ownership, a role or a sign-in factor, or destroy an account or an organization (impersonating a user, setting a password, enabling two-factor authentication, creating a user or an API key, copying an invitation's accept link, transferring ownership, changing a member's role, deleting an organization, and the rest the addendum names) declare no `ai` block and are not listed over MCP.

Over MCP these actions are listed and run behind every existing gate: the capability gate, the installation's action switch, the param contract, the confirmation gate and the identity route's own authorization. They are `type: 'api'` actions forwarded to identity routes that accept a signed-in session only, so an agent connected over OAuth sees them listed and is refused at the route when it runs one.
12 changes: 12 additions & 0 deletions .changeset/28368-identity-two-factor-invite-link-human-only.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@objectstack/platform-objects': patch
---

fix(platform-objects): enabling two-factor authentication and copying an invitation's accept link are human-only — an agent cannot reach them over MCP

Clause-②: no
Metadata-protocol: none

`sys_user.enable_two_factor` and `sys_two_factor.enable_two_factor` no longer declare an ADR-0011 `ai` block. Each takes the user's current password and answers a new sign-in factor (the authenticator URI and one-time backup codes), so an MCP agent no longer sees them in `list_actions` and `run_action` refuses them by name. `sys_invitation.copy_invitation_link`, which answers the invitation's accept link (a bearer credential), is assessed human-only and keeps declaring no `ai` block. A person signed in to the Console runs all three exactly as before.

ADR-0011's identity-actions addendum records the amended lists (human-only 23, exposed with confirmation 17, exposed 19: 36 `ai` blocks over 59 actions) and notes that `sys_approval_request`'s 9 actions are unassessed and human-only until assessed.
32 changes: 19 additions & 13 deletions docs/adr/0011-actions-as-ai-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,15 +324,21 @@ three buckets, and the declarations were written in the same change as this adde
| **C** exposed, confirmed | a destructive or privilege-changing lifecycle act that is reversible or bounded | `ai: { exposed: true, description, requiresConfirmation: true }`. ⛔ The confirmation is never relaxed. |
| **E** exposed | an additive or self-scoped act with no credential and no privilege escalation | `ai: { exposed: true, description }`; confirmation is the author's choice (none is declared today). |

**H — human-only (20):** `sys_user.impersonate_user` · `sys_user.set_user_password` ·
> Amended (2026-10-31, ruling of record `6540309323` on #27900, the maintainer 「其他同意」): both
> `enable_two_factor` actions move from E to H, since each takes the user's password and answers a new
> sign-in factor, and `sys_invitation.copy_invitation_link` is assessed H, since it answers the
> invitation's accept link, a bearer credential. The lists below are the amended record.

**H — human-only (23):** `sys_user.impersonate_user` · `sys_user.set_user_password` ·
`sys_user.create_user` · `sys_user.delete_my_account` · `sys_user.change_my_password` ·
`sys_user.change_my_email` · `sys_user.disable_two_factor` · `sys_user.generate_backup_codes` ·
`sys_two_factor.disable_two_factor` · `sys_two_factor.regenerate_backup_codes` ·
`sys_api_key.create_api_key` · `sys_api_key.restore_api_key` ·
`sys_oauth_application.create_oauth_application` · `sys_oauth_application.rotate_client_secret` ·
`sys_sso_provider.register_sso_provider` · `sys_sso_provider.register_saml_provider` ·
`sys_sso_provider.verify_domain` · `sys_member.transfer_ownership` · `sys_member.update_member_role` ·
`sys_organization.delete_organization`.
`sys_organization.delete_organization` · `sys_user.enable_two_factor` ·
`sys_two_factor.enable_two_factor` · `sys_invitation.copy_invitation_link`.

**C — exposed, confirmed (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` ·
Expand All @@ -342,22 +348,22 @@ three buckets, and the declarations were written in the same change as this adde
`sys_oauth_application.delete_oauth_application` · `sys_sso_provider.delete_sso_provider` ·
`sys_organization.leave_organization` · `sys_account.unlink_account`.

**E — exposed (21):** `sys_user.invite_user` · `sys_invitation.invite_user` ·
**E — exposed (19):** `sys_user.invite_user` · `sys_invitation.invite_user` ·
`sys_organization.invite_user` · `sys_member.invite_user` · `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_oauth_application.enable_oauth_application` · `sys_user.update_my_profile` ·
`sys_user.resend_verification_email` · `sys_sso_provider.request_domain_verification`.

That is 58 actions and 38 `ai` blocks. The card's bucket headers read H 19 and E 22; its name lists,
which the ruling adopted, hold 20 and 21, and the names are the record.
That is 59 actions and 36 `ai` blocks (H 23 / C 17 / E 19). The 2026-10-30 assessment covered 58
actions in 38 blocks: the card's bucket headers read H 19 and E 22, its name lists, which the ruling
adopted, held 20 and 21, and the names are the record. `sys_invitation.copy_invitation_link`, added
after that assessment, and the two `enable_two_factor` moves came with the 2026-10-31 amendment.

**Not assessed.** `sys_invitation.copy_invitation_link` was added after the assessment (the link-only
invitation delivery). It declares no `ai` block, which is this ADR's default, and it is not assessed
until a maintainer ruling names it.
**Not assessed.** `sys_approval_request`'s 9 actions (`plugin-approvals`) are outside this assessment:
they are unassessed and human-only, with no `ai` block, until a maintainer ruling assesses them.

**No automatic check.** The maintainer struck the pin test the assessment proposed:
「自动检查 有必要吗?每一个元数据应用都有类似的问题,后续都要检查吗」. This table lives only here; a reviewer
Expand All @@ -368,14 +374,14 @@ activation switch.
**How the declarations reach an agent.** Over MCP, a `sys_*` action is listed and run only when the
action itself declares `ai.exposed: true` — ADR-0066 D4's 2026-10-30 note records that mechanism
(`packages/runtime/src/action-execution.ts#systemObjectActionExposureError`). As of this addendum the
38 E and C actions above are the platform `sys_*` actions that declare it. Every gate after admission
36 E and C actions above are the platform `sys_*` actions that declare it. Every gate after admission
still runs: ADR-0066 D4's capability gate, the ADR-0126 §8 activation switch, the
`ai.requiresConfirmation` gate (a C action is refused `428 ACTION_CONFIRMATION_REQUIRED`, nothing run,
until the call carries `confirm: true`; `packages/runtime/src/action-execution.ts#actionConfirmationRefusal`),
and the target's own authorization.

**Declared is not yet runnable for an OAuth-connected agent.** All 38 are `type: 'api'` actions that
forward to an identity route: 37 to the better-auth routes under `/api/v1/auth/`, and
**Declared is not yet runnable for an OAuth-connected agent.** All 36 are `type: 'api'` actions that
forward to an identity route: 35 to the better-auth routes under `/api/v1/auth/`, and
`sys_api_key.revoke_api_key` to the REST data route. Those targets accept a signed-in session, and the
forward of an OAuth-connected agent's call is answered `401` there. Which credentials a forward
target accepts is not decided here; it is the open decision on #28178. Until that lands, an
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ export const SysInvitation = ObjectSchema.create({
fields: [{ path: 'acceptUrl', label: 'Invite link' }],
acknowledge: 'Done',
},
// ADR-0011 identity-actions note, bucket H: human-only, no `ai` block.
// It answers the accept link, a bearer credential.
},

// ── Recipient-side actions (the invited user) ────────────────────
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,8 @@ export const SysTwoFactor = ObjectSchema.create({
{ path: 'backupCodes', label: 'Backup Codes', format: 'code-list' },
],
},
// ADR-0011 identity-actions note, bucket E: exposed by plain declaration.
ai: {
exposed: true,
description:
'Turn on two-factor authentication for the caller\'s own account (param: the current password). Answers the authenticator URI and one-time backup codes; the user must save the codes, which are shown only once.',
},
// ADR-0011 identity-actions note, bucket H: human-only, no `ai` block.
// It takes the user's password and answers a new sign-in factor.
},
{
name: 'disable_two_factor',
Expand Down
8 changes: 2 additions & 6 deletions packages/platform-objects/src/identity/sys-user.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -733,12 +733,8 @@ export const SysUser = ObjectSchema.create({
{ path: 'backupCodes', label: 'Backup Codes', format: 'code-list' },
],
},
// ADR-0011 identity-actions note, bucket E: exposed by plain declaration.
ai: {
exposed: true,
description:
'Turn on two-factor authentication for the caller\'s own account (param: the current password). Answers the authenticator URI and one-time backup codes; the user must save the codes, which are shown only once.',
},
// ADR-0011 identity-actions note, bucket H: human-only, no `ai` block.
// It takes the user's password and answers a new sign-in factor.
},
{
name: 'disable_two_factor',
Expand Down
Loading