Skip to content

Harden intent→result correlation and complete uninstall cleanup - #3

Merged
ilicfilip merged 1 commit into
mainfrom
harden-capture-and-uninstall
Jul 7, 2026
Merged

ilicfilip merged 1 commit into
mainfrom
harden-capture-and-uninstall

Conversation

@ilicfilip

Copy link
Copy Markdown
Collaborator

Two robustness fixes surfaced in a design review of the capture/enforcement path.

1. Correlation registry hardening (Capture\Gatekeeper)

The intent→result match is by recency because the core wp_ai_client_after_generate_result event carries no builder identity. A pending intent that never receives its result — a prompt that errored or was blocked after the intent was recorded — would otherwise linger as the "newest un-finalised" intent and steal the next genuine result: a permanent off-by-one that corrupts attribution/accounting for the rest of the request.

Two guards close this:

  • Blocked prompts discard their intent immediately in observe_prompt() (both the prior-$prevent path and a hard-limit block). No request runs, so no result will arrive for that intent.
  • match_pending() ignores aged-out intents older than a correlation window (MATCH_MAX_AGE_SECONDS, default 300s, filterable via wp_aiut_match_max_age). An abandoned/errored intent ages out and is left for the existing shutdown estimate sweep instead of mis-pairing a later real result.

New tests/gatekeeper-test.php covers recency matching, block-discard (both paths), and the age-out window.

2. Complete uninstall cleanup (uninstall.php)

Now drops the Phase 2 {prefix}aiut_limits table and deletes the autoloaded aiut_has_hard_limits fast-path option, so a fully opted-in uninstall leaves nothing behind. This gap was explicitly flagged in ADR-7.

Docs

  • ADR-2 (stale-intent hardening) and ADR-7 (uninstall) updated.
  • New wp_aiut_match_max_age filter documented in HOOKS.md.

Verification

php -l, composer check-cs (0 errors), composer phpstan (level 10, clean), and composer lint all pass locally. Test execution runs in CI (needs the MySQL + WP-test-lib setup).

🤖 Generated with Claude Code

Two robustness fixes surfaced in a design review.

1. Correlation registry (Capture\Gatekeeper). The intent→result match is by
   recency because the core result event carries no builder identity. A pending
   intent that never receives its result — a prompt that errored or was blocked
   after the intent was recorded — would otherwise linger as the "newest
   un-finalised" intent and steal the *next* genuine result, a permanent
   off-by-one for the rest of the request. Two guards:
   - A blocked prompt (prior filter OR a hard-limit block) now discards its
     intent immediately in observe_prompt(): no request runs, so no result will
     arrive for it.
   - match_pending() ignores intents older than a correlation window
     (MATCH_MAX_AGE_SECONDS, default 300s, filter `wp_aiut_match_max_age`), so an
     abandoned/errored intent ages out and is left for the shutdown estimate
     sweep instead of mis-pairing a later real result.
   New tests in tests/gatekeeper-test.php cover recency matching, block-discard
   (both paths), and the age-out window.

2. uninstall.php now drops the Phase 2 `{prefix}aiut_limits` table and deletes
   the autoloaded `aiut_has_hard_limits` fast-path option, so a fully opted-in
   uninstall leaves nothing behind (previously flagged in ADR-7).

Docs: ADR-2 (stale-intent hardening) and ADR-7 (uninstall) updated; the new
`wp_aiut_match_max_age` filter documented in HOOKS.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Signed-off-by: Filip Ilic <ilic.filip@gmail.com>
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

🧪 Test on WordPress Playground

Open this PR in WordPress Playground →

or download the plugin zip.

Note: this plugin needs WordPress 7.0+ (the AI Client API) and a configured
AI provider to capture real usage. In Playground you'll see the dashboard, the
empty state, and the Limits UI — but no live AI requests (no provider key).

@ilicfilip
ilicfilip merged commit 022f668 into main Jul 7, 2026
11 checks passed
@ilicfilip
ilicfilip deleted the harden-capture-and-uninstall branch July 7, 2026 11:42
ilicfilip added a commit that referenced this pull request Jul 7, 2026
…ng (#4)

Follow-up to the correlation + uninstall hardening (PR #3). Two docs still
described the pre-fix behaviour:

- ARCHITECTURE.md: note the two match_pending() recency guards (block-discard +
  age-out window / wp_aiut_match_max_age) in the timing section and the
  pre-request flow diagram.
- DATA-MODEL.md §11: uninstall now drops all three tables (incl. aiut_limits)
  and deletes every persisted option (incl. aiut_has_hard_limits). Removed the
  "known gap" callout (closed); demoted the aiut_alert_* transients to an honest
  footnote (self-expiring, intentionally not swept).

Signed-off-by: Filip Ilic <ilic.filip@gmail.com>
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