Repository navigation
Harden intent→result correlation and complete uninstall cleanup - #3
Merged
Merged
Conversation
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>
🧪 Test on WordPress PlaygroundOpen this PR in WordPress Playground →
|
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_resultevent 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:
observe_prompt()(both the prior-$preventpath 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 viawp_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.phpcovers recency matching, block-discard (both paths), and the age-out window.2. Complete uninstall cleanup (
uninstall.php)Now drops the Phase 2
{prefix}aiut_limitstable and deletes the autoloadedaiut_has_hard_limitsfast-path option, so a fully opted-in uninstall leaves nothing behind. This gap was explicitly flagged in ADR-7.Docs
wp_aiut_match_max_agefilter documented inHOOKS.md.Verification
php -l,composer check-cs(0 errors),composer phpstan(level 10, clean), andcomposer lintall pass locally. Test execution runs in CI (needs the MySQL + WP-test-lib setup).🤖 Generated with Claude Code