feat: consolidate docs, backend/session infra, and settings updates

This commit is contained in:
Dwindi Ramadhana
2026-05-28 00:58:20 +07:00
parent 2424acf726
commit 44e06eed88
102 changed files with 35423 additions and 11181 deletions

View File

@@ -0,0 +1,371 @@
# WP Agentic Writer Follow-up Audit
Status: COMPLETE / SUPERSEDED
Completion marker date: 2026-05-24
Retrace audit: `docs/architecture/PLUGIN_AUDIT_RETRACE_2026-05-24.md`
This follow-up audit has been implementation-traced. Remaining work should be tracked from the retrace audit to avoid reopening duplicate findings that are already closed.
Audit date: 2026-05-24
Baseline audited: `docs/architecture/PLUGIN_AUDIT_REPORT_2026-05-22.md`
Definition of Done audited: `docs/DEFINITION_OF_DONE.md`
Scope: implementation trace, UI/UX, system architecture, conversation context/history, cost tracking, provider/model routing, migration safety, security, data lifecycle, and process gaps.
## Executive Summary
The 2026-05-22 audit has been partially implemented. The strongest improvements are: conversation table creation was added to activation, PHP 7.4 streaming incompatibilities were removed, a Context Service exists, several session endpoints now check ownership, and the frontend attempts legacy chat migration.
However, the plugin is not yet out of the "fix A, break B" risk zone. The highest-risk previous defect, the OpenRouter model cache shape conflict, is still open. Conversation history is still split between post meta and the session table. Several post-scoped REST routes still rely only on `edit_posts`, not `edit_post` for the target post. Provider fallback remains silent to the user, cost tracking is still not a reliable ledger, and model defaults remain inconsistent across activation, settings, providers, and JS.
Current readiness: improved beta, but still not production-safe for multi-user editorial sites or cost-sensitive usage.
## Verification Performed
- Static traced the previous audit against current PHP, JS, docs, and migration code.
- Ran PHP syntax checks across plugin PHP files with `php -l`; no syntax errors detected.
- Ran JS syntax checks with `node -c assets/js/sidebar.js` and `node -c assets/js/settings-v2.js`; no syntax errors detected.
- Did not run a live WordPress browser workflow in this pass, so runtime workflow findings remain static-analysis based.
## Previous Audit Status Trace
| Previous finding | Status | Evidence | Remaining action |
|---|---:|---|---|
| Conversation table migration not wired | Partially fixed | Activation calls `wpaw_create_conversations_table()` in `wp-agentic-writer.php:182-184`; versioned table creation also calls it in `wp-agentic-writer.php:234-238`. | Make conversation table creation independent of main `wpaw_db_version`; use `wpaw_conversations_db_version` everywhere. |
| OpenRouter model cache conflicting shapes | Open | Full model objects and ID lists still share `wpaw_openrouter_models` in `includes/class-openrouter-provider.php:105-177` and `includes/class-openrouter-provider.php:248-255`. | Split cache keys and harden validation. |
| PHP 7.4 incompatible `str_starts_with()` | Fixed | `rg` found no remaining `str_starts_with`; streaming checks use `strpos`. | Add a CI lint job under PHP 7.4 or raise the PHP requirement if future code uses PHP 8 APIs. |
| Conversation endpoints lack per-session ownership | Partially fixed | GET/PUT/DELETE/messages endpoints now call `current_user_can_access()` in `includes/class-gutenberg-sidebar.php:7283-7411`. | Add post ownership checks to list/create/link-post/writing-state routes and strengthen session IDs. |
| Two context stores compete | Open | Chat still writes `_wpaw_chat_history` and session messages in `includes/class-gutenberg-sidebar.php:996-1026` and `includes/class-gutenberg-sidebar.php:1139-1168`. | Make `wpaw_conversations.messages` the only message authority. |
| Silent provider fallback | Open | Provider Manager still returns OpenRouter on missing/unreachable provider in `includes/class-provider-manager.php:35-50`. | Return provider metadata and warning to backend response and UI. |
| Cost tracking setting does not stop tracking or enforce budget | Open | `add_request()` always inserts cost rows in `includes/class-cost-tracker.php:58-75`. | Clarify setting semantics, add policy guardrails, track provider/session/status. |
| REST route contracts too loose | Open | Routes mostly lack `args` schemas, including core routes in `includes/class-gutenberg-sidebar.php:302-813`. | Add route schemas and contract tests. |
| Main backend class too large | Open | `includes/class-gutenberg-sidebar.php` still owns route registration, workflow, context, providers, SEO/GEO, image, and session handlers. | Extract REST controllers and workflow/context/provider/cost services. |
| Admin settings depend on external CDNs | Open | CDN assets still load in `includes/class-settings-v2.php:67-75`. | Bundle vendor assets locally or use WP-native components. |
| Uninstall incomplete and duplicated | Open | Main uninstall omits conversations/custom models/meta in `wp-agentic-writer.php:269-278`; `uninstall.php:12-21` is a second, smaller cleanup path. | Consolidate uninstall and add a data-retention option. |
| Image generation partially integrated | Open | Image variants save cost locally but do not emit `wp_aw_after_api_request` in `includes/class-image-manager.php:482-522`. | Ledger image costs through Cost Tracker and add image lifecycle states. |
| Settings defaults and model labels inconsistent | Open | Defaults diverge across activation, provider, settings PHP, and JS. Examples: `wp-agentic-writer.php:140-142`, `includes/class-openrouter-provider.php:34-69`, `includes/class-settings-v2.php:105-111`, `assets/js/settings-v2.js:26-38`. | Create one model preset registry and migrate legacy `execution_model`. |
| Debug logging too noisy | Partially fixed | `wpaw_debug_log()` exists in `includes/class-gutenberg-sidebar.php:20-27`, but console logs remain in `assets/js/sidebar.js` and `assets/js/settings-v2.js`; some `error_log()` calls remain. | Gate frontend logging and remove prompt/response/debug traces from production. |
## Critical Findings
### P0: OpenRouter Model Cache Conflict Still Breaks Valid Models
`get_cached_models()` stores full OpenRouter model objects in `wpaw_openrouter_models`, while `validate_model_availability()` reads the same transient as a flat list of model IDs. If the settings page refreshes model data first, validation compares a string model ID against arrays and can reject valid models.
Evidence:
- Full object cache: `includes/class-openrouter-provider.php:105-177`
- ID-list validation using the same key: `includes/class-openrouter-provider.php:248-255`
- Strict `in_array()` against the potentially wrong shape: `includes/class-openrouter-provider.php:258-265`
Impact:
- Settings model refresh can break chat streaming or image generation.
- "Model unavailable" errors can be false negatives.
- This directly preserves the old "fix models UI, break generation" loop.
Recommended fix:
- Rename full object cache to `wpaw_openrouter_model_objects`.
- Use `wpaw_openrouter_model_ids` for validation.
- Make validation normalize arrays safely if old transient data exists.
- Delete both old transients on model refresh.
### P0: Conversation Storage Is Still Not a Single Source of Truth
The Definition of Done says conversation messages are authoritative in `wpaw_conversations.messages` via Context Service, but current chat flow still writes legacy `_wpaw_chat_history` and session messages.
Evidence:
- DoD requires session-table message authority in `docs/DEFINITION_OF_DONE.md:17-31`.
- Non-stream chat writes legacy post meta and session table in `includes/class-gutenberg-sidebar.php:996-1026`.
- Stream chat writes legacy post meta and session table in `includes/class-gutenberg-sidebar.php:1139-1168`.
- Legacy history endpoint still returns `_wpaw_chat_history` via `get_post_chat_history()` at `includes/class-gutenberg-sidebar.php:1217-1233`.
- Context Service claims legacy history is "migrated to session table on read" in `includes/class-context-service.php:21-25`, but `get_context()` does not call migration in `includes/class-context-service.php:62-87`.
- Migration leaves legacy meta in place in `includes/class-context-service.php:299-300`.
Impact:
- Chat, sessions, migration, clear context, and resume can diverge.
- Users can see old chat resurrect after session migration.
- Costs and context summaries may reference different conversation state.
Recommended fix:
- Stop writing `_wpaw_chat_history` for new messages.
- Make `/chat-history/{post_id}` a migration-only compatibility route or remove it after migration.
- Have `Context_Service::get_context()` perform one-time migration when legacy history exists.
- Delete or mark migrated legacy meta after a successful migration.
### P0: Post-scoped REST Authorization Is Still Incomplete
The base permission callback is `current_user_can('edit_posts')`, which is too broad for routes that read or write a specific post. Some conversation endpoints now check session ownership, but several post-scoped handlers still do not check the target post.
Evidence:
- Global route permission is only `edit_posts` in `includes/class-gutenberg-sidebar.php:906-908`.
- `/conversations/post/{post_id}` returns a post-linked session without `edit_post` check in `includes/class-gutenberg-sidebar.php:7217-7226`.
- `handle_create_conversation()` accepts arbitrary `post_id` without checking `edit_post` in `includes/class-gutenberg-sidebar.php:7255-7261`.
- Writing state reads and writes post meta without `edit_post` checks in `includes/class-gutenberg-sidebar.php:823-887`.
- `handle_link_conversation_to_post()` checks target post edit permission, but does not first verify access to the source session in `includes/class-gutenberg-sidebar.php:7453-7482`.
Impact:
- Any user who can edit posts may read or modify state for posts they should not access.
- Session linking can attach another user's known session ID to an editable post.
- Editorial privacy is weak on multi-author sites.
Recommended fix:
- Add `current_user_can('edit_post', $post_id)` to every post-scoped read/write handler.
- Add `current_user_can_access($session_id)` before link-post.
- Add route-level `args` with validation for `post_id` and `session_id`.
### P0: Conversation Table Migration Is Better, But Still Version-fragile
Activation now creates the conversation table, but version checks still mix the main DB version and conversation-specific version. Existing installs with `wpaw_db_version=1.1.0` but no conversation table can still be skipped by `wp_agentic_writer_maybe_create_tables()`.
Evidence:
- Conversation table creation records `wpaw_conversations_db_version` in `includes/class-conversation-migration.php:43-47`.
- `wpaw_run_migrations()` still checks `wpaw_db_version` in `includes/class-conversation-migration.php:67-72`.
- Main table creation only runs when `wpaw_db_version < 1.1.0` in `wp-agentic-writer.php:223-242`.
- Cleanup cron is scheduled at include time in `includes/class-conversation-migration.php:94-99`, independent of table readiness.
Impact:
- A site previously marked upgraded can still miss `wpaw_conversations`.
- Cron may run SQL against a missing table.
- Reinstall and upgrade behavior remains hard to reason about.
Recommended fix:
- Change migration runner to read and update only `wpaw_conversations_db_version`.
- Always call a lightweight `ensure_conversations_table()` on plugin load or before conversation DB access.
- Unschedule `wpaw_cleanup_old_sessions` on deactivation.
## High Priority Findings
### P1: Provider Fallback Violates Provider Transparency Contract
The DoD requires actual provider/model metadata plus warnings in every AI response. Current provider routing still falls back to OpenRouter without surfacing that to the UI.
Evidence:
- DoD provider metadata requirement: `docs/DEFINITION_OF_DONE.md:53-66`.
- Fallback behavior: `includes/class-provider-manager.php:35-50`.
- OpenRouter chat responses include `model`, but not `provider` or `warnings`, in `includes/class-openrouter-provider.php:505-513` and `includes/class-openrouter-provider.php:739-747`.
Impact:
- A user choosing local/private generation may unknowingly send prompts to OpenRouter.
- Sidebar cost/provider labels can be wrong.
- Debugging model routing remains confusing.
Recommended fix:
- Replace raw provider return with a `Provider_Selection_Result` containing provider instance, provider name, selected provider, actual provider, and warnings.
- Include `provider`, `selected_provider`, `fallback_used`, and `warnings` in all AI responses.
- Add a setting for fallback policy: fail closed, ask user, or auto fallback.
### P1: Cost Tracker Is Still a Log, Not a Reliable Ledger
Cost tracking inserts rows, but it does not honor the tracking setting, record failures, record provider/session IDs, or enforce budget limits.
Evidence:
- DoD cost integrity requires success and failed calls to be intentionally recorded in `docs/DEFINITION_OF_DONE.md:68-75`.
- Cost table schema lacks provider/session/status/error columns in `wp-agentic-writer.php:198-209`.
- `WP_Agentic_Writer_Cost_Tracker::add_request()` unconditionally inserts in `includes/class-cost-tracker.php:58-75`.
- Image generation returns variant costs but does not call the ledger hook in `includes/class-image-manager.php:482-522`.
Impact:
- "Cost tracking disabled" is ambiguous because backend records still happen.
- Failed paid attempts are invisible.
- Costs cannot be reconciled by provider or conversation.
Recommended fix:
- Define whether `cost_tracking_enabled` means "show UI" or "store records"; rename or enforce accordingly.
- Add columns: `provider`, `session_id`, `request_id`, `status`, `error_code`, `currency`, `raw_usage`.
- Add preflight estimates and optional hard monthly budget enforcement before remote calls.
### P1: Settings Cost Log Queries Load Too Much Data
The V2 AJAX cost log computes pagination after loading all matching rows. On real sites this can become slow.
Evidence:
- `ajax_get_cost_log_data()` counts rows, then fetches all matching records in `includes/class-settings-v2.php:645-652`.
- It groups in PHP and paginates after formatting in `includes/class-settings-v2.php:654-703`.
Impact:
- Cost log can degrade admin performance as usage grows.
- Large histories increase memory usage and can time out.
Recommended fix:
- Push grouping and pagination into SQL.
- Add indexes for `created_at`, `action`, `model`, and composite reporting queries.
### P1: Writing State Routes Can Leak or Modify Other Users' Draft State
The new writing-state endpoints read and write post meta but rely only on the broad `edit_posts` permission.
Evidence:
- Routes registered at `includes/class-gutenberg-sidebar.php:624-641`.
- Read/write handlers do not call `current_user_can('edit_post', $post_id)` in `includes/class-gutenberg-sidebar.php:823-887`.
Impact:
- A contributor/editor can potentially inspect or change workflow state for a post outside their permissions.
- Pause/resume state can be corrupted across users.
Recommended fix:
- Require `edit_post` for the specific post in both handlers.
- Sanitize status against an allowlist: `idle`, `in_progress`, `paused`, `completed`, `failed`.
- Consider moving writing state into the conversation/session record to reduce post meta sprawl.
## Medium Priority Findings
### P2: Context Service Exists But Is Not the Unified Interface
`WP_Agentic_Writer_Context_Service` is a useful foundation, but most generation paths still assemble context directly in `class-gutenberg-sidebar.php`. Static search shows only chat persistence and migration use it.
Evidence:
- Context Service methods exist in `includes/class-context-service.php`.
- Current references from sidebar are limited to message append and migration in `includes/class-gutenberg-sidebar.php:1009`, `includes/class-gutenberg-sidebar.php:1151`, and `includes/class-gutenberg-sidebar.php:7526`.
- DoD requires all generation paths to use it in `docs/DEFINITION_OF_DONE.md:29-52`.
Opportunity:
- Make `Context_Service::build_ai_context()` the only path for chat, plan, write, refine, SEO/GEO, image prompt generation, and suggestions.
- Let REST handlers pass request data to Workflow Service, not directly assemble prompts.
### P2: Session IDs Are Short and Non-cryptographic
Session IDs are generated with `substr(md5(uniqid(wp_rand(), true)), 0, 16)`.
Evidence:
- `includes/class-conversation-manager.php:63-65`
Impact:
- The attack surface is still small, but this is weaker than modern token generation.
- Since session IDs gate private editorial context, stronger IDs are cheap insurance.
Recommended fix:
- Use `bin2hex(random_bytes(16))` where available with a WP fallback, or `wp_generate_uuid4()`.
- Expand DB column from `VARCHAR(32)` if needed.
### P2: Model Defaults Are Still Fragmented
The implementation made some defaults cheaper, but there is still no single model registry.
Evidence:
- Activation still stores `planning_model` and legacy `execution_model` in `wp-agentic-writer.php:140-142`.
- Provider defaults differ in `includes/class-openrouter-provider.php:34-69`.
- Settings V2 has multiple fallback sets in `includes/class-settings-v2.php:105-111`, `includes/class-settings-v2.php:228-273`, and `includes/class-settings-v2.php:990-995`.
- JS has another set in `assets/js/settings-v2.js:26-38`.
Impact:
- Fresh install, upgraded install, saved settings, and JS reset can produce different model choices.
- Support/debugging becomes harder because "default" depends on code path.
Recommended fix:
- Add `WP_Agentic_Writer_Model_Registry`.
- Expose the registry to JS through localization.
- Migrate `execution_model` to `writing_model` and stop storing both.
### P2: Debug Logging Is Partly Gated, Frontend Logging Is Not
Backend logging improved in some places, but frontend debug logs remain numerous and some backend providers still log operational details.
Evidence:
- `wpaw_debug_log()` is gated in `includes/class-gutenberg-sidebar.php:20-27`.
- Frontend migration/session/clarity logs remain in `assets/js/sidebar.js:308-322`, `assets/js/sidebar.js:5619-5630`, and `assets/js/sidebar.js:6130-6157`.
- Settings debug logs remain in `assets/js/settings-v2.js:53-130` and cost log logs in `assets/js/settings-v2.js:390-503`.
Impact:
- Browser console can expose topics, local backend behavior, model state, and debug-only workflows.
- Debug noise hides real errors for users.
Recommended fix:
- Add `wpAgenticWriter.debug` and `wpawSettingsV2.debug`.
- Wrap all console logging behind a tiny logger utility.
- Keep `console.error` only for actionable user-visible failures.
### P2: Admin UX Still Has External Runtime Dependencies
Settings V2 still uses CDN Bootstrap and Select2.
Evidence:
- `includes/class-settings-v2.php:67-75`
Impact:
- Settings UI can break offline, under CSP, or in restricted enterprise admin environments.
- External admin CDNs create privacy and supply-chain risk.
Recommended fix:
- Bundle vendor files locally or rebuild the settings UI with WordPress components.
### P2: Uninstall and Data Lifecycle Are Not Clean
The main uninstall hook and `uninstall.php` disagree. Neither fully removes conversations, versions, custom models, all post meta, transients, or cron events.
Evidence:
- Main uninstall: `wp-agentic-writer.php:269-294`
- Separate uninstall file: `uninstall.php:12-21`
Impact:
- Reinstalls can inherit stale model settings, chat sessions, and DB versions.
- Testing fresh install behavior remains unreliable.
Recommended fix:
- Choose one uninstall path.
- Add an admin setting for "delete all plugin data on uninstall".
- Delete all plugin options, transients, scheduled hooks, tables, upload temp files, and known post/user meta.
## UI/UX Opportunities
### Make Context Visible
Users need a compact "What the agent knows" panel: active session, linked post, focus keyword, language, plan status, message count, provider, model, and estimated cost. This directly reduces the confusion when a conversation resumes with unexpected memory.
### Make Provider Execution Explicit
Before any paid/private-sensitive action, show the selected provider and the actual provider health. If fallback will happen, ask or show a visible warning. After the call, show the actual provider/model used.
### Add Workflow State Instead of Hidden Modes
The sidebar has chat, sessions, planning, writing, cost, SEO, clarification, resume, and suggestions. These should map to one visible state machine:
1. Context
2. Plan
3. Write
4. Review
5. Publish Assist
Each state should have one clear primary action and one clear recovery action.
### Add Review/Accept Safety for High-impact Edits
Generated blocks and refinements should have a review layer for diff/accept/reject, especially for article-wide and multi-pass edits.
## Definition of Done Compliance
| DoD area | Current compliance | Notes |
|---|---:|---|
| Storage layer declaration | Partial | DoD exists, but current code still uses dual chat storage. |
| Context Service usage | Failing | Not all generation paths use Context Service. |
| Provider transparency | Failing | Responses do not consistently include `provider` or fallback warnings. |
| Cost record integrity | Failing | Failed calls and image calls are not consistently ledgered. |
| Workflow tests | Unknown | Syntax checks pass, but no end-to-end workflow evidence found. |
| No double source of truth | Failing | Conversation messages still exist in post meta and sessions. |
| Migration safety | Partial | Conversation table creation added, but versioning remains inconsistent. |
| Security contract | Partial | Some session endpoints fixed, post-scoped routes remain broad. |
| Changelog policy | Failing | `CHANGELOG.md` was not found while DoD requires it. |
## Recommended Next Work Queue
### Do First
1. Split OpenRouter model cache keys and flush old transient data.
2. Enforce `edit_post` and session access checks on all post/session routes.
3. Make conversation messages session-table only for new writes.
4. Fix conversation migration versioning to use `wpaw_conversations_db_version` independently.
### Do Second
1. Implement provider selection metadata and fallback warnings.
2. Upgrade cost tracking into a provider/session-aware ledger.
3. Centralize model defaults in a registry.
4. Gate frontend and backend debug logging.
### Do Third
1. Extract REST controllers and workflow services from `class-gutenberg-sidebar.php`.
2. Bundle admin dependencies locally.
3. Consolidate uninstall/data retention behavior.
4. Add end-to-end workflow tests for Chat -> Plan -> Write, Stop -> Resume, and Clear Context -> New Plan.
## Completion Marker
The original 2026-05-22 audit is now marked complete/superseded. Remaining work should be tracked from this follow-up audit only, to avoid duplicate jobs debt.