You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
DM's `ResolvePendingActionAbility` exposes `POST /datamachine/v1/actions/resolve` for resolving pending actions, gated by capability checks. That works for in-app review flows where the resolver is an authenticated WordPress user.
It does not work for review flows where the approval lives outside the WordPress session — the canonical example being email approval, where a user clicks "Approve" in an inbox link without any active WordPress login. Today, anyone wanting that pattern would have to bolt on a custom token system on top of DM's REST endpoint.
This is generic enough that the signed-URL primitive belongs in DM core, reusable by any consumer (Studio's socials approval, Wire moderation, future destructive ops, etc.) without each one inventing its own token scheme.
Proposal
A new ability — call it `datamachine/sign-pending-action-resolution` — that produces a pair of signed URLs for a given pending action:
Approve URL — when visited, resolves the action with `accepted` decision.
Reject URL — when visited, resolves the action with `rejected` decision.
URLs carry an HMAC token (server-side secret in `wp_options`, rotated periodically) over a payload of `{ action_id, decision, expires_at, nonce }`. They're stateless — no server-side token table, no extra DB rows. Token validity is short — default 7 days, configurable per-call up to a hard ceiling (`MAX_TOKEN_LIFETIME`, maybe 30 days).
Public surface
Ability input:
```php
array(
'action_id' => 'pa_abc123', // required
'lifetime' => 604800, // optional, seconds, default 7 days
'resolver' => 'email_approval', // optional, recorded on resolution
)
```
A new public REST route `GET /datamachine/v1/actions/resolve-by-token?t={token}` that:
Validates the HMAC.
Validates the token hasn't expired.
Loads the pending action; bails 410 Gone if already resolved or expired.
Calls the existing resolution path (same as `/actions/resolve`).
Renders a confirmation page (HTML for browser, JSON for clients with `Accept: application/json`).
Confirmation page is intentionally minimal — DM doesn't need to ship a fancy UI. Consumers wanting custom confirmation can hook a filter to inject branded HTML.
Replay protection
Each token is single-use because the resolution itself is idempotent — once the action is resolved, the second click on either link returns the existing decision rather than overwriting it.
Confirmation UI customization beyond a simple filter hook.
Per-user identity binding. The resolver is recorded as whatever string the caller passed (e.g. `email_approval`); if a consumer needs to bind to a specific WP user, they can include that in the action's metadata at `store()` time.
Why DM, not agents-api
Signed URLs presume HTTP, WordPress nonces / HMAC keys, a REST endpoint, and a URL scheme. agents-api stays transport-agnostic — it should not care if approval comes from REST, CLI, MCP, or a Discord button. The resolver contract upstream already accepts decisions from any source. Signed URLs are a DM-specific transport adapter on top of that contract.
Acceptance
New ability `datamachine/sign-pending-action-resolution` returning approve/reject URLs.
New REST route `GET /datamachine/v1/actions/resolve-by-token` that resolves by valid token.
HMAC secret stored in `wp_options`, auto-generated on first use, rotation helper for security incidents.
Problem
DM's `ResolvePendingActionAbility` exposes `POST /datamachine/v1/actions/resolve` for resolving pending actions, gated by capability checks. That works for in-app review flows where the resolver is an authenticated WordPress user.
It does not work for review flows where the approval lives outside the WordPress session — the canonical example being email approval, where a user clicks "Approve" in an inbox link without any active WordPress login. Today, anyone wanting that pattern would have to bolt on a custom token system on top of DM's REST endpoint.
This is generic enough that the signed-URL primitive belongs in DM core, reusable by any consumer (Studio's socials approval, Wire moderation, future destructive ops, etc.) without each one inventing its own token scheme.
Proposal
A new ability — call it `datamachine/sign-pending-action-resolution` — that produces a pair of signed URLs for a given pending action:
URLs carry an HMAC token (server-side secret in `wp_options`, rotated periodically) over a payload of `{ action_id, decision, expires_at, nonce }`. They're stateless — no server-side token table, no extra DB rows. Token validity is short — default 7 days, configurable per-call up to a hard ceiling (`MAX_TOKEN_LIFETIME`, maybe 30 days).
Public surface
Ability input:
```php
array(
'action_id' => 'pa_abc123', // required
'lifetime' => 604800, // optional, seconds, default 7 days
'resolver' => 'email_approval', // optional, recorded on resolution
)
```
Ability output:
```php
array(
'approve_url' => 'https://.../resolve?t=...',
'reject_url' => 'https://.../resolve?t=...',
'expires_at' => '2026-05-17T12:00:00Z',
)
```
Resolution endpoint
A new public REST route `GET /datamachine/v1/actions/resolve-by-token?t={token}` that:
Confirmation page is intentionally minimal — DM doesn't need to ship a fancy UI. Consumers wanting custom confirmation can hook a filter to inject branded HTML.
Replay protection
Each token is single-use because the resolution itself is idempotent — once the action is resolved, the second click on either link returns the existing decision rather than overwriting it.
Out of scope
Why DM, not agents-api
Signed URLs presume HTTP, WordPress nonces / HMAC keys, a REST endpoint, and a URL scheme. agents-api stays transport-agnostic — it should not care if approval comes from REST, CLI, MCP, or a Discord button. The resolver contract upstream already accepts decisions from any source. Signed URLs are a DM-specific transport adapter on top of that contract.
Acceptance
Depends on / unblocks