# WP Agentic Writer Eighth Retrace Audit Status: COMPLETE / RETRACED Completion marker: 2026-05-26 Follow-up report: `docs/architecture/PLUGIN_AUDIT_RETRACE_NINTH_PASS_2026-05-26.md` Audit date: 2026-05-25 Baseline retraced: `docs/architecture/PLUGIN_AUDIT_RETRACE_SEVENTH_PASS_2026-05-25.md` Scope: eighth pass after seventh-retrace implementation, covering conversation history migration, provider transparency, cost tracking, model defaults, UI/UX, and release readiness. ## Executive Summary The seventh-pass implementation made real progress: - `WP_Agentic_Writer_Cost_Tracker::add_request()` now defaults unknown providers to `unknown` instead of `openrouter`. - The previously sampled successful AI cost hooks now pass provider/session/status metadata. - Previously missing provider metadata was added to more backend responses, including execution, regeneration, multi-pass refinement, and article refinement. - The legacy `/chat-history` backend path now attempts to migrate/read conversation-backed data instead of always returning raw `_wpaw_chat_history`. - PHP and JavaScript syntax checks pass. However, the plugin is still not audit-clean. The most important remaining issue is conversation continuity: the sidebar still depends on the deprecated `/chat-history` route, and that route now appears internally inconsistent because it reads `_wpaw_active_session_id`, but no writer for that meta key was found. That can make legacy chat migration look successful in storage while the sidebar receives an empty history. ## Verification Performed - PHP syntax check across plugin PHP files: passed. - `node -c assets/js/sidebar.js`: passed. - `node -c assets/js/settings-v2.js`: passed. - `node -c assets/js/sidebar-utils.js`: passed. - Static retrace of seventh-pass findings against current code. - Static sweep of chat-history migration, provider metadata, cost hook metadata, failed AI paths, sidebar provider UI, and model defaults. - No live WordPress editor/browser workflow was run in this pass. ## Seventh-Pass Status Trace | Seventh-pass item | Current status | Evidence | |---|---:|---| | Sidebar dependency on `/chat-history` | Still open | `assets/js/sidebar.js:644-668` still fetches `/chat-history/${postId}`. | | Backend `/chat-history` compatibility | Partially fixed, has a new continuity bug | `get_post_chat_history()` migrates/reads sessions, but depends on `_wpaw_active_session_id` at `includes/class-gutenberg-sidebar.php:1359-1389`; no writer for that meta key was found. | | Cost hook default provider | Fixed | `add_request()` now defaults provider to `unknown` at `includes/class-cost-tracker.php:120-124`. | | Successful AI cost hook metadata | Improved | Previously sampled routes now pass provider/session/status, for example execution at `includes/class-gutenberg-sidebar.php:3269-3281`, regeneration at `includes/class-gutenberg-sidebar.php:3748-3760`, summarize at `includes/class-gutenberg-sidebar.php:6438-6450`, and article refinement at `includes/class-gutenberg-sidebar.php:7005-7017`. | | Provider metadata on backend responses | Improved but inconsistent | More routes include `provider_metadata`, but chat still uses top-level fields and the sidebar does not render either shape. | | Failed AI cost attempts | Still open | Several `is_wp_error()` branches still return without recording an error-status attempt. | | Model registry/default unification | Still open | No central model registry found; defaults remain duplicated. | | WordPress editor browser pass | Still open | Syntax checks passed, but no editor workflow was verified. | ## Remaining Findings ### P1: Deprecated Chat-History Compatibility Can Return Empty Data After Migration The sidebar still loads chat history through the deprecated route: - `assets/js/sidebar.js:644-668` fetches `${wpAgenticWriter.apiUrl}/chat-history/${postId}` and seeds `messages` from the response. The backend route now tries to be smarter: - `handle_get_chat_history()` calls `get_post_chat_history()` at `includes/class-gutenberg-sidebar.php:1300-1326`. - `get_post_chat_history()` checks `_wpaw_chat_history_migrated`, then tries to read `_wpaw_active_session_id` and load context from that id at `includes/class-gutenberg-sidebar.php:1359-1368`. - If legacy history exists and is not migrated, it calls `migrate_legacy_chat_history()` at `includes/class-gutenberg-sidebar.php:1378-1380`. - After migration, it again tries `_wpaw_active_session_id` at `includes/class-gutenberg-sidebar.php:1382-1389`. The problem: a search found no writer for `_wpaw_active_session_id`; it appears only in this read path. `migrate_legacy_chat_history()` returns the migrated session id at `includes/class-context-service.php:272-324`, but `get_post_chat_history()` ignores that return value. Impact: - A legacy post can be migrated into the conversation table and have `_wpaw_chat_history` deleted, but `/chat-history` can still return an empty array. - Because the sidebar still depends on `/chat-history`, users can perceive this as "chat history disappeared" even though the session data exists. - This is exactly the kind of context/history regression that keeps the audit loop alive. Recommended fix: - Best: stop using `/chat-history` in the sidebar and hydrate from the canonical conversation/session context endpoint. - If the compatibility endpoint remains, capture the return value from `migrate_legacy_chat_history()` and load that session directly. - For already migrated posts, use `WP_Agentic_Writer_Conversation_Manager::get_sessions_for_post( $post_id )` instead of `_wpaw_active_session_id`, or write `_wpaw_active_session_id` consistently when sessions are created/selected. - Add a regression test for a legacy post with `_wpaw_chat_history` and no `_wpaw_active_session_id`; expected result: `/chat-history` returns migrated messages and the canonical session id. ### P1: Provider Transparency Is Still Not A Single End-To-End Contract The backend now has a shared metadata helper: - `build_provider_metadata()` returns `provider`, `selected_provider`, `fallback_used`, `warnings`, and `model` at `includes/class-gutenberg-sidebar.php:956-963`. More routes now include provider metadata: - Plan generation at `includes/class-gutenberg-sidebar.php:2049-2058`. - Execution at `includes/class-gutenberg-sidebar.php:3283-3292`. - Regeneration at `includes/class-gutenberg-sidebar.php:3762-3770`. - Meta description at `includes/class-gutenberg-sidebar.php:6276-6285`. - Multi-pass refinement at `includes/class-gutenberg-sidebar.php:6930-6939`. - Article refinement at `includes/class-gutenberg-sidebar.php:7019-7028`. But the contract is still not end to end: - Chat still returns top-level `provider`, `selected_provider`, `fallback_used`, and `warnings` at `includes/class-gutenberg-sidebar.php:1067-1071`, while other routes use nested `provider_metadata`. - `assets/js/sidebar.js` does not reference `provider_metadata`, `fallback_used`, `selected_provider`, or `warnings`; its only provider references are web-search availability checks at `assets/js/sidebar.js:5983-6000`. Impact: - API consumers still need two response shapes. - Users still do not see actual provider/fallback behavior in the editor. - The Definition of Done says UI must show actual provider used, but that is still not implemented. Recommended fix: - Choose one response shape and apply it to all AI endpoints. If keeping `provider_metadata`, also make chat use that envelope. - Update sidebar state/rendering to show actual provider, model, fallback, and warnings near cost/status feedback. - Add a static check that every provider-backed route returns the same metadata shape. ### P2: Failed AI Calls Still Usually Do Not Record Error-Status Cost Attempts Successful cost tracking improved substantially, but failed attempts are still mostly invisible: - Clarity fallback returns cost `0` without recording a failed provider attempt at `includes/class-gutenberg-sidebar.php:4100-4110`. - Clarity JSON parse fallback returns cost `0` without an error-status record at `includes/class-gutenberg-sidebar.php:4117-4132`. - Regeneration returns `regeneration_error` without cost/error tracking at `includes/class-gutenberg-sidebar.php:3738-3746`. - Multi-pass refinement returns the provider error directly at `includes/class-gutenberg-sidebar.php:6910-6914`. - Article refinement returns the provider error directly at `includes/class-gutenberg-sidebar.php:6996-7000`. Impact: - Provider reliability and failure rates are undercounted. - Users may see a failure, but admins do not get a durable cost/attempt trail explaining which provider/model failed. - Fallback behavior is harder to audit because failures and successes are not represented consistently. Recommended fix: - Add one helper, for example `track_ai_cost( $post_id, $response, $action, $provider_result, $session_id = '', $status = 'success' )`. - Call it for both success and failure paths, with `status = 'error'`, `cost = 0`, and available provider/model data for failures. - Keep user-facing errors unchanged, but always record the attempt where a provider request was actually made. ### P2: Cost Tracking Is Better But Still Not Structurally Guarded The direct default-provider bug was fixed: - `add_request()` now defaults provider to `unknown` at `includes/class-cost-tracker.php:124`. The previous high-risk success hooks sampled in this pass now include provider/session/status. That is good. The remaining structural issue is that cost tracking is still done through repeated raw `do_action( 'wp_aw_after_api_request', ... )` calls across the route class. Impact: - New AI routes can still accidentally omit provider/session/status, or skip failed-attempt tracking. - The system relies on manual discipline instead of a reusable contract. Recommended fix: - Wrap the hook with a local helper and use that helper everywhere. - Add a simple static test or lint script that rejects direct `do_action( 'wp_aw_after_api_request'` outside the helper. ### P2: Model Defaults Are Still Fragmented No central model registry was found. Defaults remain spread across: - Activation defaults in `wp-agentic-writer.php:140-142`. - Sidebar defaults in `includes/class-gutenberg-sidebar.php:278-283`. - Settings defaults and fallbacks in `includes/class-settings.php` and `includes/class-settings-v2.php`. - OpenRouter provider defaults in `includes/class-openrouter-provider.php:34-69`. - JavaScript presets in `assets/js/settings-v2.js:35-56`. - Wrapper fallback model groups in `includes/class-wp-ai-client-wrapper.php:94-100`. - Image manager fallbacks in `includes/class-image-manager.php:185-249`. Impact: - Defaults can drift between activation, runtime provider behavior, settings UI, cost estimation, and JS presets. - Model-related fixes remain easy to regress. Recommended fix: - Create one PHP model registry for task defaults, labels, capabilities, provider support, pricing hints, and deprecation status. - Localize JS presets from that registry. - Add a consistency check that activation/settings/provider defaults match the registry. ### P2: Editor UI/UX Still Needs Browser Verification Syntax checks are clean, but no live editor workflow was run. Impact: - The remaining issues are heavily UI-dependent: chat hydration, session continuity, provider warning display, streaming completion, and cost feedback. - Static checks cannot validate editor package compatibility, REST nonce behavior, layout, or user-visible provider/cost states. Recommended fix: - Run a WordPress editor browser pass after the next implementation pass. - Verify sidebar open/persist, chat reload continuity, plan/write/refine cost updates, provider warning display, and unauthorized post failures. ## Recommended Next Work 1. Remove the sidebar dependency on `/chat-history`, or repair the compatibility route by using the migrated session id directly. 2. Add frontend rendering for provider/model/fallback/warnings. 3. Normalize provider metadata to one response shape across chat and non-chat AI actions. 4. Add failed-attempt cost tracking with `status = 'error'`. 5. Wrap cost tracking in a helper and stop using raw hook calls directly. 6. Consolidate model defaults into a registry. 7. Run the WordPress editor browser workflow pass. ## Current Verdict The seventh-pass implementation is partially proper and materially better than the previous state. Successful cost attribution and backend provider metadata coverage improved. The plugin is still not audit-clean. The highest-priority remaining fix is chat/context continuity: the active sidebar history loader still depends on a deprecated route, and that route can return empty results after migration because it relies on `_wpaw_active_session_id` without any discovered writer.