Files
wp-agentic-writer/docs/architecture/PLUGIN_AUDIT_RETRACE_2026-05-24.md

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.js and node -c assets/js/settings-v2.js passed.
  • 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 in includes/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 in includes/class-image-manager.php:478-483.
  • Keyword suggester calls $provider->chat() on the selection result in includes/class-keyword-suggester.php:32-84.
  • WP AI legacy wrapper calls $provider->chat(), $provider->chat_stream(), and $provider->get_name() on the selection result in includes/class-wp-ai-client-wrapper.php:225-252 and includes/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, and warnings where 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_content and $chunks_emitted by reference in includes/class-gutenberg-sidebar.php:1089-1109.
  • $chunks_emitted is compared at includes/class-gutenberg-sidebar.php:1111-1121.
  • $accumulated_content is used at includes/class-gutenberg-sidebar.php:1158-1164.
  • $last_user_message is stored into session messages at includes/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 $provider at includes/class-image-manager.php:478.
  • It calls $provider->generate_image() at includes/class-image-manager.php:483.

Impact:

  • /generate-image can 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_request with 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 in includes/class-cost-tracker.php:113-133.
  • Chat emits provider, session ID, and status in includes/class-gutenberg-sidebar.php:1011-1023 and includes/class-gutenberg-sidebar.php:1144-1156.

Impact:

  • Provider may be recorded, but session_id and explicit status are 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_id if 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_version in includes/class-conversation-migration.php:43-47.
  • wpaw_run_migrations() still reads wpaw_db_version in includes/class-conversation-migration.php:67-72.
  • Main table creation remains gated by wpaw_db_version < 1.1.0 in wp-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.0 but 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 on wpaw_conversations_db_version or direct table existence.
  • Unschedule wpaw_cleanup_old_sessions on 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-1053 and includes/class-gutenberg-sidebar.php:1167-1187.
  • /chat-history/{post_id} still reads legacy meta in includes/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 in includes/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_history after successful migration.
  • Call migration from Context_Service::get_context() when legacy meta exists.
  • Update handle_clear_context() to require edit_post and clear active session messages when sessionId is 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_config without edit_post in includes/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_post in includes/class-gutenberg-sidebar.php:4776-4835.
  • Image recommendation/generate/commit routes are registered with broad permissions at includes/class-gutenberg-sidebar.php:593-620 and handlers lack target post checks in includes/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-1029 and streaming complete event includes metadata in includes/class-gutenberg-sidebar.php:1190-1199.
  • Many other calls use $provider_result internally 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_model in wp-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, and includes/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_Registry and localize it to settings JS.
  • Stop writing execution_model for 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 TABLE adds provider/session/status in includes/class-cost-tracker.php:61-97.

Risk:

  • Fresh installs depend on Cost Tracker initialization to complete schema.
  • Failed DESCRIBE on a missing table is not handled before in_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.php deletes settings, _wpaw_plan, and cost table only in uninstall.php:12-21.

Still missing:

  • wpaw_conversations, wp_agentic_writer_custom_models, DB version options, transients, scheduled wpaw_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.debug is localized in includes/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, and assets/js/sidebar.js:6130-6157.
  • Settings JS logs model and cost debug info in assets/js/settings-v2.js:52-130 and assets/js/settings-v2.js:390-503.
  • Provider/backend error_log() calls remain in provider files and class-gutenberg-sidebar.php.

P2: Changelog Policy Still Fails

docs/DEFINITION_OF_DONE.md requires updating CHANGELOG.md, but no CHANGELOG.md file was found.

Do Before Chat/Context Focus

  1. Fix provider result contract callers in Image Manager, Keyword Suggester, and WP AI Client wrapper.
  2. Restore streaming chat variable initialization.
  3. Change Cost Tracker hook accepted args from 7 to 9 and add failure recording.
  4. Fix conversation migration versioning and cleanup cron scheduling.

Then Focus Chat/Context

  1. Make Context_Service::get_context() migrate legacy history on read.
  2. Delete or mark migrated _wpaw_chat_history.
  3. Make clear-context clear both post context and active session messages.
  4. Add a visible context inspector in the UI: active session, message count, linked post, focus keyword, language, provider/model, cost estimate.

Later

  1. Apply edit_post checks to every post-scoped route.
  2. Centralize model defaults in a registry.
  3. Consolidate uninstall/data retention.
  4. Gate logging.
  5. 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.