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

14 KiB

WP Agentic Writer Second Retrace Audit

Status: COMPLETE / SUPERSEDED
Completion marker date: 2026-05-24
Next retrace report: docs/architecture/PLUGIN_AUDIT_RETRACE_THIRD_PASS_2026-05-24.md

Audit date: 2026-05-24
Baseline retraced: docs/architecture/PLUGIN_AUDIT_RETRACE_2026-05-24.md
Scope: second pass after retrace implementation, covering UI/UX, editor runtime, system boundaries, conversation context/history, cost tracker, provider/model routing, migrations, data lifecycle, and release readiness.

Executive Summary

Several of the previous retrace findings were implemented correctly. The provider selection result is now unwrapped in the image, keyword, and WP AI legacy provider paths. Streaming chat state variables are initialized again. The cost table base schema and hook argument count were expanded. Uninstall cleanup is also much more complete, and a changelog now exists.

However, the plugin is not ready to move only into chat/context feature work yet. Two release blockers remain:

  1. assets/js/sidebar.js currently does not parse. The attempted debug-logger conversion damaged string literals and logger calls, so the Gutenberg sidebar can fail before any UI renders.
  2. includes/class-wp-ai-client-wrapper.php still calls WP_Agentic_Writer_Cost_Tracker::record_usage(), but that method does not exist. Successful WP AI Client text generation or legacy wrapper tracking can fatal.

After those are fixed, the next biggest risks are still the conversation migration/version gate, legacy context source-of-truth behavior, and post-scoped authorization gaps outside the conversation routes.

Verification Performed

  • PHP syntax check across plugin PHP files: passed.
  • node -c assets/js/settings-v2.js: passed.
  • node -c assets/js/sidebar.js: failed at line 17.
  • Static trace of prior retrace recommendations against the current code.
  • No live WordPress browser workflow was run in this pass because the sidebar JavaScript parse failure blocks meaningful UI verification.

Retrace Implementation Status

Area Current status Evidence
Provider selection result callers Fixed for previously named helper paths Image manager, keyword suggester, and WP AI legacy wrapper now use $provider_result->provider.
Streaming chat variables Fixed stream_chat_request() initializes accumulated content, chunk count, total cost, and last user message before streaming.
Cost hook expanded args Fixed includes/class-cost-tracker.php:48-53 registers add_request with 9 accepted args.
Cost base schema Improved wp-agentic-writer.php:198-216 now creates session_id, provider, and status columns and indexes.
Uninstall/data cleanup Improved Main uninstall removes settings, custom models, tables, transients, user meta, post meta, scheduled events, and temp images.
Changelog Added but currently inaccurate CHANGELOG.md says sidebar logging was fixed, but assets/js/sidebar.js now fails syntax validation.
Conversation migration versioning Still open Conversation migration still checks wpaw_db_version instead of wpaw_conversations_db_version.
Context/history source of truth Still partial Legacy migration is manual, get_context() does not migrate on read, and clear-context route only deletes post meta.
Post-scoped REST authorization Still partial Several post-id routes rely on generic edit_posts route permission and do not check edit_post for the target post.
Model registry/default unification Still open Defaults and model lists remain spread across activation, settings PHP, settings JS, providers, and wrapper classes.

Critical Findings

P0: Gutenberg Sidebar JavaScript Does Not Parse

