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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ wp_register_agent(
- `AgentsAPI\AI\Approvals\WP_Agent_Pending_Action`
- `AgentsAPI\AI\Approvals\WP_Agent_Pending_Action_Status`
- `AgentsAPI\AI\Approvals\WP_Agent_Pending_Action_Store`
- `AgentsAPI\AI\Approvals\WP_Agent_Pending_Action_Observer`
- `AgentsAPI\AI\Approvals\WP_Agent_Pending_Action_Resolver`
- `AgentsAPI\AI\Approvals\WP_Agent_Pending_Action_Handler`
- `AgentsAPI\AI\Context\WP_Agent_Context_Authority_Tier`
Expand Down
1 change: 1 addition & 0 deletions agents-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
require_once AGENTS_API_PATH . 'src/Approvals/class-wp-agent-pending-action-status.php';
require_once AGENTS_API_PATH . 'src/Approvals/class-wp-agent-pending-action.php';
require_once AGENTS_API_PATH . 'src/Approvals/class-wp-agent-approval-decision.php';
require_once AGENTS_API_PATH . 'src/Approvals/class-wp-agent-pending-action-observer.php';
require_once AGENTS_API_PATH . 'src/Approvals/class-wp-agent-pending-action-handler.php';
require_once AGENTS_API_PATH . 'src/Approvals/class-wp-agent-pending-action-resolver.php';
require_once AGENTS_API_PATH . 'src/Approvals/register-pending-action-abilities.php';
Expand Down
5 changes: 4 additions & 1 deletion docs/channels-workflows-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,13 @@ Approval primitives live in `src/Approvals/`:
- `WP_Agent_Pending_Action_Status`: pending/accepted/rejected/expired/deleted vocabulary.
- `WP_Agent_Approval_Decision`: resolver decision value object.
- `WP_Agent_Pending_Action_Store`: durable queue/audit interface.
- `WP_Agent_Pending_Action_Observer`: lifecycle observer contract for stores that emit stored/resolved/expired events.
- `WP_Agent_Pending_Action_Resolver`: accept/reject resolution contract.
- `WP_Agent_Pending_Action_Handler`: product handler contract for permission checks and applying/rejecting proposals.

Consumers own the database tables, REST routes, admin/chat UI, queues, product-specific apply/reject handlers, and authorization ceilings for transcript and approval materialization.
Store implementations own observer registration and invocation. Observers should tolerate duplicate lifecycle calls and avoid throwing; stores should defensively catch observer failures so notification, logging, or metrics observers cannot break durable approval state transitions.

Consumers own the database tables, REST routes, admin/chat UI, queues, product-specific apply/reject handlers, event adapters, and authorization ceilings for transcript and approval materialization.

## Operational failure behavior

Expand Down
43 changes: 43 additions & 0 deletions src/Approvals/class-wp-agent-pending-action-observer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php
/**
* Pending Action Observer Interface
*
* Generic lifecycle observer contract for pending action stores. Concrete store
* implementations choose how observers are registered and invoked.
*
* @package AgentsAPI
* @since next
*/

namespace AgentsAPI\AI\Approvals;

defined( 'ABSPATH' ) || exit;

interface WP_Agent_Pending_Action_Observer {

/**
* Called after a pending action has been stored successfully.
*
* @param WP_Agent_Pending_Action $action Durable pending action record.
* @return void
*/
public function on_stored( WP_Agent_Pending_Action $action ): void;

/**
* Called after a pending action has been resolved.
*
* @param WP_Agent_Pending_Action $action Durable pending action record.
* @param WP_Agent_Approval_Decision $decision Accepted/rejected decision.
* @param string $resolver Resolver identifier, such as a user, token, or service actor.
* @return void
*/
public function on_resolved( WP_Agent_Pending_Action $action, WP_Agent_Approval_Decision $decision, string $resolver ): void;

/**
* Called after a pending action expires without resolution.
*
* @param WP_Agent_Pending_Action $action Durable pending action record.
* @return void
*/
public function on_expired( WP_Agent_Pending_Action $action ): void;
}
1 change: 1 addition & 0 deletions tests/bootstrap-smoke.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
agents_api_smoke_assert_equals( true, class_exists( 'AgentsAPI\\AI\\WP_Agent_Markdown_Section_Compaction_Adapter' ), 'AgentsAPI\\AI\\WP_Agent_Markdown_Section_Compaction_Adapter contract is available', $failures, $passes );
agents_api_smoke_assert_equals( true, class_exists( 'AgentsAPI\\AI\\WP_Agent_Conversation_Loop' ), 'WP_Agent_Conversation_Loop facade is available', $failures, $passes );
agents_api_smoke_assert_equals( true, interface_exists( 'AgentsAPI\\AI\\Approvals\\WP_Agent_Pending_Action_Store' ), 'AgentsAPI\\AI\\Approvals\\WP_Agent_Pending_Action_Store contract is available', $failures, $passes );
agents_api_smoke_assert_equals( true, interface_exists( 'AgentsAPI\\AI\\Approvals\\WP_Agent_Pending_Action_Observer' ), 'AgentsAPI\\AI\\Approvals\\WP_Agent_Pending_Action_Observer contract is available', $failures, $passes );
agents_api_smoke_assert_equals( true, class_exists( 'AgentsAPI\\AI\\Approvals\\WP_Agent_Pending_Action_Status' ), 'AgentsAPI\\AI\\Approvals\\WP_Agent_Pending_Action_Status vocabulary is available', $failures, $passes );
agents_api_smoke_assert_equals( true, interface_exists( 'WP_Agent_Consent_Policy' ), 'WP_Agent_Consent_Policy contract is available', $failures, $passes );
agents_api_smoke_assert_equals( true, class_exists( 'WP_Agent_Default_Consent_Policy' ), 'WP_Agent_Default_Consent_Policy implementation is available', $failures, $passes );
Expand Down
25 changes: 25 additions & 0 deletions tests/pending-action-store-contract-smoke.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

echo "\n[1] Pending action store contract is available without a concrete backend:\n";
agents_api_smoke_assert_equals( true, interface_exists( 'AgentsAPI\\AI\\Approvals\\WP_Agent_Pending_Action_Store' ), 'pending action store interface is available', $failures, $passes );
agents_api_smoke_assert_equals( true, interface_exists( 'AgentsAPI\\AI\\Approvals\\WP_Agent_Pending_Action_Observer' ), 'pending action observer interface is available', $failures, $passes );

$reflection = new ReflectionClass( 'AgentsAPI\\AI\\Approvals\\WP_Agent_Pending_Action_Store' );
$methods = array();
Expand Down Expand Up @@ -62,4 +63,28 @@
agents_api_smoke_assert_equals( array( 'action_id' ), array_map( static fn( ReflectionParameter $parameter ): string => $parameter->getName(), $delete_parameters ), 'delete accepts action ID only', $failures, $passes );
agents_api_smoke_assert_equals( 'string', (string) $delete_parameters[0]->getType(), 'delete action ID is string', $failures, $passes );

$observer_reflection = new ReflectionClass( 'AgentsAPI\\AI\\Approvals\\WP_Agent_Pending_Action_Observer' );
$observer_methods = array();
foreach ( $observer_reflection->getMethods() as $method ) {
$observer_methods[ $method->getName() ] = $method;
}

echo "\n[3] Pending action observer exposes stored, resolved, and expired lifecycle hooks:\n";
agents_api_smoke_assert_equals( array( 'on_stored', 'on_resolved', 'on_expired' ), array_keys( $observer_methods ), 'observer exposes lifecycle methods', $failures, $passes );
agents_api_smoke_assert_equals( 'void', (string) $observer_methods['on_stored']->getReturnType(), 'on_stored returns void', $failures, $passes );
agents_api_smoke_assert_equals( 'void', (string) $observer_methods['on_resolved']->getReturnType(), 'on_resolved returns void', $failures, $passes );
agents_api_smoke_assert_equals( 'void', (string) $observer_methods['on_expired']->getReturnType(), 'on_expired returns void', $failures, $passes );

$stored_parameters = $observer_methods['on_stored']->getParameters();
$resolved_parameters = $observer_methods['on_resolved']->getParameters();
$expired_parameters = $observer_methods['on_expired']->getParameters();
agents_api_smoke_assert_equals( array( 'action' ), array_map( static fn( ReflectionParameter $parameter ): string => $parameter->getName(), $stored_parameters ), 'on_stored accepts action', $failures, $passes );
agents_api_smoke_assert_equals( 'AgentsAPI\\AI\\Approvals\\WP_Agent_Pending_Action', (string) $stored_parameters[0]->getType(), 'on_stored action is pending action', $failures, $passes );
agents_api_smoke_assert_equals( array( 'action', 'decision', 'resolver' ), array_map( static fn( ReflectionParameter $parameter ): string => $parameter->getName(), $resolved_parameters ), 'on_resolved accepts action, decision, and resolver', $failures, $passes );
agents_api_smoke_assert_equals( 'AgentsAPI\\AI\\Approvals\\WP_Agent_Pending_Action', (string) $resolved_parameters[0]->getType(), 'on_resolved action is pending action', $failures, $passes );
agents_api_smoke_assert_equals( 'AgentsAPI\\AI\\Approvals\\WP_Agent_Approval_Decision', (string) $resolved_parameters[1]->getType(), 'on_resolved decision is approval decision', $failures, $passes );
agents_api_smoke_assert_equals( 'string', (string) $resolved_parameters[2]->getType(), 'on_resolved resolver is string', $failures, $passes );
agents_api_smoke_assert_equals( array( 'action' ), array_map( static fn( ReflectionParameter $parameter ): string => $parameter->getName(), $expired_parameters ), 'on_expired accepts action', $failures, $passes );
agents_api_smoke_assert_equals( 'AgentsAPI\\AI\\Approvals\\WP_Agent_Pending_Action', (string) $expired_parameters[0]->getType(), 'on_expired action is pending action', $failures, $passes );

agents_api_smoke_finish( 'Agents API pending action store contract', $failures, $passes );
Loading