17 KiB
WP Agentic Writer Retrace Audit
Status: COMPLETE / SUPERSEDED
Completion marker date: 2026-05-24
Next retrace report: docs/architecture/PLUGIN_AUDIT_RETRACE_SECOND_PASS_2026-05-24.md
Audit date: 2026-05-24
Baseline retraced: docs/architecture/PLUGIN_AUDIT_FOLLOWUP_2026-05-24.md
Scope: current implementation after follow-up fixes, with emphasis on UI/UX, system boundaries, conversation context/history, cost tracker, provider/model routing, migrations, and data lifecycle.
Executive Summary
The follow-up implementation closed several important items, but the plugin is not yet clean enough to shift only into chat/context implementation. The highest-risk remaining problem is a new provider contract mismatch: WP_Agentic_Writer_Provider_Manager::get_provider_for_task() now returns a WPAW_Provider_Selection_Result, but several older classes still treat the return value as a provider and call ->chat() or ->generate_image() directly. That can fatal in image, keyword, and WP AI wrapper paths.
The second urgent issue is streaming chat state: stream_chat_request() currently references $accumulated_content, $chunks_emitted, and $last_user_message without initializing them. That can corrupt streaming persistence and produce warnings or missing session messages.
Good news: OpenRouter cache separation is now implemented, several conversation permissions were tightened, settings cost-log pagination was improved, and chat responses now include provider metadata. But there are still cross-cutting runtime, migration, cost-ledger, and authorization gaps.
Verification Performed
- PHP syntax check across plugin PHP files: passed.
- JS syntax check:
node -c assets/js/sidebar.jsandnode -c assets/js/settings-v2.jspassed. - Static trace of follow-up audit items against current code.
- No live WordPress browser workflow was run in this pass.
Follow-up Status Trace
| Follow-up item | Current status | Evidence |
|---|---|---|
| Split OpenRouter model cache keys | Fixed | Full objects use wpaw_openrouter_model_objects; IDs use wpaw_openrouter_model_ids in includes/class-openrouter-provider.php:106-264. |
| Add post/session auth for conversation list/create/link | Mostly fixed | edit_post added for post-linked conversation routes and link-post checks session access in includes/class-gutenberg-sidebar.php:7263-7555. |
| Add writing-state post auth | Fixed | edit_post checks and status allowlist added in includes/class-gutenberg-sidebar.php:823-887. |
| Provider fallback metadata for chat | Partially fixed | Provider result object and chat response metadata exist in includes/class-provider-manager.php:20-92 and includes/class-gutenberg-sidebar.php:987-1029, but helper classes still use the old API. |
| Cost log SQL pagination | Improved | Grouping/pagination moved into SQL in includes/class-settings-v2.php:645-667. |
| Conversation table migration versioning | Still open | Migration still checks wpaw_db_version, not wpaw_conversations_db_version, in includes/class-conversation-migration.php:67-72. |
| Single source of truth for chat messages | Partially fixed | Chat no longer writes new _wpaw_chat_history, but legacy methods/routes remain and migration does not delete migrated meta. |
| Cost tracker provider/session/status ledger | Partially fixed | New columns and parameters exist, but hook accepts only 7 args in includes/class-cost-tracker.php:48-53, so session/status are dropped. |
| Model registry/default unification | Still open | Defaults remain fragmented across activation, providers, settings PHP, JS, and WP AI wrapper. |
| Uninstall/data lifecycle | Still open | Main uninstall and uninstall.php remain inconsistent and incomplete. |
| Debug logging | Still open | Backend and frontend debug logs remain broad. |
Critical Findings
P0: Provider Manager Return Contract Breaks Older Callers
get_provider_for_task() now returns WPAW_Provider_Selection_Result, which is correct for provider transparency. But not every caller was updated to use $provider_result->provider.
Broken paths:
- Image placement analysis calls
$provider->chat()on the selection result inincludes/class-image-manager.php:218-219. - Image prompt generation does the same in
includes/class-image-manager.php:295-296. - Image variant generation calls
$provider->generate_image()on the selection result inincludes/class-image-manager.php:478-483. - Keyword suggester calls
$provider->chat()on the selection result inincludes/class-keyword-suggester.php:32-84. - WP AI legacy wrapper calls
$provider->chat(),$provider->chat_stream(), and$provider->get_name()on the selection result inincludes/class-wp-ai-client-wrapper.php:225-252andincludes/class-wp-ai-client-wrapper.php:272-280.
Impact:
- Image generation, image prompt analysis, keyword suggestions, and WP AI fallback paths can fatal with "Call to undefined method WPAW_Provider_Selection_Result::chat()".
- This is a classic fix-A-break-B regression from changing a shared service contract.
Recommended fix:
- Update all callers to:
$provider_result = WP_Agentic_Writer_Provider_Manager::get_provider_for_task( ... );$provider = $provider_result->provider;- propagate
$provider_result->actual_provider,fallback_used, andwarningswhere returned or tracked.
- Consider adding
__call()only as a temporary compatibility shim if too many callers exist, but explicit updates are safer.
P0: Streaming Chat Uses Uninitialized State
stream_chat_request() references variables that are never initialized in the current implementation.
Evidence:
- Closure captures
$accumulated_contentand$chunks_emittedby reference inincludes/class-gutenberg-sidebar.php:1089-1109. $chunks_emittedis compared atincludes/class-gutenberg-sidebar.php:1111-1121.$accumulated_contentis used atincludes/class-gutenberg-sidebar.php:1158-1164.$last_user_messageis stored into session messages atincludes/class-gutenberg-sidebar.php:1169-1177, but is never assigned inside the method.
Impact:
- Streaming chat can emit warnings/notices and fail to persist the user message correctly.
- Session history may save an empty or undefined user message while the assistant message is stored.
- This directly affects the chat/context path the team wants to focus on.
Recommended fix:
- Restore initializers at the top of
stream_chat_request():$accumulated_content = '';$chunks_emitted = 0;$last_user_message = $this->get_last_user_message( $messages );$total_cost = 0;
P0: Image Generation Is Broken By Provider Contract Mismatch
Image generation was already a sensitive flow because it touches model routing, temporary files, cost, and DB state. The provider result contract now breaks it before generation.
Evidence:
generate_image_variants()assigns provider result to$provideratincludes/class-image-manager.php:478.- It calls
$provider->generate_image()atincludes/class-image-manager.php:483.
Impact:
/generate-imagecan fail at runtime even though PHP syntax passes.- Image generation costs still will not reliably reach the main cost ledger because generation is interrupted before any ledger call.
Recommended fix:
- Unwrap provider result and track image generation through
wp_aw_after_api_requestwith provider/session/status metadata.
High Priority Findings
P1: Cost Tracker Hook Drops Session and Status Metadata
The Cost Tracker now accepts provider, session ID, and status, but the hook registration only allows 7 arguments.
Evidence:
- Hook registration accepts 7 args in
includes/class-cost-tracker.php:48-53. add_request()signature expects 9 args inincludes/class-cost-tracker.php:113-133.- Chat emits provider, session ID, and status in
includes/class-gutenberg-sidebar.php:1011-1023andincludes/class-gutenberg-sidebar.php:1144-1156.
Impact:
- Provider may be recorded, but
session_idand explicitstatusare silently dropped. - Failed calls still are not intentionally recorded.
- Cost records cannot be reconciled to conversation context.
Recommended fix:
- Change hook registration to
add_action( 'wp_aw_after_api_request', array( $this, 'add_request' ), 10, 9 );. - Add explicit failure ledger entries around provider errors.
- Add
error_code/request_idif this is meant to be a true ledger.
P1: Conversation Migration Is Still Version-fragile
The follow-up audit recommended conversation-specific migration versioning, but the migration runner still checks the main DB version.
Evidence:
- Conversation table creation stores
wpaw_conversations_db_versioninincludes/class-conversation-migration.php:43-47. wpaw_run_migrations()still readswpaw_db_versioninincludes/class-conversation-migration.php:67-72.- Main table creation remains gated by
wpaw_db_version < 1.1.0inwp-agentic-writer.php:223-241. - Conversation cleanup cron is scheduled at include time in
includes/class-conversation-migration.php:94-99.
Impact:
- Existing installs with
wpaw_db_version=1.1.0but no conversation table can still be skipped. - Cleanup cron can run against a missing table.
Recommended fix:
- Run an idempotent
wpaw_ensure_conversations_table()based onwpaw_conversations_db_versionor direct table existence. - Unschedule
wpaw_cleanup_old_sessionson deactivation.
P1: Chat History Is Better, But Legacy Read/Migration Still Keeps Two Truths Alive
New chat writes no longer call update_post_chat_history(), which is good. But legacy post-meta read/write helpers and migration behavior still keep _wpaw_chat_history alive.
Evidence:
- New chat comments say legacy meta is deprecated in
includes/class-gutenberg-sidebar.php:1031-1053andincludes/class-gutenberg-sidebar.php:1167-1187. /chat-history/{post_id}still reads legacy meta inincludes/class-gutenberg-sidebar.php:1240-1256.- Legacy update helper still exists and writes meta in
includes/class-gutenberg-sidebar.php:1268-1301. - Context Service
get_context()does not migrate on read inincludes/class-context-service.php:62-87. - Migration still leaves legacy meta in place in
includes/class-context-service.php:299-300.
Impact:
- Legacy history can still be surfaced, migrated more than once, or diverge from the session table.
- Clear context does not clear an active session unless another caller invokes
Context_Service::clear_context()with a session ID.
Recommended fix:
- Make
/chat-history/{post_id}migration-only or remove it after frontend no longer needs it. - Delete or mark
_wpaw_chat_historyafter successful migration. - Call migration from
Context_Service::get_context()when legacy meta exists. - Update
handle_clear_context()to requireedit_postand clear active session messages whensessionIdis present.
P1: Post-scoped Authorization Remains Incomplete Outside Conversation Routes
Conversation routes improved, but other post-scoped routes still trust the broad edit_posts permission callback.
Examples:
- Post config get/update read and write
_wpaw_post_configwithoutedit_postinincludes/class-gutenberg-sidebar.php:1722-1756. - Cost tracking for a post lacks a target post check in
includes/class-gutenberg-sidebar.php:3623-3629. - Section block mapping reads/writes post meta without
edit_postinincludes/class-gutenberg-sidebar.php:4776-4835. - Image recommendation/generate/commit routes are registered with broad permissions at
includes/class-gutenberg-sidebar.php:593-620and handlers lack target post checks inincludes/class-gutenberg-sidebar.php:6457-6520.
Impact:
- Multi-author sites can still leak or mutate post-scoped state across users.
- This is larger than conversations and should be fixed as a shared helper.
Recommended fix:
- Add a helper like
require_edit_post_or_error( $post_id, $action ). - Apply it to every route that reads or writes post meta, cost, image state, SEO/GEO state, or generated content.
Medium Priority Findings
P2: Provider Metadata Is Not Yet Consistent Across All AI Responses
Chat responses now include provider metadata, but planning/writing/refinement/clarity helper responses often only track cost internally and do not return provider/warning metadata to the frontend.
Evidence:
- Chat adds metadata in
includes/class-gutenberg-sidebar.php:1025-1029and streaming complete event includes metadata inincludes/class-gutenberg-sidebar.php:1190-1199. - Many other calls use
$provider_resultinternally but return older response shapes.
Opportunity:
- Standardize an AI response envelope for all REST/SSE flows:
content,provider,selected_provider,model,fallback_used,warnings,cost,usage,request_id.
P2: Model Defaults Are Still Fragmented
No single model registry exists yet. Defaults remain spread across activation, OpenRouter provider, settings V2, legacy settings, JS presets, Image Manager, and WP AI wrapper.
Evidence:
- Activation still stores
execution_modelinwp-agentic-writer.php:140-142. - OpenRouter defaults differ across properties in
includes/class-openrouter-provider.php:34-69. - Settings V2 still has multiple fallback groups in
includes/class-settings-v2.php:105-111,includes/class-settings-v2.php:228-273, andincludes/class-settings-v2.php:989-994. - JS presets remain in
assets/js/settings-v2.js:24-38. - Image Manager has its own writing/image fallbacks in
includes/class-image-manager.php:183-248.
Recommendation:
- Add a PHP
Model_Registryand localize it to settings JS. - Stop writing
execution_modelfor fresh installs.
P2: Cost Table Schema Is Split Between Creation and Runtime ALTER
The runtime upgrader adds columns, but the base table creation in the main plugin file still creates the old schema.
Evidence:
- Base schema lacks provider/session/status in
wp-agentic-writer.php:198-209. - Runtime
ALTER TABLEadds provider/session/status inincludes/class-cost-tracker.php:61-97.
Risk:
- Fresh installs depend on Cost Tracker initialization to complete schema.
- Failed
DESCRIBEon a missing table is not handled beforein_array()checks.
Recommendation:
- Move the latest schema into
wp_agentic_writer_create_cost_table(). - Make runtime migrations table-existence safe and versioned.
P2: Uninstall Is Still Incomplete And Duplicated
The main uninstall hook and uninstall.php remain inconsistent.
Evidence:
- Main uninstall deletes settings and some tables in
wp-agentic-writer.php:269-294. uninstall.phpdeletes settings,_wpaw_plan, and cost table only inuninstall.php:12-21.
Still missing:
wpaw_conversations,wp_agentic_writer_custom_models, DB version options, transients, scheduledwpaw_cleanup_old_sessions,_wpaw_chat_history,_wpaw_memory,_wpaw_post_config, writing-state meta, user preferences, and image post meta.
P2: Debug Logging Remains Too Broad
There is a localized debug flag, but console logs and backend logs remain noisy.
Evidence:
wpAgenticWriter.debugis localized inincludes/class-gutenberg-sidebar.php:257.- Frontend still logs migration/session/clarity details in
assets/js/sidebar.js:308-322,assets/js/sidebar.js:5619-5630, andassets/js/sidebar.js:6130-6157. - Settings JS logs model and cost debug info in
assets/js/settings-v2.js:52-130andassets/js/settings-v2.js:390-503. - Provider/backend
error_log()calls remain in provider files andclass-gutenberg-sidebar.php.
P2: Changelog Policy Still Fails
docs/DEFINITION_OF_DONE.md requires updating CHANGELOG.md, but no CHANGELOG.md file was found.
Recommended Next Work Queue
Do Before Chat/Context Focus
- Fix provider result contract callers in Image Manager, Keyword Suggester, and WP AI Client wrapper.
- Restore streaming chat variable initialization.
- Change Cost Tracker hook accepted args from 7 to 9 and add failure recording.
- Fix conversation migration versioning and cleanup cron scheduling.
Then Focus Chat/Context
- Make
Context_Service::get_context()migrate legacy history on read. - Delete or mark migrated
_wpaw_chat_history. - Make clear-context clear both post context and active session messages.
- Add a visible context inspector in the UI: active session, message count, linked post, focus keyword, language, provider/model, cost estimate.
Later
- Apply
edit_postchecks to every post-scoped route. - Centralize model defaults in a registry.
- Consolidate uninstall/data retention.
- Gate logging.
- Add
CHANGELOG.md.
Conclusion
Do not switch solely to chat/context yet. The implementation is closer, but the provider contract mismatch and streaming-chat uninitialized state should be fixed first because they directly break runtime behavior and chat persistence. After those are resolved, chat/context is the right next focus.