Skip to content

feat: write-excluding safe default ceiling for autonomous executions (Stage A)#413

Merged
chubes4 merged 1 commit into
Automattic:mainfrom
chubes4:ceiling-autonomous-default
Jul 6, 2026
Merged

feat: write-excluding safe default ceiling for autonomous executions (Stage A)#413
chubes4 merged 1 commit into
Automattic:mainfrom
chubes4:ceiling-autonomous-default

Conversation

@chubes4

@chubes4 chubes4 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Part of #412 (Stage A).

What

Gives autonomous execution principals a safe default capability ceiling that excludes content-mutating WordPress capabilities, unless the host has already made an explicit grant. This is the derivation + ceiling-model half of #412; it does not touch src/Tools/ enforcement, which another change owns (Stage B).

Why

WP_Agent_WordPress_Authorization_Policy already composes a WP_Agent_Capability_Ceiling with user_can(), but today a ceiling is only attached when a caller (token authenticator, host) explicitly supplies one. An autonomous run that arrives with no ceiling is effectively unrestricted by the ceiling layer. Stage A closes that gap at the derivation layer: there is now a substrate helper that produces a conservative default for autonomous runs, expressed generically and filterably so Core/hosts stay in control.

How

Autonomy is derived from principal fields, not consumer modes

WP_Agent_Autonomous_Capability_Policy::is_autonomous() treats an execution as autonomous when its auth_source is system, runtime, or agent_token — i.e. no human is directly authorizing actions in real time. The substrate never references consumer orchestration vocabulary; the set is purely a property of the principal.

  • agents_api_autonomous_auth_sources — adjust which auth sources count as autonomous.
  • agents_api_principal_is_autonomous — per-principal override (e.g. a host can mark a custom audience principal autonomous, or declassify one).

Safe default excludes content-mutating capabilities

WP_Agent_Autonomous_Capability_Policy::DEFAULT_DENIED_CAPABILITIES is a principled, conservative set: post/page authoring at every lifecycle stage, taxonomy and options administration, media uploads, unfiltered markup, and plugin/theme/user management. Read access and other non-mutating capabilities are left unrestricted.

  • agents_api_autonomous_denied_capabilities — adjust the denied set.

Additive deny-list on the ceiling value object

WP_Agent_Capability_Ceiling gains an optional denied_capabilities deny-list (appended after metadata to preserve positional compatibility). allows_capability() consults the deny-list first, so a denied capability is never allowed even when the allow-list is unrestricted — the natural way to express "everything except these dangerous ones" without enumerating an infinite WordPress capability vocabulary. from_array/to_array/with_denied_capabilities/with_allowed_capabilities all carry and preserve it.

Composition (never widens, never overrides an explicit grant)

WP_Agent_Autonomous_Capability_Policy::resolve_ceiling( $principal ):

  1. If the principal carries an explicit host ceiling with any capability shaping (allow-list or deny-list), it is returned unchanged. The safe default never narrows or overrides a deliberate host decision — including an explicit write grant, which a host can use to opt an autonomous run into writing.
  2. If the principal is autonomous and has no explicit grant, it receives the safe default (unrestricted allow-list + the denied write caps).
  3. Otherwise (interactive principal, no grant) the ceiling is returned untouched — null/unrestricted as the host supplied.

Convenience accessor

WP_Agent_Execution_Principal::is_autonomous_execution() delegates to the policy for ergonomic identity checks.

Verification

New tests/autonomous-capability-ceiling-smoke.php (76 assertions) covers: deny-list precedence and composition with the allow-list; with_denied_capabilities/with_allowed_capabilities/from_array/to_array round-trips; autonomous system/agent_token/runtime principals get the write-excluding default while keeping read access; interactive user/application_password principals are unaffected; explicit host grants are returned unchanged by identity (including an explicit write grant to an autonomous principal, and an unrestricted ceiling that still triggers the safe default); the documented default denied set; and all three filters (agents_api_autonomous_auth_sources, agents_api_principal_is_autonomous, agents_api_autonomous_denied_capabilities).

  • composer smoke — green (full suite, including the layer-purity no-product-imports-smoke check).
  • composer phpstan (level max) — green.

Layer purity

No consumer orchestration vocabulary, product names, or mode strings enter the substrate. Autonomy is a property of the principal's auth_source; the default capability set and the autonomous auth-source set are both filterable. No changes to src/Tools/ (Stage B territory).

Out of scope (deliberately)

Wiring resolve_ceiling() into tool execution / the authorization policy's can() path is Stage B. This PR ships the derivation mechanism and the ceiling-model enhancement, proven by smoke tests that drive resolve_ceiling() directly.

…ecutions

Part of Automattic#412 (Stage A). Adds WP_Agent_Autonomous_Capability_Policy, a substrate helper that derives a filterable, write-excluding capability ceiling for autonomous execution principals (system/runtime/agent_token) when the host has not made an explicit grant. Introduces an additive denied_capabilities deny-list on WP_Agent_Capability_Ceiling that composes with the existing allow-list (deny takes precedence) without widening prior restrictions, and an is_autonomous_execution() accessor on the principal.
@chubes4 chubes4 merged commit dc0e1be into Automattic:main Jul 6, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant