From 3efa112a6062496581ea41535f265bcc987a6dd4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 08:37:50 +0000 Subject: [PATCH 1/2] fix(platform-objects): enable_two_factor and copy_invitation_link are human-only identity actions Both enable_two_factor actions drop their ADR-0011 ai block (E to H), and copy_invitation_link is assessed H with no ai block. ADR-0011's identity addendum records the amended lists (H 23 / C 17 / E 19, 36 ai blocks) and notes sys_approval_request's 9 actions as unassessed and human-only. Claude-Session: https://claude.ai/code/session_01QUs3BrhuPB4AGHyXk3rvfh Co-authored-by: Claude --- .../27900-identity-actions-ai-exposure.md | 6 ++-- ...ntity-two-factor-invite-link-human-only.md | 13 ++++++++ docs/adr/0011-actions-as-ai-tools.md | 32 +++++++++++-------- .../src/identity/sys-invitation.object.ts | 2 ++ .../src/identity/sys-two-factor.object.ts | 8 ++--- .../src/identity/sys-user.object.ts | 8 ++--- 6 files changed, 41 insertions(+), 28 deletions(-) create mode 100644 .changeset/28368-identity-two-factor-invite-link-human-only.md diff --git a/.changeset/27900-identity-actions-ai-exposure.md b/.changeset/27900-identity-actions-ai-exposure.md index a911146a55b..2e10be049d5 100644 --- a/.changeset/27900-identity-actions-ai-exposure.md +++ b/.changeset/27900-identity-actions-ai-exposure.md @@ -7,10 +7,10 @@ feat(platform-objects): the platform identity actions an agent may reach declare 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. diff --git a/.changeset/28368-identity-two-factor-invite-link-human-only.md b/.changeset/28368-identity-two-factor-invite-link-human-only.md new file mode 100644 index 00000000000..d2b30e2b359 --- /dev/null +++ b/.changeset/28368-identity-two-factor-invite-link-human-only.md @@ -0,0 +1,13 @@ +--- +'@objectstack/platform-objects': minor +--- + +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: narrowed +Skills-docs: none — no file under `skills/**` or `content/docs/**` names `enable_two_factor` or `copy_invitation_link` as agent-exposed; `content/docs/ai/connect-mcp.mdx` already says acts that change a sign-in factor declare nothing and are not listed, which this change makes true for both `enable_two_factor` actions. + +`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. diff --git a/docs/adr/0011-actions-as-ai-tools.md b/docs/adr/0011-actions-as-ai-tools.md index e88e2387167..ae5c1c0d18b 100644 --- a/docs/adr/0011-actions-as-ai-tools.md +++ b/docs/adr/0011-actions-as-ai-tools.md @@ -324,7 +324,12 @@ 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` · @@ -332,7 +337,8 @@ three buckets, and the declarations were written in the same change as this adde `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` · @@ -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 @@ -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 diff --git a/packages/platform-objects/src/identity/sys-invitation.object.ts b/packages/platform-objects/src/identity/sys-invitation.object.ts index 90c10b6ea6b..c039b8cd630 100644 --- a/packages/platform-objects/src/identity/sys-invitation.object.ts +++ b/packages/platform-objects/src/identity/sys-invitation.object.ts @@ -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) ──────────────────── diff --git a/packages/platform-objects/src/identity/sys-two-factor.object.ts b/packages/platform-objects/src/identity/sys-two-factor.object.ts index 48a034f1828..fceb27b7130 100644 --- a/packages/platform-objects/src/identity/sys-two-factor.object.ts +++ b/packages/platform-objects/src/identity/sys-two-factor.object.ts @@ -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', diff --git a/packages/platform-objects/src/identity/sys-user.object.ts b/packages/platform-objects/src/identity/sys-user.object.ts index 43fcb3adf7d..dc6761f210b 100644 --- a/packages/platform-objects/src/identity/sys-user.object.ts +++ b/packages/platform-objects/src/identity/sys-user.object.ts @@ -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', From cbd231941209c68ff5881ba988b77222e7b5976c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 08:54:13 +0000 Subject: [PATCH 2/2] chore(changeset): declare the 28368 change Metadata-protocol none, patch Claude-Session: https://claude.ai/code/session_01QUs3BrhuPB4AGHyXk3rvfh Co-authored-by: Claude --- .../28368-identity-two-factor-invite-link-human-only.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.changeset/28368-identity-two-factor-invite-link-human-only.md b/.changeset/28368-identity-two-factor-invite-link-human-only.md index d2b30e2b359..e02440a0832 100644 --- a/.changeset/28368-identity-two-factor-invite-link-human-only.md +++ b/.changeset/28368-identity-two-factor-invite-link-human-only.md @@ -1,12 +1,11 @@ --- -'@objectstack/platform-objects': minor +'@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: narrowed -Skills-docs: none — no file under `skills/**` or `content/docs/**` names `enable_two_factor` or `copy_invitation_link` as agent-exposed; `content/docs/ai/connect-mcp.mdx` already says acts that change a sign-in factor declare nothing and are not listed, which this change makes true for both `enable_two_factor` actions. +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.