The sidebar bundle currently fails before runtime:

  • node -c assets/js/sidebar.js fails with SyntaxError: Invalid or unexpected token.
  • The first parse error is at assets/js/sidebar.js:17, where the logger calls wpawLog.log(', ...args);.
  • assets/js/sidebar.js:18 has the same pattern for errors.
  • Many later converted calls lost opening quotes or brackets, for example wpawLog.error( Failed to load session for post:', error);, wpawLog.log( Found legacy chat history, triggering migration...');, and wpawLog.warn([WPAW] No questions returned from clarity check!);.

Impact:

  • The editor sidebar can fail to load entirely.
  • Chat, context migration prompts, cost UI refresh, plan generation, article generation, and image UI are all unreachable if the script is enqueued.
  • This also invalidates the changelog claim that the sidebar debug logging conversion is complete.

Recommended fix:

  • Restore the logger definition to call console.log, console.error, console.info, and console.warn.
  • Repair every malformed wpawLog.* call in assets/js/sidebar.js.
  • Prefer a mechanical but syntax-aware conversion from console.* to wpawLog.*, not a plain text replacement.
  • Gate completion on node -c assets/js/sidebar.js plus a browser load of the Gutenberg sidebar.

P0: WP AI Wrapper Still Calls Missing Cost Tracker Method

includes/class-wp-ai-client-wrapper.php still calls a method that does not exist:

  • Core AI text path calls WP_Agentic_Writer_Cost_Tracker::get_instance()->record_usage() at includes/class-wp-ai-client-wrapper.php:197-202.
  • Legacy text path calls the same missing method at includes/class-wp-ai-client-wrapper.php:249-254.
  • Repository search found no record_usage() implementation in WP_Agentic_Writer_Cost_Tracker.

Impact:

  • If WP AI Client text generation succeeds and cost tracking class is loaded, the request can fatal after the model returns.
  • If the legacy wrapper succeeds and tries to track cost, it can fatal.
  • This is a cross-path cost-tracker regression because most of the plugin uses do_action( 'wp_aw_after_api_request', ... ), but this wrapper uses a different contract.

Recommended fix:

  • Either add a record_usage() compatibility method to WP_Agentic_Writer_Cost_Tracker, or convert both wrapper call sites to do_action( 'wp_aw_after_api_request', ... ).
  • The compatibility method is safer if external or older internal code might call it.
  • Include model, action/task type, input/output token estimates, cost, provider, session id, and status where available.

High Priority Findings

P1: Conversation Migration Still Uses the Main DB Version Gate

The conversation migration stores a dedicated option but does not consistently read it:

  • wpaw_create_conversations_table() stores wpaw_conversations_db_version at includes/class-conversation-migration.php:43-47.
  • wpaw_run_migrations() still reads wpaw_db_version at includes/class-conversation-migration.php:67-72.
  • Main table creation only runs when wpaw_db_version < 1.1.0 in wp-agentic-writer.php:230-249.

Impact:

  • Sites where the main plugin DB version is already current can skip conversation table repair or creation.
  • A failed partial migration can leave the plugin thinking the main schema is current while conversation storage is missing or stale.
  • Chat/history work remains risky until table creation is idempotent per table/version, not only per global plugin version.

Recommended fix:

  • Make conversation table creation check wpaw_conversations_db_version.
  • In wp_agentic_writer_maybe_create_tables(), either always call idempotent table creators or use per-table schema versions.
  • Update wpaw_drop_conversations_table() to delete wpaw_conversations_db_version, not wpaw_db_version.

P1: Clear Context and Legacy Migration Still Do Not Respect the New Source of Truth

The code still has split context behavior:

  • WP_Agentic_Writer_Context_Service::get_context() returns an empty context when no session exists and does not try legacy migration on read.
  • migrate_legacy_chat_history() keeps _wpaw_chat_history after migration at includes/class-context-service.php:299-300.
  • handle_clear_context() only deletes _wpaw_memory and _wpaw_chat_history at includes/class-gutenberg-sidebar.php:1216-1236; it does not clear the active conversation session.
  • The context service has a better clear_context( $session_id, $post_id ) method, but the REST handler does not use it.

Impact:

  • Users can clear visible legacy meta while the active session still keeps messages.
  • Legacy meta can be re-migrated later, causing duplicate or surprising history.
  • The system still has two competing history stores, which is the core reason A/B regressions keep recurring around chat.

Recommended fix:

  • Pass sessionId through the clear-context endpoint and call WP_Agentic_Writer_Context_Service::clear_context().
  • Decide migration policy: either delete legacy _wpaw_chat_history after successful migration or mark it migrated with a durable meta flag.
  • Add migrate-on-read for post-linked sessions so old posts behave consistently.

P1: Post-Scoped REST Authorization Is Still Incomplete Outside Conversation Routes

The conversation routes received targeted post authorization, but older post-id endpoints still depend mostly on generic edit_posts permission:

  • Route registration uses check_permissions, which returns current_user_can( 'edit_posts' ) at includes/class-gutenberg-sidebar.php:931.
  • handle_get_post_config() and handle_update_post_config() do not check edit_post for the target post at includes/class-gutenberg-sidebar.php:1728-1760.
  • handle_get_cost_tracking() does not check target post access at includes/class-gutenberg-sidebar.php:3629-3635.
  • handle_save_section_blocks() and handle_get_section_blocks() do not check target post access at includes/class-gutenberg-sidebar.php:4782-4842.
  • Image recommendation/generation/commit handlers do not check target post access at includes/class-gutenberg-sidebar.php:6463-6525.

Impact:

  • An authenticated author/editor with broad edit_posts can potentially read or mutate plugin data for posts they cannot edit.
  • Cost history, post config, image recommendations, image variants, and section mappings can leak or be modified across post boundaries.

Recommended fix:

  • Every REST handler accepting post_id or postId should validate current_user_can( 'edit_post', $post_id ) before read or write.
  • Centralize this in helper methods so future endpoints inherit the same rule.
  • Include negative permission tests for another user's private/draft post.

Medium Priority Findings

P2: Cost Tracker Migration Still Assumes the Table Exists

WP_Agentic_Writer_Cost_Tracker::maybe_upgrade_table() runs DESCRIBE {$table_name} and immediately uses in_array() on the result. If the table is missing, $wpdb->get_col() can return an unexpected value and the method does not create the table.

Impact:

  • Sites with a missing cost table but a current wpaw_db_version may not self-heal.
  • The first cost tracker access can produce warnings or fail to record usage.

Recommended fix:

  • Detect missing table with SHOW TABLES LIKE before DESCRIBE.
  • If missing, call wp_agentic_writer_create_cost_table() or move schema creation into the cost tracker class.
  • Make the table creator idempotent and callable regardless of global plugin DB version.

P2: Provider Metadata Is Still Not a Uniform Response Contract

Chat responses now include provider metadata, but non-chat endpoints still often return only content or variants. As provider fallback expands, users and support logs need a consistent answer to "which provider/model actually served this?".

Recommended fix:

  • Standardize response metadata for generated text, image analysis, image variants, keyword suggestions, and plan/edit endpoints.
  • Include provider, fallback_used, warnings, model, and cost where available.
  • Mirror the same metadata into cost records.

P2: Model Defaults and Registry Remain Fragmented

Model defaults still appear in multiple layers:

  • Activation defaults in wp-agentic-writer.php.
  • Settings presets in assets/js/settings-v2.js.
  • Settings PHP model transforms and saved options.
  • Provider defaults in OpenRouter/local/WP AI wrapper classes.
  • Image model fallback logic in the image manager.

Impact:

  • A model replacement can fix one UI path while leaving old defaults in runtime paths.
  • Cost estimation and provider selection may disagree with the model shown in settings.

Recommended fix:

  • Create a single model registry/default resolver in PHP.
  • Localize the resolved defaults to JS instead of duplicating presets.
  • Give each task type one canonical key: chat, clarity, planning, writing, refinement, image.

P2: Deactivation Still Leaves the Conversation Cleanup Event Scheduled

Uninstall now clears wpaw_cleanup_old_sessions, but deactivation only clears wpaw_cleanup_temp_images in wp-agentic-writer.php:260-266.

Impact:

  • A deactivated plugin can leave scheduled cleanup hooks behind until uninstall.

Recommended fix:

  • Add wp_clear_scheduled_hook( 'wpaw_cleanup_old_sessions' ) to deactivation.
  • Prefer scheduling cron events on activation or migration, not at include time.

Definition of Done Gates for This Pass

Before considering the retrace implementation complete, require:

  • node -c assets/js/sidebar.js passes.
  • node -c assets/js/settings-v2.js passes.
  • Full PHP syntax check passes.
  • No references to undefined methods such as record_usage() remain, unless the method is intentionally added.
  • Conversation table creation is controlled by wpaw_conversations_db_version or always-idempotent table checks.
  • Clear-context clears the active session and legacy meta in one path.
  • Every post-scoped endpoint checks edit_post for its target post.
  • Changelog entries match verified behavior.
  1. Fix assets/js/sidebar.js syntax and logger conversion, then browser-test the sidebar.
  2. Fix the missing record_usage() contract or convert the WP AI wrapper to the existing cost hook.
  3. Fix conversation table versioning and table self-healing.
  4. Align clear-context and legacy migration around the conversations table as the source of truth.
  5. Add post-scoped authorization checks for config, cost, section-block, and image routes.
  6. Then continue into deeper chat/context implementation.

Current Decision

Do not move fully into new chat/context feature work yet. The sidebar parse failure and missing cost tracker method should be fixed first because they can block or fatal the existing editor workflow. Once those P0s are closed, chat/context can become the main focus with fewer regressions.