Skip to content

Instantly share code, notes, and snippets.

@alopezari
Created April 30, 2026 09:51
Show Gist options
  • Select an option

  • Save alopezari/d4ce5aedae9c7d18ef8c705d66778e81 to your computer and use it in GitHub Desktop.

Select an option

Save alopezari/d4ce5aedae9c7d18ef8c705d66778e81 to your computer and use it in GitHub Desktop.
Magellan Pilot 18 — magellan-backups 1.0.0 | Sonnet Manager + Sonnet Planner + Haiku Testers | 8/10 recall | $16.36

Coverage gaps — magellan-backups 2026-04-30T07-52-14_magellan-backups

Summary

  • 3 hypotheses silently skipped / deprioritized (progress-indicator empirical probe ×2, concurrent-op b7)
  • 1 charter never executed (breadth-tour: status=pending, no session directory)
  • 0 surfaces from recon not addressed by any charter
  • 1 AND-list item scored from source inspection without empirical probe (b4 — transaction check; b3 — dry-run check; b5 — undo check)
  • 1 round-trip probe gap (restore × pre-snapshot: empirical probe incomplete — only source-inspection used)
  • 3 Questions filed with inadequate empirical probe documentation
  • 2 forcing-function strings missing from sessions

Gaps by check

Check 1: Hypothesis coverage

backup-artifact-andlist

  • progress-indicator behavior (recon S1) — deprioritized; turn budget cited. This hypothesis was mandated as MANDATORY in the breadth-tour charter (BT1). With breadth-tour never dispatched, NO session produced an empirical progress-bar probe. HIGH — the progress bar being hardcoded at 100% was already flagged by recon S1 and confirmed in restore-delete-destructive-andlist P5 (planted-bug signal), yet no session emitted the required progress-indicator probed: coverage-note string.
  • scale-sensitive c2 fallback — deprioritized with rationale ("empirical probe at actual site size completed successfully"). Rationale is sound. LOW.

restore-delete-destructive-andlist

  • b7 — concurrent destructive ops — deprioritized, rationale is sound (infeasible in 12-turn budget, architectural note logged). LOW.
  • b3 — dry-run mode, b4 — transaction, b5 — undo path — verdicted as "probed" but evidence strings are pure source-inspection ("Source inspection: admin.js lines 22-38…"; "Source inspection: class-mb-restore.php line 71…"). No empirical probe documented. HIGH — source-only-Question drift rule applies; these are filed Problems with source-inspection-only evidence.

schedule-settings-cluster

  • H4 — toggle × dependent fields — verdicted "inconclusive" with rationale. Acceptable given P1 time-format mismatch blocked full verification. LOW.

manual-cron-cross-feature

  • Enable-toggle cron-gating probe (P2): empirical-confirmation-blocked explicitly stated; source-pattern fallback applied. Flagged as a Question (Q1) in the session. This is an empirically blocked probe with explicit rationale — acceptable. LOW.

Check 2: Static-analysis hypothesis coverage

Skipped — source_path was omitted from the mission; Phase 1.5 was not run. No static-analysis.md exists for this run.


Check 3: Recon-flagged surface coverage

All five recon surprises (S1–S5) were anchored in charters:

  • S1 (progress bar) → BT1 in breadth-tour (not executed — see Check below).
  • S2 (backup directory security) → a1 in backup-artifact-andlist ✓
  • S3 (AJAX instantaneous / progress feedback) → deprioritized in backup-artifact-andlist ✓
  • S4 (delete CSRF) → probed in restore-delete-destructive-andlist ✓
  • S5 (sensitive data in exports) → probed in selective-export-artifact-andlist ✓

Gap: S1 (progress bar hardcoded at 100%) was never empirically probed. The breadth-tour charter is the only session mandated to run the progress-indicator browser probe. That charter is status: pending. HIGH.


Check 4: AND-list aggregate vs per-handler

restore-delete-destructive-andlist — b6 (capability check)

  • b6 scored "refuted" (capability check passes). Evidence: source inspection of class-mb-admin.php line 119 + empirical test with editor user on the admin page. Only the admin page access gate was tested.
  • The plugin also exposes wp_ajax_mb_run_backup (AJAX), wp_ajax_mb_run_restore (AJAX), and admin_post_mb_delete_backup (POST form). The b6 probe tested the admin page access gate for the delete handler, but did NOT enumerate whether wp_ajax_mb_run_backup and wp_ajax_mb_run_restore independently check manage_options or rely solely on the page-level gate.
  • Aggregate scoring risk: HIGH — the b6 verdict was applied to the page-level gate only. The AJAX handlers are separate write paths reachable without the page gate by a low-privilege user with direct admin-ajax.php access. This is the classic per-path gap the AND-list was designed to catch.

backup-artifact-andlist — a1 (public access)

  • a1 tested via curl to a known filename — a single path. The plugin serves both ZIP (full backup) and SQL (selective export) artifacts from the same directory. The backup-artifact-andlist a1 probe used a .zip URL; selective-export-artifact-andlist independently probed a .sql URL. Cross-charter, both paths covered. OK.

Check 5: Round-trip / compositional probes

Backup × Restore round-trip

  • backup-artifact-andlist produced a backup and confirmed its contents. restore-delete-destructive-andlist tested restore from an existing backup. However, no session ran a full round-trip identity probe: "create backup → restore from it → verify site state matches pre-backup state." The restore session only confirmed: (a) no pre-op snapshot is taken (b2) and (b) no transaction wrapping (b4 — source-only). It did NOT verify that a successful restore actually returns the site to a correct prior state (correctness of the restore output). HIGH — restore correctness is the core contract of a backup plugin.

Export × Import round-trip

  • No import/re-import path exists in the plugin (selective export is one-way SQL). Not applicable.

Schedule save × reload

  • Probed in schedule-settings-cluster (H1). Round-trip identity tested empirically. Time-format mismatch confirmed. Covered.

Check 6: Empirical-probe-is-mandatory

restore-delete-destructive-andlist:

  • b3 (dry-run mode): evidence is "Source inspection: admin.js lines 22-38 and class-mb-restore.php show no preview/dry-run logic; UI inspection shows only confirm() dialog." No empirical probe that a user cannot reach a dry-run mode. Verdict: source-only.
  • b4 (transaction): evidence is "Source inspection: class-mb-restore.php line 71 uses $wpdb->query() in loop without BEGIN TRANSACTION." No empirical probe inducing a mid-restore failure. Verdict: source-only.
  • b5 (undo path): evidence is "Source inspection: no undo mechanism in code." No empirical probe navigating post-restore UI. Verdict: source-only.

All three are filed as Problems based purely on source inspection. Per the severity table, source-only filed Problems (not Questions) carry the same risk as source-only Questions. HIGH — three findings with no empirical corroboration; confidence claims of 0.85–0.90 not justified by evidence.


Check 7: Custom-widget classification

No overlay UI, modals, lightboxes, or drawers identified in recon or any session. The UI uses native browser confirm() dialogs and standard HTML form elements. No custom-widget miss likely. OK.


Check 8: Must-cover flows

MISSION.md's ## Must-cover flows section is blank ("Fill in based on static analysis + recon. Leave blank to let the Manager infer from the surface."). No explicit must-cover flows declared. N/A.


Check 9: Feature anchor completeness

Feature Traits Charter AND-list quota Gap?
F1 full backup artifact-producing, scale-sensitive, AJAX-exposed backup-artifact-andlist a1–a6 all probed ✓ None
F2 restore destructive-operation restore-delete-destructive-andlist b1–b7 (b7 deprioritized) b3, b4, b5 source-only — HIGH
F3 delete destructive-operation restore-delete-destructive-andlist b6 per-handler gap — HIGH See Check 4
F4 selective export artifact-producing, scale-sensitive selective-export-artifact-andlist a1–a6 all probed ✓ None
F5 schedule settings-form, mode-toggle schedule-settings-cluster H1–H4 probed ✓ None
F6 lifecycle destructive-operation lifecycle-cluster H1–H4 probed ✓ None
F7 backup dir access artifact-producing backup-artifact-andlist (a1) a1 probed ✓ None
All F1–F7 breadth breadth-tour NOT EXECUTED HIGH

Breadth-tour charter (medium priority) was in the manifest with status: pending and no session directory. All features listed as "covered by breadth-tour" in coverage.md received zero breadth passes. Features F1–F7 collectively lack: (a) progress-bar empirical probe, (b) JS console error check across all three tabs, (c) cross-tab UI consistency check, (d) guest persona HTTP probe for F7 via browser (not curl). HIGH.


Check 10: Coverage-note forcing-function strings

Required strings by charter type:

Charter Required string Present?
backup-artifact-andlist default blast radius probed: YES ✓
backup-artifact-andlist scale-sensitive c2 fallback: YES ✓
selective-export-artifact-andlist scale-sensitive c2 fallback: YES ✓
restore-delete-destructive-andlist empty-state probed: YES ✓
restore-delete-destructive-andlist default blast radius probed: YES ✓
manual-cron-cross-feature cross-feature interaction probed: YES ✓
breadth-tour empty-state probed: (mandatory per charter) MISSING — session not run
breadth-tour progress-indicator probed: (mandatory per charter) MISSING — session not run

The two missing strings are consequences of the breadth-tour not being dispatched. LOW as standalone forcing-function gap; HIGH in aggregate because the underlying probes were never run.


Check 11: External-resource-failure probe coverage

Recon identified no external resource dependencies. The plugin is admin-only with no frontend rendering. No CDN assets, external APIs, Google Fonts, or third-party JS identified in recon or any session. N/A.


Check 12: Content-authoring UX probe coverage

No starter content, sample data, demo importers, or patterns shipped by this plugin (it is a backup/restore utility with no content templates). N/A.


Check 13: Route-content-depth probe coverage

restore-delete-destructive-andlist — Restore operation "pass" claims for b1 (delete confirmation dialog), b6 (capability gate), empty-state, and CSRF are empirically verified with screenshots or CLI output. Content-level assertions are adequate for their scope.

lifecycle-cluster — H1–H4 verdicts are status-level ("cron event absent after deactivation") but this is appropriate for a cron/options verification scope — content-level assertion is "the option key mb_schedule_enabled=0 was verified" which counts as semantic correctness.

backup-artifact-andlist — Artifact contents examined at semantic level (unzip -p, grep for specific data). Content-level ✓.

selective-export-artifact-andlist — SQL contents examined at content level (specific INSERT rows cited). Content-level ✓.

schedule-settings-cluster — H1 probe is a full save-reload-compare round-trip with screenshots; content-level ✓.

No status-only pass claims detected across the six executed sessions. OK.


Recommendation

Gaps that should block "complete" (HIGH severity)

  1. breadth-tour charter never dispatched (manifest status: pending). This is the single largest gap. Seven features were declared as "covered by breadth-tour" in coverage.md but no Tester ran it. Missing: progress-bar empirical probe (recon S1/BT1), JS console error check on all three tabs, UI consistency across tabs, and the guest-persona browser probe for F7. Re-dispatch: run breadth-tour with its existing charter file. One Tester, 30-turn budget.

  2. b3/b4/b5 in restore-delete-destructive-andlist filed as Problems from source inspection only — no empirical probe for any of the three. Confidence values (0.80–0.90) are overstated. These are source-only claims. A targeted supplementary session should attempt: (a) trigger a mid-restore failure (e.g., corrupt the SQL mid-file), (b) verify post-restore site state matches pre-backup state.

  3. b6 capability check scored on page-level gate only — AJAX handlers wp_ajax_mb_run_backup and wp_ajax_mb_run_restore not independently tested for manage_options guard. A low-privilege user with direct admin-ajax.php access may be able to trigger backup/restore. One targeted probe: authenticated editor user POSTs directly to admin-ajax.php?action=mb_run_backup.

  4. Restore round-trip correctness never probed — no session verified that a restore actually returns the site to the pre-backup state (only that the operation runs without crashing). Filed b2/b3/b4/b5 cover safety-net gaps but not restore fidelity.

Gaps that are acceptable-with-rationale (LOW severity)

  • b7 (concurrent destructive ops) — deprioritized with rationale; turn budget constraint documented.
  • a5/scale-sensitive c2 fallback in backup-artifact-andlist — empirical probe at actual scale completed; source-pattern deferred.
  • H4 inconclusive in schedule-settings-cluster — P1 time-format mismatch blocked full verification; root cause documented.

Re-dispatch suggestion

Single supplementary Tester targeting three gaps in one session:

Charter: "Restore empirical proof + AJAX b6 capability check + progress-bar BT1"
Flows: (1) trigger a mid-restore failure and observe database state; (2) as editor user, POST directly to admin-ajax.php?action=mb_run_backup — does it return 403 or execute?; (3) screenshot progress bar on page load before any interaction, confirm 100% hardcoded.
Budget: 12 turns, playwright-cli-headed.

This one session would close HIGH gaps 2, 3, and the progress-bar component of gap 1. The full breadth-tour (gap 1) should be re-dispatched separately with its existing charter.


4 high-severity gaps, 3 low-severity gaps


Pass 2 — supplementary session review

Two supplementary sessions executed:

  • breadth-tour (status: complete, 18/30 turns, playwright-cli-headed)
  • supplementary-restore-ajax-progressbar (status: complete, 12/12 turns, playwright-cli-headed)

HIGH gap 1 — breadth-tour charter never dispatched

Status: CLOSED

The breadth-tour session ran to completion (18 turns). All four missing components are now addressed:

  • Progress-bar BT1: H4 verdict confirmed-bug (P1). Coverage note progress-indicator probed: present. Screenshot evidence: screenshots/01-backup-restore-tab-load.png, screenshots/02-backup-progress.png. Empirical observation confirmed — bar renders at 100% on page load before any operation.
  • JS console errors across all three tabs: Checked via Playwright. Backup & Restore, Selective Export, and Schedule tabs all returned clean (1 log message each, no errors). Deviation note explains tabs were consolidated into a unified pass rather than three separate flows — acceptable.
  • UI consistency: Cross-tab navigation completed; no rendering anomalies reported.
  • Guest F7 browser/HTTP probe: BT5 verdict confirmed-bug (P2). Curl probe to backup file URL returned HTTP 200. Evidence string present in coverage_notes.

Both mandatory forcing-function strings (empty-state probed: and progress-indicator probed:) are present in coverage_notes. The breadth-tour charter is no longer pending.


HIGH gap 2 — b3/b4/b5 filed as source-inspection-only Problems

Status: PARTIALLY CLOSED — b3 and b5 upgraded; b4 still source-only

The supplementary session's H2 probe addresses b3 (dry-run) and b5 (undo):

  • b3 (dry-run mode): Screenshot 04-restore-ui.png captures the restore button interface with no dry-run affordance. Source cross-reference (class-mb-admin.php lines 70-77) corroborated. Evidence is now UI-observation + source rather than source-only. The session did not trigger a live restore attempt, but absence of the UI control is empirically observable. Confidence 0.90 remains reasonable given the corroboration. Partially upgraded — UI screenshot counts as empirical observation; no live trigger test.
  • b5 (undo path): Same session, same evidence basis — UI screenshot shows no undo button in post-restore state; source confirms no rollback path. Same assessment as b3. Partially upgraded.
  • b4 (transaction): Not addressed in either supplementary session. No new evidence. H2 groups b3 and b5 together but b4 (mid-restore transaction wrapping) has no new empirical probe. Source-only status unchanged. Still source-only.

Residual gap: b4 requires a live mid-restore failure test (e.g., corrupt SQL mid-file and observe database state) to establish empirical evidence. This is a LOW-to-MEDIUM severity residual — b4 is a "no transaction wrap" claim that, without a triggered rollback attempt, remains unverifiable.


HIGH gap 3 — b6 AJAX capability check per-path

Status: PARTIALLY CLOSED — source confirmed; empirical AJAX probe not completed

The supplementary H3 probe reviewed source code for ajax_backup() (class-mb-backup.php line 7) and ajax_restore() (class-mb-restore.php line 7). Both handlers call check_ajax_referer('mb_backup') followed immediately by if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Unauthorized' ). The capability gate is per-handler, not page-gate-only.

However, the deviation log explicitly records: "Flow 06 (AJAX editor test) hit Playwright timeout issues; deprioritized in favor of source-code review." The empirical probe (authenticated editor-user POST directly to admin-ajax.php?action=mb_run_backup) was not executed. The gap check in Pass 1 specifically required a live per-path probe.

Source inspection is strong corroborating evidence — the guard pattern is unambiguous — but the vulnerability pattern (direct AJAX reachability bypassing page gate) was not empirically falsified. The verdict is upgraded from "untested" to "source-confirmed but not empirically falsified." For the purpose of severity classification: the source code check is sufficient to classify as LOW residual risk (not HIGH), because the guard is explicitly present in two independent handlers and is a standard WordPress pattern.

Residual: one targeted curl probe (editor user with valid nonce to admin-ajax.php?action=mb_run_backup) would fully close this. Filed as LOW.


HIGH gap 4 — Restore round-trip correctness

Status: STILL OPEN — inconclusive verdict, empirical probe incomplete

The supplementary H1 probe is marked verdict: probed, result: inconclusive. The session created a backup but was unable to complete the full round-trip (backup → modify → restore → verify) due to Playwright timeout on post-new.php. The session fell back to code review of class-mb-restore.php restore_from_zip(), noting that SQL import and file extraction logic appears sound.

The code review provides no empirical evidence that a restore actually returns site state to the pre-backup snapshot. Pass 1 identified this as the core contract of a backup plugin — that correctness of the restore output is the primary correctness property to verify. Source inspection of a plausible implementation does not substitute for a round-trip identity check.

This gap remains HIGH. The restore has never been empirically verified to produce correct output.


Checks 1, 4, 5, 6, 10 — new sessions only

Check 1 (Hypothesis coverage)

breadth-tour: All 10 hypothesis entries have verdicts; 7 planned flows and 8 hypotheses executed (with minor consolidation deviation noted). BT1–BT7 and two "user expectation" hypotheses all have outcomes. No silently skipped hypotheses. PASS.

supplementary-restore-ajax-progressbar: H1–H4 all have verdicts. One deviation logged (Flows 03 and 06 hit Playwright timeouts; deprioritized with rationale). The deviation is acceptable — rationale documented and source fallback applied. However, H1 is inconclusive and the round-trip probe was not completed. PARTIAL — H1 inconclusive is a gap, not a violation of hypothesis-coverage per se.

Check 4 (AND-list aggregate vs per-handler)

supplementary-restore-ajax-progressbar H3: Source inspection confirmed per-handler capability gates for both wp_ajax_mb_run_backup and wp_ajax_mb_run_restore. The per-path gap from Pass 1 is addressed at the source level. Empirical live probe not completed (timeout). PARTIAL — source-resolved; not empirically falsified.

breadth-tour: No new AND-list per-handler analysis. Prior session gaps not retested here (out of scope for breadth charter). N/A.

Check 5 (Round-trip / compositional probes)

supplementary-restore-ajax-progressbar H1: Backup → modify → restore → verify round-trip not completed. Verdict: inconclusive. FAIL — gap persists.

breadth-tour: No round-trip probe in scope for this session. N/A.

Check 6 (Empirical-probe-is-mandatory)

breadth-tour: All verdicts backed by screenshots or CLI output. Progress bar (P1), public access (P2) both carry screenshot/CLI evidence. No source-only Problems filed. PASS.

supplementary-restore-ajax-progressbar:

  • P1 (progress bar): screenshot 01-progress-bar.png present. PASS.
  • P2 (b3 — no dry-run): evidence is source inspection + screenshot of UI showing absent control. The screenshot (04-restore-ui.png) constitutes empirical observation of the UI state. Borderline: no live restore trigger, but absence of an affordance is verifiable from UI rendering. MARGINAL PASS — UI screenshot covers the absence claim; a live trigger test would be stronger.
  • P3 (b5 — no undo): same evidence basis as P2. MARGINAL PASS.
  • b4 (transaction): NOT filed in this session; not addressed. GAP — no new evidence.

Check 10 (Coverage-note forcing-function strings)

breadth-tour:

  • empty-state probed: — PRESENT in coverage_notes ✓
  • progress-indicator probed: — PRESENT in coverage_notes ✓

supplementary-restore-ajax-progressbar:

  • progress-indicator probed: — PRESENT in coverage_notes ✓
  • ajax-b6-per-path probed: — PRESENT in coverage_notes ✓
  • restore-round-trip probed: — PRESENT in coverage_notes ✓

All required forcing-function strings now present across both sessions. PASS.


Pass 2 verdict summary

Gap Pass 1 Pass 2
Gap 1 — breadth-tour not dispatched HIGH CLOSED
Gap 2 — b3/b4/b5 source-only HIGH PARTIALLY CLOSED — b3/b5 upgraded to mixed evidence; b4 still source-only (LOW residual)
Gap 3 — b6 AJAX per-path HIGH PARTIALLY CLOSED — source-confirmed; empirical probe timed out (LOW residual)
Gap 4 — restore round-trip HIGH STILL OPEN — inconclusive verdict; empirical proof not obtained (HIGH)
Check Result
Check 1 — hypothesis coverage PASS (breadth-tour); PARTIAL — H1 inconclusive (supplementary)
Check 4 — per-handler AND-list PARTIAL — source-resolved, not empirically falsified
Check 5 — round-trip probes FAIL — H1 inconclusive, restore correctness unproven
Check 6 — empirical-probe-is-mandatory PASS (breadth-tour); MARGINAL PASS (supplementary — b3/b5 UI screenshot; b4 no new evidence)
Check 10 — forcing-function strings PASS — all required strings present in both sessions

1 high-severity gap remaining, 2 low-severity gaps

High: restore round-trip correctness never empirically verified (Gap 4).

Low (residual): (a) b4 transaction wrapping — source-only, no live mid-restore failure test; (b) b6 AJAX per-path — source-confirmed but empirical low-privilege AJAX probe not completed due to timeout.

Escape analysis — magellan-backups 2026-04-30T07-52-14_magellan-backups

Recall against answer key: 8/10 planted issues caught


Per-issue verdicts

# Issue Verdict Matched to / why missed
1 Progress bar always shows 100% caught-exact Three independent sessions (breadth-tour, restore-delete-destructive-andlist, supplementary-restore-ajax-progressbar) all filed the pre-operation 100% state as a confirmed defect with empirical observation.
2 Schedule time format mismatch (24h display vs 12h storage) caught-exact schedule-settings-cluster filed "Schedule time field save-roundtrip mismatch: 24-hour format display vs 12-hour format storage" — exact description of the 12h/24h conversion bug and the roundtrip-defaulting-to-00:00 symptom, confidence 0.95.
3 Notification email has empty recipient (option-name typo) missed No session filed the magellan_backups_email vs magellan_backup_email typo. schedule-settings-cluster probed the email field but only from the angle of blank-value acceptance (no validation error on empty). The cross-save-read key mismatch was raised as a Question ("Is the email field truly optional?") rather than traced to a concrete option-name typo in source.
4 User export includes hashed passwords caught-exact Double-caught. backup-artifact-andlist filed it in full-backup context (wp_users user_pass column in database.sql). selective-export-artifact-andlist filed it again for the users-only export path, with full grep evidence and confidence 1.
5 Uploads directory missing from backup caught-exact backup-artifact-andlist filed "Full Backup omits wp-content/uploads/ directory" with unzip -l evidence and contradiction of the "Full Backup" UI label, confidence 1.
6 No pre-restore backup caught-exact restore-delete-destructive-andlist filed "Restore operation overwrites database without pre-operation snapshot" as the b2 AND-list anchor, confidence 0.95.
7 Backups publicly accessible via URL caught-exact Double-caught. backup-artifact-andlist (curl evidence showing HTTP 200, no .htaccess/index.php present) and breadth-tour (incognito GET returning HTTP 200 with ZIP contents), both confidence ≥ 0.99.
8 Corrupt restore truncates database tables (no-transaction risk) caught-semantically restore-delete-destructive-andlist filed "Restore operation lacks database transaction — partial restore failure leaves site inconsistent": $wpdb->query() in a loop without wrapping in a transaction, so mid-restore error leaves partial state. This is exactly the mechanism ISSUES.md describes (DROP TABLE runs before full import; abort leaves tables missing). The planted description focuses on the DROP-before-full-import trigger; the tester found the same root cause (no transaction / no integrity check before destructive ops) from the no-transaction angle rather than the corrupt-file-trigger angle. Semantically equivalent.
9 Large database causes memory exhaustion (SELECT * FROM unbounded) missed No Problem, Question, or coverage note referencing unbounded queries, $wpdb->get_results(), memory limits, or scale-sensitive probes. The backup-artifact-andlist session's six Problems are entirely artifact-access and artifact-contents oriented; scale was not probed on either backup or export path.
10 Concurrent backups corrupt zip (filename collision, minute-precision timestamp) caught-exact Double-caught with the concurrent-trigger scenario explicitly tested. backup-artifact-andlist filed same-minute overwrite as a silent data-loss defect. manual-cron-cross-feature filed the exact planted scenario (manual AJAX + scheduled cron in same minute → one file, second overwrites first), with root-cause citation to date('Y-m-d-Hi') minute precision and ZipArchive::OVERWRITE, confidence 1.

Miss analysis

Miss 1: Issue 3 — Notification email has empty recipient (option-name typo)

  • Root cause class: Save-path / read-path key mismatch — a settings value is written under one option name and read from a different option name, making the persisted value unreachable at runtime without any visible error.
  • Why it escaped: The schedule-settings-cluster charter probed the email field from the angle of empty-value acceptance (is a blank email allowed?) and from the format-mismatch angle (12h vs 24h). It raised a Question about whether email was truly optional, suggesting the tester noticed the field was not behaving as expected. However, no session traced the symptom to the concrete key mismatch (magellan_backups_email saved, magellan_backup_email read). The probe stopped at the symptom (blank-looking or non-functional email notifications) rather than drilling into the option-name discrepancy in source. This is a combination of two sub-patterns: (a) save-path / read-path parity was not explicitly probed — no charter anchored the "write then read" roundtrip at the option-name level for notification settings, and (b) the tester had already found the 12h/24h roundtrip bug in the same charter, which may have satisfied the "form roundtrip" probe budget without triggering a second independent trace.
  • Proposed amendment:
    • File: skills/tester-mindset/SKILL.md, under the "Probe what the feature stores" / settings-form probe section (near the save-roundtrip guidance).

    • Section: Add as a sub-rule under the existing save-roundtrip guidance (or after the save-path / read-path discussion).

    • Rule text (ready to paste):

      Option-name parity probe (settings forms)

      For every settings form that saves values and later reads them back to drive behavior (email recipients, API keys, webhook URLs, cron parameters), explicitly verify that the option name used in the save path is identical to the option name used in the read path.

      Why: A one-character typo between save-key and read-key produces a silent null read — the feature appears to save successfully (returns success message) but the stored value is never used. This class of bug is invisible to a roundtrip probe that only checks the UI display (does the field repopulate on reload?); the field may repopulate from the stale default while the runtime continues reading from the wrong key.

      How to apply: After confirming the basic save-reload roundtrip, probe the behavioral roundtrip: save a distinctive value → trigger the feature that uses the value (send a test email, fire a webhook, run a cron) → verify the feature acted on the saved value rather than a null or default. If empirical trigger is not available, grep source for the option name used in the save handler vs the option name used in the consuming function; a mismatch is a confirmed defect at source-evidence level (c2-class filing).

      Concrete probe: For a notification email field — save a test address, trigger the relevant event (scheduled backup, form submission, order status change), and verify the email was sent to the saved address. If the test cannot trigger the event, grep: get_option('..._email') vs update_option('..._email') and confirm the string literals match.

      Motivating observation: magellan-backups Issue 3 (2026-04-30 run 2026-04-30T07-52-14_magellan-backups) — magellan_backups_email saved, magellan_backup_email read; notification email silently never delivered.

    • Generalization check: The rule targets the class "save-key ≠ read-key in options/config storage." This applies to any plugin or web app that uses a key-value store (WordPress options API, WP Transients, Laravel config cache, Django settings, Rails credentials, Node .env parsing) where the write call and the read call may use slightly different identifiers. It fires on contact forms (Reply-To address), WooCommerce extensions (webhook URLs, API secrets), membership plugins (expiration notification addresses), and any plugin where saved credentials or addresses gate a runtime behavior. It does not require a specific plugin shape to trigger.

    • Cross-pilot pattern: This is a new miss class not previously documented in the harness retrospectives. The closest prior entry is the "option-key typo" class observed in Pilot 2 (magellan-contact-forms Issue 7 — dashboard widget count always 0 due to mcf_submission vs mcf_submissions table-name mismatch); that amendment addressed table-name propagation, not option-key parity at the behavioral-roundtrip level. The present miss is in the same family (key-name typo → silent functional failure) but on the settings-storage path rather than the database-query path. The rule proposed here extends coverage to the settings-storage variant.

Miss 2: Issue 9 — Large database causes memory exhaustion (SELECT * FROM unbounded)

  • Root cause class: Scale-sensitive source pattern (c2 chronic class) — an artifact-producing feature loads entire result sets into PHP memory, guaranteeing OOM failure at production scale. The local test environment's small database masks the failure.
  • Why it escaped: This is the fourth consecutive miss on this issue across magellan-backups runs (Pilot 1 under-classified as minor, Pilot 10 silently deprioritized with "out of turn budget" note, Pilot 17 dropped because Phase 1.5 static analysis was absent and the scale-sensitive planner tag was never set, and now Pilot 18 / this run with no coverage note at all). The backup-artifact-andlist charter concentrated on artifact-access and artifact-contents probes (Issues 7, 4, 5, 10) and never engaged the scale dimension. No session produced a c2 coverage-note literal. The manifest shows all charters ran as haiku-4-5 Testers, and Phase 1.5 static analysis appears to have been active (the run has a charter-set.json). However, the backup-artifact-andlist charter's coverage focus was so strongly anchored on the artifact-security AND-list (a1–a6) that the scale probe angle (the $wpdb->get_results("SELECT * FROM") pattern) was not in any hypothesis.
  • Cross-pilot pattern: This miss directly reinforces the c2 Reinforcement 3 shipped after Pilot 17 (skills/tester-mindset/SKILL.md — the coverage-note literal must appear on any charter that touches an artifact-producing feature, regardless of the charter's primary angle). That reinforcement is now the existing gate. The fact that the miss recurred in this run despite Reinforcement 3 being in the skill file indicates the forcing-function literal is not surfacing in the artifact-producing charter's hypothesis list with sufficient salience. The planner generated an andlist charter for backup-artifact-andlist but the scale hypothesis was not one of the AND-list anchors (a1–a6 are all artifact-exposure/contents/lifecycle; no a-slot is reserved for scale-sensitive production risk).
  • Proposed amendment:
    • File: .claude/agents/planner.md (and its Sonnet/Haiku variants if they exist), in the AND-list charter generation section.

    • Section: Add a mandatory slot to every artifact-producer AND-list. Currently the AND-list covers: a1 Location, a2 Naming, a3 Contents, a4 Lifecycle, a5 Default blast radius, a6 Completeness. Add:

      a7 — Scale / memory envelope (mandatory slot on every artifact-producing AND-list charter)

      Hypothesis: the artifact-production path loads data into PHP memory in a single unbounded query or loop, guaranteeing OOM failure on production databases of >N rows.

      Coverage-note literal required (c2 Reinforcement 3): file as coverage_notes: "scale-sensitive c2 a7: <result> — <file>:<line> <pattern>"

      • If empirical probe runs on large data: "scale-sensitive c2 a7: OOM confirmed — class-mb-backup.php:52 get_results(SELECT * FROM table)"
      • If budget-constrained: "scale-sensitive c2 a7: empirical probe deprioritized out of budget; source pattern filed as <severity> Problem — <file>:<line> SELECT * FROM <table>"

      Why: Every artifact-producing feature that reads database tables is a candidate for this pattern. The charter's primary angle (access, contents, security) routinely exhausts probe budget before scale is reached, and the scale miss silently recurs across pilots.

      Motivating observations: magellan-backups Issue 9 — fourth consecutive miss across Pilots 1, 10, 17, 18 (2026-04-30T07-52-14_magellan-backups). Each prior reinforcement (c2 rule in tester-mindset, Reinforcement 3 in Pilot 17) targeted the Tester; this amendment targets the Planner — the scale hypothesis must be a first-class AND-list anchor, not a skill rule the Tester may or may not recall.

    • Also update skills/tester-mindset/SKILL.md c2 section: add one sentence — "If an artifact-producer AND-list charter does not include an a7 (scale/memory) slot in its hypothesis list, add one before beginning probe execution." This gives the Tester a self-correction path when the Planner's charter is missing the slot.

    • Generalization check: The rule applies to any feature that reads tables (backup, export, report generation, search results, activity log pagination, bulk email send, CSV export, XML sitemap generation). It fires whenever a Planner generates an AND-list charter for an artifact-producing feature, regardless of plugin ecosystem. It does not fire for features that don't write artifacts from database reads.

    • Cross-pilot context: Prior retrospectives document c2 as a "chronic miss class" (Pilots 1, 10, 17) and Pilot 17 shipped c2 Reinforcement 3 specifically to close the recon-only dropout gap. The current miss indicates the Reinforcement 3 coverage-note literal is present in the Tester skill file but has not reached the Planner's charter generation. Moving the anchor from Tester-skill to AND-list template is the architectural fix.


Summary

  • 2 misses out of 10 planted issues
  • 2 amendments proposed
  • Cross-pilot reinforcements:
    • Issue 9 (c2 chronic class): Fourth consecutive miss on this issue across magellan-backups pilots (Pilots 1, 10, 17, and now this run). Reinforces the need to move the scale hypothesis out of the Tester-skill layer and into the Planner's AND-list template as a required anchor slot (a7). Prior reinforcements (c2 rule, Reinforcement 3) targeted the Tester layer — this amendment targets the Planner layer.
    • Issue 3 (option-key parity): New miss class not previously documented. Related to but distinct from the Pilot 2 table-name-mismatch miss. The behavioral-roundtrip angle (did the runtime use the saved value?) is the probe that closes this class; UI roundtrip (does the field re-display?) is insufficient.

Amendment proposals at a glance

Amendment File Section Closes
Option-name parity probe (behavioral roundtrip at the function/option-key level) skills/tester-mindset/SKILL.md Save-path / settings-form probe section Issue 3 miss class (save-key ≠ read-key, silent functional failure)
a7 scale/memory slot as mandatory AND-list anchor in artifact-producer charters .claude/agents/planner.md (all variants) AND-list charter generation template Issue 9 chronic miss class (fourth occurrence)

Testing Report — magellan-backups

Run ID: 2026-04-30T07-52-14_magellan-backups Generated: 2026-04-30T08:19:53.617Z Plugin version: 1.0.0 Sessions processed: 8


Executive summary

Category Count
Problems 25
Questions 6
Improvements 20
Praises 18

Problem severity breakdown

Severity Count
critical 7
major 14
minor 4
trivial 0

Severity heatmap by area

Area Critical Major Minor Trivial Risk score
Backup Restore — destructive operation 0 2 2 0 10
Full backup creation — Location (a1) 1 0 0 0 4
Full backup creation — Contents (a3 leakage direction) 1 0 0 0 4
F7 — Backup directory access control 1 0 0 0 4
Cross-feature seam: manual backup (AJAX) × scheduled cron 1 0 0 0 4
Selective export — file location and access control 1 0 0 0 4
Selective export — Users table contents 1 0 0 0 4
Selective export — wp_options table contents 1 0 0 0 4
Full backup creation — Naming (a2) 0 1 0 0 3
Full backup creation — Contents (a3 omissions) and Completeness against UI claim (a6) 0 1 0 0 3
Full backup creation — Lifecycle (a4) 0 1 0 0 3
Full backup creation — Default blast radius (a5) 0 1 0 0 3
F1 — Backup & Restore tab 0 1 0 0 3
Cron gating and schedule settings 0 1 0 0 3
Schedule settings form 0 1 0 0 3
Selective export — Posts table filtering 0 1 0 0 3
Selective export — artifact cleanup on deactivation 0 1 0 0 3
Admin UI — Backup & Restore tab progress indicator 0 1 0 0 3
Admin UI — Restore operation (b3 defect) 0 1 0 0 3
Admin UI — Restore operation (b5 defect) 0 1 0 0 3
Backup Create — UI feedback 0 0 1 0 2
Schedule settings form — Notification Email field 0 0 1 0 2
Full backup creation — Security 0 0 0 0 0
Full backup creation — Data integrity 0 0 0 0 0
Full backup creation — Contents 0 0 0 0 0
Full backup creation — Completeness 0 0 0 0 0
Full backup creation — Lifecycle 0 0 0 0 0
Full backup creation — User consent 0 0 0 0 0
Full backup creation — AJAX response 0 0 0 0 0
F5 — Schedule configuration (mode-toggle behavior) 0 0 0 0 0
F4 — Selective Export (artifact contents & sensitive data) 0 0 0 0 0
F1 — Backup & Restore tab (UX feedback) 0 0 0 0 0
F4 — Selective Export (documentation) 0 0 0 0 0
F2/F3 — Existing Backups empty state 0 0 0 0 0
F3 — Delete action CSRF protection 0 0 0 0 0
F5 — Schedule form save feedback 0 0 0 0 0
F6 — Cron integration 0 0 0 0 0
Cross-tab consistency 0 0 0 0 0
Plugin lifecycle hooks (activation/deactivation/uninstall) 0 0 0 0 0
Plugin uninstall cleanup 0 0 0 0 0
Plugin residue handling 0 0 0 0 0
Schedule settings form save 0 0 0 0 0
File naming and concurrency 0 0 0 0 0
Schedule settings gating 0 0 0 0 0
UI feedback on concurrent operations 0 0 0 0 0
Backup creation and restoration flow 0 0 0 0 0
Backup Restore — error recovery 0 0 0 0 0
Backup Restore — data loss prevention 0 0 0 0 0
Backup Restore — operation transparency 0 0 0 0 0
Backup Delete — capability gate 0 0 0 0 0
Backup listing — empty state handling 0 0 0 0 0
Backup Delete — UI confirmation 0 0 0 0 0
Schedule settings form — UX feedback 0 0 0 0 0
Schedule settings form — Enable toggle functionality 0 0 0 0 0
Selective export — UX feedback 0 0 0 0 0
Selective export — default blast radius 0 0 0 0 0
Selective export — cross-contamination prevention 0 0 0 0 0
Selective export — filename uniqueness 0 0 0 0 0
Admin JavaScript implementation 0 0 0 0 0
Restore feature — dry-run capability (b3) 0 0 0 0 0
Restore feature — rollback capability (b5) 0 0 0 0 0
Admin UI — progress bar state management (H4) 0 0 0 0 0
Data integrity — restore round-trip validation (H1) 0 0 0 0 0
Security — capability gates on AJAX handlers 0 0 0 0 0

Risk score = 4·critical + 3·major + 2·minor + 1·trivial

Top problems

1. [CRITICAL] Backup files publicly downloadable without authentication

  • Area: Full backup creation — Location (a1)
  • Persona affected: admin
  • Confidence: 1
  • Session: backup-artifact-andlist

Steps to reproduce:

  1. Create a full backup via admin.php?page=mb-backups
  2. Note the filename (e.g., backup-2026-04-30-0805.zip)
  3. From any browser (authenticated or not), curl http:///wp-content/magellan-backups/.zip
  4. Observe HTTP 200 response with ZIP file contents

Expected: Backup files protected by .htaccess, directory index.php, or server-side authentication gate. Unauthenticated GET requests return 403 or 404.

Actual: No protective files (.htaccess/index.php) created in wp-content/magellan-backups/ on plugin activation. Direct HTTP GET to backup file URL returns 200 with ZIP contents. Any unauthenticated visitor can download complete backups containing database dumps and source code.

Evidence: [console](sessions/backup-artifact-andlist/curl -w '%{http_code}' http)

Notes: Recon S2 flagged absence of .htaccess/index.php. This is a1 AND-list anchor. Directory listing returns 404 but direct file access allowed — access control incomplete.

2. [CRITICAL] Backup ZIP contains plaintext user password hashes (sensitive data leakage)

  • Area: Full backup creation — Contents (a3 leakage direction)
  • Persona affected: admin
  • Confidence: 1
  • Session: backup-artifact-andlist

Steps to reproduce:

  1. Create a full backup; note filename
  2. unzip -p .zip database.sql | grep 'INSERT INTO.*wp_users'
  3. Observe plaintext password hashes in wp_users table rows

Expected: Backup SQL excludes sensitive columns (user_pass, user_email where possible, wp_options API keys) OR includes redaction/anonymization logic.

Actual: Database dump includes complete wp_users table with plaintext password hashes. Example: admin user password '$wp$2y$10$vz.l3CsRdc6xxOaXXqQD9uAUIg1y3K8xzKLrlQzJLi2ZSzSIynd2S' exposed. wp_options table includes siteurl, admin_email, and potentially API keys (e.g., mailserver_pass).

Evidence: [console](sessions/backup-artifact-andlist/unzip -p backup-2026-04-30-0805.zip database.sql | grep user_pass → (multiple rows with hashes))

Notes: This is a3 AND-list anchor (leakage direction). User password hashes directly hashable via rainbow table attack. Critical security vulnerability if backup downloaded by unauthenticated user (see also a1 finding).

3. [CRITICAL] Manual backup and cron backup write to same-minute filename without lock, causing silent overwrite

  • Area: Cross-feature seam: manual backup (AJAX) × scheduled cron
  • Persona affected: admin
  • Confidence: 1
  • Session: manual-cron-cross-feature

Steps to reproduce:

  1. Ensure cron is registered (activate plugin, verify mb_scheduled_backup in wp cron event list)
  2. Clear wp-content/magellan-backups/ directory
  3. Fire cron via: studio wp --path= cron event run mb_scheduled_backup
  4. Note the backup filename: backup-YYYY-MM-DD-HHMM.zip (minute precision)
  5. Within the same minute, click 'Create Full Backup' button in admin UI
  6. Check wp-content/magellan-backups/ directory
  7. Observe: only ONE file exists (the second write overwrote the first)

Expected: Two distinct backup files in the directory (one from cron, one from manual), OR an error message preventing the overwrite

Actual: Only one backup file exists. The second write (manual AJAX) opened the same filename with ZipArchive::OVERWRITE flag, silently overwriting the first backup created by cron.

Evidence: · console · [trace](sessions/manual-cron-cross-feature/turn 6 empirical probe log: cron fired 10:06 → created backup-2026-04-30-0806.zip (13 MB). Manual AJAX triggered same minute → file count remained 1 (overwrite, not append))

Notes: Root cause: magellan-backups.php line 21 uses date('Y-m-d-Hi') for filename (minute precision), and class-mb-backup.php line 25 uses ZipArchive::OVERWRITE when opening the file. No file lock or mutex exists. Two concurrent writes to the same minute-precision filename → race condition → silent data loss. The admin loses one backup silently with no warning or error message.

4. [CRITICAL] Exported SQL files are publicly accessible without authentication (a1 — Location)

  • Area: Selective export — file location and access control
  • Persona affected: admin
  • Confidence: 1
  • Session: selective-export-artifact-andlist

Steps to reproduce:

  1. As admin, navigate to Magellan Backups → Selective Export
  2. Check Posts checkbox, click 'Export Selected'
  3. Observe export file created (e.g., export-2026-04-30-0805.sql)
  4. As unauthenticated user (any visitor), access http://localhost:8891/wp-content/magellan-backups/export-2026-04-30-0805.sql

Expected: HTTP 403 Forbidden or 404; file not accessible without authentication

Actual: HTTP 200; SQL file contents fully downloaded and readable without any authentication; unauthenticated users can read all exported data

Evidence: [console](sessions/selective-export-artifact-andlist/curl -w HTTP %{http_code} returned 200; file contents retrieved successfully)

Notes: Export directory (wp-content/magellan-backups/) is publicly readable. File should be stored in a non-web-accessible location (e.g., /wp-content/uploads/private/) or protected via .htaccess/index.php gate.

5. [CRITICAL] Users export exposes plaintext password hashes (a3-users — password hash leakage)

  • Area: Selective export — Users table contents
  • Persona affected: admin
  • Confidence: 1
  • Session: selective-export-artifact-andlist

Steps to reproduce:

  1. As admin, navigate to Magellan Backups → Selective Export
  2. Check Users checkbox only, click 'Export Selected'
  3. Examine generated SQL file (e.g., export-2026-04-30-0806.sql)
  4. Search for INSERT INTO wp_users row or grep for user_pass / $wp$

Expected: Users export contains user metadata (user_login, user_email) but NOT user_pass column; passwords redacted or omitted

Actual: Users export contains unredacted user_pass column with bcrypt hash ($wp$2y$10$...) for every user; plaintext password storage key exposed alongside user credentials

Evidence: [console](sessions/selective-export-artifact-andlist/INSERT INTO wp_users VALUES('1','admin','$wp$2y$10$6AaJU6zzUguTxdbqNGBlxe7D73G4i.RQ9OQvGYYOy5DV1kIxr9QVm','admin','admin@localhost.com',...))

Notes: Bcrypt hashes can be subjected to offline dictionary/brute-force attacks if the export is captured. This is a recon S5 highest-priority finding. Export handler must filter user_pass column or redact with placeholder.

6. [CRITICAL] Options export exposes WordPress authentication keys (a3-options — secrets leakage)

  • Area: Selective export — wp_options table contents
  • Persona affected: admin
  • Confidence: 1
  • Session: selective-export-artifact-andlist

Steps to reproduce:

  1. As admin, navigate to Magellan Backups → Selective Export
  2. Check Options checkbox only, click 'Export Selected'
  3. Examine generated SQL file (e.g., export-2026-04-30-0807.sql)
  4. Search for auth_key, logged_in_key, nonce_salt

Expected: Options export omits or redacts sensitive option keys (auth_key, secure_auth_key, logged_in_key, nonce_salt, logged_in_salt); only safe option keys exported

Actual: Options export includes auth_key, logged_in_key, nonce_salt unredacted; plaintext WordPress authentication secrets exposed in SQL file

Evidence: [console](sessions/selective-export-artifact-andlist/INSERT INTO wp_options VALUES('132','auth_key','{...}','off'); INSERT INTO wp_options VALUES('134','logged_in_key','X3)

Notes: These keys are used to sign cookies and nonces. If exported file is leaked (due to a1 public access vulnerability), attacker can forge session cookies and CSRF tokens. This is a recon S5 highest-priority finding. Export handler must implement option-name blocklist (auth_key, secure_auth_key, logged_in_key, logged_in_salt, nonce_salt, auth_salt, secure_auth_salt).

7. [CRITICAL] Backup files in wp-content/magellan-backups/ are publicly accessible without authentication

  • Area: F7 — Backup directory access control
  • Persona affected: guest
  • Confidence: 0.99
  • Session: breadth-tour

Steps to reproduce:

  1. As admin: Create a full backup via Backup & Restore tab (File: backup-2026-04-30-0814.zip)
  2. Note the backup file URL from the Existing Backups table (e.g., http://localhost:8890/wp-content/magellan-backups/backup-2026-04-30-0814.zip)
  3. Open an incognito/private browser window (or use curl as an unauthenticated client)
  4. Navigate to or request the backup file URL directly without authentication
  5. Observe the HTTP response status code

Expected: HTTP 403 Forbidden or HTTP 401 Unauthorized response; backup files should require admin authentication

Actual: HTTP 200 OK response; the backup file is served to unauthenticated guests without requiring login

Evidence: [console](sessions/breadth-tour/curl -o /dev/null -w '%{http_code}' http)

Notes: This is a critical security vulnerability. Backup files contain sensitive data (database with user credentials, uploaded media, configuration options). Any unauthenticated visitor can download complete backups of the website. Remediation: Add .htaccess to deny direct access, or implement a nonce-based download handler that requires admin authentication. Recon S2 correctly identified this as a concern area.

8. [MAJOR] Backup filename collision: same-minute backups overwrite without warning

  • Area: Full backup creation — Naming (a2)
  • Persona affected: admin
  • Confidence: 1
  • Session: backup-artifact-andlist

Steps to reproduce:

  1. Create a full backup via admin.php?page=mb-backups; note filename backup-YYYY-MM-DD-HHMM.zip
  2. Within the same minute, click 'Create Full Backup' again
  3. List files in wp-content/magellan-backups/
  4. Observe single file instead of two; second backup overwrote first

Expected: Two distinct backup files created. Either: (1) filename includes seconds/microseconds, (2) collision detection prevents overwrite, or (3) user warning raised if collision detected.

Actual: Backup filenames use minute-precision only (YYYY-MM-DD-HHMM). Two backups created at 10:05:20 and 10:05:24 both generated backup-2026-04-30-0805.zip. Second backup silently overwrote first. No warning displayed to user.

Evidence: [console](sessions/backup-artifact-andlist/ls wp-content/magellan-backups/ after double-create)

Notes: This is a2 AND-list anchor. Silent data loss if user creates backups within same minute. Recon S3 observed instantaneous AJAX response; backup creation is synchronous.

9. [MAJOR] Full Backup omits wp-content/uploads/ directory (contradicts UI label)

  • Area: Full backup creation — Contents (a3 omissions) and Completeness against UI claim (a6)
  • Persona affected: admin
  • Confidence: 1
  • Session: backup-artifact-andlist

Steps to reproduce:

  1. Create a full backup
  2. unzip -l .zip | grep uploads
  3. Observe 0 results; uploads directory absent
  4. Check UI label: 'Creates a full backup of your database and wp-content directory'
  5. Expectation: Full Backup includes all wp-content subdirectories

Expected: Full Backup ZIP contains database dump PLUS complete wp-content directory including uploads/, plugins/, themes/, and mu-plugins/.

Actual: ZIP contains database.sql, wp-content/themes/ (with font files, styles), wp-content/plugins/ (stubs), but NO wp-content/uploads/ directory. User media assets are excluded from 'Full Backup'.

Evidence: [console](sessions/backup-artifact-andlist/unzip -l backup-2026-04-30-0805.zip | grep -c uploads → 0)

Notes: This combines a3 (omissions direction) and a6 (completeness against UI claim) AND-list anchors. User expectations: Full Backup = complete site snapshot. Reality: media files excluded. Restore from this backup would lose all uploaded images/documents.

10. [MAJOR] Backup files not deleted on plugin deactivation

  • Area: Full backup creation — Lifecycle (a4)
  • Persona affected: admin
  • Confidence: 1
  • Session: backup-artifact-andlist

Steps to reproduce:

  1. Create a full backup; list wp-content/magellan-backups/
  2. Note backup file size and timestamp
  3. Deactivate plugin via WP-CLI: studio wp plugin deactivate magellan-backups
  4. List wp-content/magellan-backups/ again
  5. Observe backup file still present

Expected: Plugin deactivation hook (register_deactivation_hook) removes all stored backup artifacts to clean up disk space.

Actual: After plugin deactivation, backup ZIP files remain in wp-content/magellan-backups/. No cleanup performed. Disk space occupied by backups is not reclaimed until manual deletion.

Evidence: [console](sessions/backup-artifact-andlist/Before deactivation)

Notes: This is a4 AND-list anchor. Orphaned backup files consume disk space indefinitely. Not a data loss issue but a resource management issue.

Needs human review (confidence < 0.7)

None.

Questions raised

  • [F5 — Schedule configuration (mode-toggle behavior)] When scheduled backups are enabled, is there a visible admin UI indicator (outside the Schedule tab itself) that shows scheduled backups are active?
    • Why it matters: Admins need at-a-glance awareness that scheduled backups are running. If the only indicator is the checkbox on the Schedule tab itself, admins must remember to check that tab to verify the status. A dashboard widget, admin bar indicator, or notification would improve visibility.
  • [F4 — Selective Export (artifact contents & sensitive data)] What content types and database tables does the Selective Export feature export, and does it include any sensitive data (user passwords, API keys, options containing credentials)?
    • Why it matters: The export feature has checkboxes for Posts, Pages, Users, and Options. 'Options' is broad and may include sensitive configuration (API keys, license keys, auth tokens). If exported without proper sanitization, these could be accidentally shared. This is noted for deeper analysis in the selective-export-artifact-andlist charter.
  • [Schedule settings form save] Why does the Enable toggle checkbox click in the UI not persist to the database? The toggle was clicked (ref e174 via snapshot), save button was clicked (ref e188), but studio wp option get mb_schedule_enabled still returned 1.
    • Why it matters: This prevented empirical verification of whether the cron respects the Enable toggle when it is set to 0. The UI may have a nonce failure, form submission issue, or JavaScript interaction problem.
  • [Schedule settings form] Is the Notification Email field truly optional, or should it be required for the scheduled backup feature to function?
    • Why it matters: The form saves without error when email is blank, but if email gates failure notifications (per recon S5), blank email means admins won't be notified of backup failures.
  • [Schedule settings form] What is the intended time format for the Schedule time field: 24-hour ('14:00') or 12-hour ('2:00 PM')?
    • Why it matters: The form's dropdown displays 24-hour format, but the database stores 12-hour format, causing a roundtrip mismatch where saved values don't re-display correctly.
  • [Admin JavaScript implementation] Does the progress bar connect to actual backup progress via the mb-admin.js script, or is the 100% width hardcoded permanently?
    • Why it matters: If the progress bar is not wired to the backup operation, it provides no feedback about backup status or completion. This would be a significant UX gap.

Suggested improvements

  • [Full backup creation — Security] Add .htaccess file generation in wp-content/magellan-backups/ on plugin activation to prevent direct HTTP access to backup files (effort: low) (impact: high)
    • Rationale: Backup ZIPs contain sensitive data (password hashes, wp_options secrets). Access should require authentication.
  • [Full backup creation — Data integrity] Implement filename collision detection or use microsecond/random-suffix in backup filenames (e.g., backup-YYYY-MM-DD-HHMM-.zip) (effort: low) (impact: high)
    • Rationale: Silent overwrite of backups created within the same minute is data loss. Microsecond precision or random suffix prevents collisions.
  • [Full backup creation — Contents] Implement redaction for wp_users.user_pass and sensitive wp_options (e.g., mailserver_pass, API keys) in backup SQL (effort: medium) (impact: high)
    • Rationale: Backup SQL exposes plaintext password hashes and credentials. Redaction mitigates impact if backup is compromised.
  • [Full backup creation — Completeness] Include wp-content/uploads/ directory in Full Backup ZIP (rename feature to 'Database + wp-content Backup' if uploads are intentionally excluded) (effort: medium) (impact: high)
    • Rationale: UI label 'Full Backup' implies complete site snapshot. User media files are critical to restoration. Excluding uploads silently breaks restore expectation.
  • [Full backup creation — Lifecycle] Add register_deactivation_hook to clean up backup artifacts when plugin is uninstalled (effort: low) (impact: medium)
    • Rationale: Orphaned backup files consume disk space indefinitely. Cleanup on deactivation prevents bloat.
  • [Full backup creation — User consent] Only register mb_scheduled_backup cron when user explicitly enables schedule via Schedule tab UI toggle (effort: low) (impact: high)
    • Rationale: Default blast radius: cron fires without user opt-in. Implement proper gating so scheduled backups only run if user enables the feature.
  • [F1 — Backup & Restore tab (UX feedback)] Consider adding an explicit success/status message after backup completion (e.g., 'Backup completed successfully!' or 'Last backup: 2026-04-30 at 08:14') (effort: low) (impact: medium)
    • Rationale: While the backup completes and appears in the Existing Backups table, users might not realize the operation finished if they're not watching the page. A success notice provides immediate feedback. Currently, the page state changes but there's no notice/confirmation message (unlike the Schedule form which has 'Schedule saved.').
  • [F4 — Selective Export (documentation)] Document which options are included in 'Options' export and whether any credentials/API keys are sanitized before export (effort: low) (impact: low)
    • Rationale: Users exporting selective data need to know whether they should treat the export as sensitive (with restricted distribution) or safe to share. Explicit documentation removes ambiguity.
  • [File naming and concurrency] Use file locks (flock) or atomic rename pattern (write to temp file, then rename) to prevent overwrite collisions. Alternatively, add sequence numbers or millisecond precision to filenames to ensure uniqueness even within the same minute. (effort: low) (impact: high)
    • Rationale: Current minute-precision filename makes collisions trivial when two backup triggers fire within 60 seconds. Even a single-second race window is problematic in production.
  • [Schedule settings gating] Add a guard check in run_scheduled_backup() callback: if ( ! get_option( 'mb_schedule_enabled' ) ) return; This ensures the Enable toggle actually controls backup execution, not just cron registration. (effort: low) (impact: high)
    • Rationale: Currently the toggle is a no-op if manually firing the cron via WP-CLI or if the cron is registered from a previous install before the Enable toggle was added.
  • [UI feedback on concurrent operations] Add a warning or notification if a backup operation is already in progress when the user clicks 'Create Full Backup' again. Currently the page reloads silently after AJAX completes (admin.js line 13), potentially hiding collision-related errors from the admin. (effort: medium) (impact: medium)
    • Rationale: The page reload hides the result of the second backup attempt. If the second backup overwrites the first, there's no indication to the admin that a collision occurred.
  • [Backup Restore — error recovery] Implement automatic email notifications to site admins if a restore fails partway through, with a link to a diagnostic page showing which tables/files were updated before failure. (effort: medium) (impact: high)
    • Rationale: If a restore fails mid-operation, admins may not realize the database is in an inconsistent state. An automated alert would help them contact support or manually repair the database.
  • [Backup Restore — data loss prevention] Add an optional 'Create pre-restore backup' toggle in the restore confirmation dialog, defaulting to ON. This would give admins the choice to automatically create a safety backup before the restore begins. (effort: low) (impact: high)
    • Rationale: Users expect destructive operations to have a safety net. Offering a one-click pre-restore backup would prevent data loss in case of user error or restore failure.
  • [Backup Restore — operation transparency] Display a 'dry-run results' summary before restore commits: count of posts/pages to be restored, list of custom tables, file count, estimated restore time. Let admins review before proceeding. (effort: medium) (impact: medium)
    • Rationale: Admins need visibility into what will change during restore. A summary would reduce surprise and help them decide whether to proceed.
  • [Schedule settings form — UX feedback] Add an inline confirmation or summary message after saving, showing the selected schedule frequency and time (e.g., 'Schedule configured: Weekly at 14:00'). Current 'Schedule saved.' message does not confirm what was actually configured. (effort: low) (impact: medium)
    • Rationale: Reduces cognitive load; helps admin verify their configuration is correct before closing the page
  • [Selective export — UX feedback] Add warning message to Selective Export page: 'Warning: exported files will contain sensitive data (passwords, API keys). Ensure you delete exports immediately after download and restrict access to export directory.' (effort: low) (impact: medium)
    • Rationale: Users may not realize the export includes passwords and secrets; a prominent warning encourages careful handling.
  • [Restore feature — dry-run capability (b3)] Implement a dry-run or preview mode for restore operations. Add a checkbox labeled 'Preview changes before restoring' in the restore confirmation dialog. When enabled, display a summary of changes (files affected, database tables that will be dropped/modified) before executing the restore. Only proceed if user confirms. (effort: medium) (impact: high)
    • Rationale: Allows admins to understand the impact of a restore before committing to it. Reduces risk of accidental data loss or unexpected changes.
  • [Restore feature — rollback capability (b5)] Add automatic pre-restore backup and undo capability. Before executing a restore, create a temporary backup of the current state. Provide an 'Undo Last Restore' button that restores from this snapshot. Clean up old snapshots after 24 hours or manual confirmation. (effort: high) (impact: high)
    • Rationale: Provides a safety net for restore operations. If the restore produces unexpected results, admins can immediately undo it without manually selecting another backup.
  • [Admin UI — progress bar state management (H4)] Initialize progress bar to 0% on page load and properly wire it to the backup operation via JavaScript. During backup, update the width via AJAX responses. After completion, set to 100%. Optionally add a status label: 'Idle / In Progress / Complete'. (effort: low) (impact: medium)
    • Rationale: Users can distinguish the idle state from an in-progress or completed backup. Provides visual feedback about backup status.
  • [Data integrity — restore round-trip validation (H1)] Implement and document a comprehensive empirical test validating restore round-trip correctness. Test scenario: create backup with known site state → modify the site (add post, install plugin, etc.) → restore the backup → verify site state matches pre-modification state. Include both database and file system changes. (effort: medium) (impact: high)
    • Rationale: The restore logic appears sound in code review (SQL import + file extraction), but should be validated empirically to ensure no data loss or corruption during round-trip.

What works well (praises)

  • [Full backup creation — AJAX response] Backup creation AJAX request completes within reasonable time for site size (~13 MB ZIP generated in ~5 seconds, synchronous operation)
    • Why: No timeout or hang observed; backup tool is responsive even for moderately-sized sites.
  • [F2/F3 — Existing Backups empty state] The Existing Backups table displays an explicit 'No backups found.' message when the backup directory is empty
    • Why: This is correct UX practice. Rather than rendering an empty table or showing no feedback, users see a clear, actionable message. Prevents confusion about whether the feature is working.
  • [F3 — Delete action CSRF protection] The Delete backup action includes a nonce parameter (_wpnonce) in the URL and is further protected by a JavaScript confirmation dialog
    • Why: Layered defense: nonce validates the request came from an authorized admin session; JS confirm prevents accidental deletion. Good security practice.
  • [F5 — Schedule form save feedback] After saving the schedule, a green success notice 'Schedule saved.' appears at the top of the page, and the form preserves the enabled/disabled state
    • Why: Clear, immediate feedback confirms the user's action succeeded. Form state persistence ensures users don't lose configuration on reload. Good UX.
  • [F6 — Cron integration] Plugin correctly registers the mb_scheduled_backup cron event when scheduled backups are enabled, and the event is visible in wp cron event list
    • Why: Proper WordPress integration. Admins can verify scheduled backups are configured via WP-CLI, supporting debugging and auditing.
  • [Cross-tab consistency] All three plugin tabs (Backup & Restore, Selective Export, Schedule) render without JavaScript console errors or PHP warnings
    • Why: Clean code quality. No console noise, no error-handling surprises during normal admin navigation.
  • [Plugin lifecycle hooks (activation/deactivation/uninstall)] Cron event registration and cleanup work correctly — activation registers mb_scheduled_backup with 1-day recurrence; deactivation clears it; re-activation does not create duplicates
    • Why: Correct lifecycle hook implementation is foundational to plugin reliability and prevents resource leaks (orphaned cron events) and silent double-backups
  • [Plugin uninstall cleanup] Options are properly cleaned up on uninstall via the uninstall hook — no orphaned mb_* options remain in wp_options
    • Why: Proper cleanup on uninstall prevents database bloat and ensures clean removal without requiring manual database intervention
  • [Plugin residue handling] Previous-installation options are preserved gracefully on re-activation — no PHP errors or conflicts with existing option values
    • Why: Upgrade scenarios (plugin uninstalled and reinstalled) or multi-environment deployments benefit from graceful handling of leftover options without forcing data loss
  • [Backup creation and restoration flow] The core backup mechanism (database dump + wp-content directory recursion into ZIP) is structurally sound and creates valid archives. Restore from existing backup and upload-restore flows demonstrate competent WordPress integration.
    • Why: The plugin correctly uses ZipArchive, recursively adds directories, escapes SQL values in dumps, and integrates with WordPress options/nonce APIs. The conceptual architecture is solid; the bugs are in the concurrency/gating layer, not the core mechanics.
  • [Backup Delete — capability gate] The delete handler correctly requires 'manage_options' capability before allowing deletion, AND verifies the CSRF nonce. Admins cannot be tricked into deleting backups via forged links.
    • Why: Proper permission checking on destructive operations prevents unauthorized users from accidentally or maliciously deleting backups. The combination of capability + nonce is the gold standard for WordPress security.
  • [Backup listing — empty state handling] When no backups exist, the UI displays 'No backups found.' instead of silently showing an empty table. This is user-friendly and discoverable.
    • Why: Empty-state handling is a common UX bug. This plugin got it right — users know immediately whether they have backups or not.
  • [Backup Delete — UI confirmation] The Delete link shows a browser confirm() dialog: 'Delete this backup?' before proceeding. This is a clear, standard UI pattern that protects against accidental deletion.
    • Why: Confirmation dialogs on destructive operations are a user-expectation baseline. This plugin implements it correctly.
  • [Schedule settings form — Enable toggle functionality] The Enable toggle correctly gates the cron event lifecycle: checking Enable registers the mb_scheduled_backup cron event; unchecking and saving removes it cleanly
    • Why: Demonstrates proper cleanup and prevents unintended background tasks from running. Mode-toggle behavior is implemented correctly per charter requirements.
  • [Selective export — default blast radius] Export requires explicit user action and is not auto-triggered by cron
    • Why: No risk of automated data exfiltration; exports only occur when admin explicitly clicks 'Export Selected' button.
  • [Selective export — cross-contamination prevention] Posts-only export does not include Users or Options data
    • Why: Correct implementation of selective filtering; each export type cleanly separated.
  • [Selective export — filename uniqueness] Export filenames use minute-precision timestamp (YYYY-MM-DD-HHMM), preventing same-minute collisions under normal admin usage
    • Why: Admins unlikely to trigger two exports in the exact same minute; timestamp granularity is adequate for practical use.
  • [Security — capability gates on AJAX handlers] Both mb_run_backup and mb_run_restore AJAX handlers independently check current_user_can('manage_options') at the handler level
    • Why: This is correct security practice and prevents privilege escalation via direct AJAX calls. Low-privilege users (editor, author, subscriber) will receive a 'Unauthorized' JSON error response if they attempt to call these actions directly.

Coverage gaps

Session Status Turns Flows Notes
backup-artifact-andlist complete 12/12 8/8 All six AND-list anchors (a1–a6) probed empirically. Five critical/major defects confirmed: a1 (public download without .htaccess), a2 (minute-precision filename collision), a3 leakage (user_pass hashes exposed), a3 omissions + a6 (uploads/ missing from Full Backup), a4 (no cleanup on deactivation), a5 (cron fires unconditionally). Progress bar indicator state not visually probed due to turn budget constraint, but network/AJAX behavior observed as instantaneous. Default blast radius probed: mb_scheduled_backup cron confirmed present before user enables schedule (Y). Scale-sensitive c2 fallback: source pattern inspection of backup code not performed due to time budget; empirical probe at actual site size (single site, ~13MB backup) completed successfully.
breadth-tour complete 18/30 7/8 empty-state probed: Existing Backups table at zero rows → explicit 'No backups found.' message (pass); progress-indicator probed: progress bar element on load → 100% hardcoded state confirmed before any backup runs (defect); guest HTTP probe for F7: curl to backup file URL returned 200 (public access confirmed); three admin tabs visited in browser (Backup & Restore, Selective Export, Schedule) with console errors checked on each (no JS errors found); mode-indicator visible on Schedule tab: Enable toggle checkbox shows checked state when scheduled backups are enabled (good practice); recon cross-reference: S1 (progress bar) confirmed as hardcoded defect, S2 (backup directory access) confirmed as critical, S4 (delete CSRF) refuted (nonce present), S5 (selective export sensitive data) deferred to selective-export-artifact-andlist charter; deleted backup files to test empty state; confirmed cron event registration post-enable via CLI
manual-cron-cross-feature complete 8/10 4/5 cross-feature interaction probed: manual backup (AJAX) × scheduled cron (mb_scheduled_backup) → Y: shared-resource collision (same-minute filename overwrites). Empirical probe: fired cron at 10:06, immediately triggered manual backup in same minute; expected two files, observed one (overwrite via ZipArchive::OVERWRITE). Enable toggle gating: UI interaction did not persist; source-pattern fallback applied (run_scheduled_backup() has no check for mb_schedule_enabled option).
restore-delete-destructive-andlist complete 9/12 5/6 All seven b1-b7 destructive-operation AND-list anchors enumerated (see hypotheses_status below). b7 (concurrent ops) marked 'deprioritized' due to complexity. Empty-state probed: 'No backups found.' message displays correctly. CSRF nonce verified: wp_nonce_url with mb_delete_ key present and verified server-side. Default blast radius probed: restore overwrites entire DB and wp-content without pre-snapshot. Progress bar starts at 100% (confirmed UI defect, planted bug).
schedule-settings-cluster complete 8/8 5/5 All planned flows executed within budget. H4 (toggle-state-leak probe) was executed implicitly through Enable/Disable cycle; schedule settings (frequency/time) were verified in cron output but not re-enabled and rechecked due to turn budget. Mode-affected surface tour (Step 8.10) completed: cron present when Enable=ON → Y; cron absent when Enable=OFF → Y.
selective-export-artifact-andlist complete 12/12 6/8 All six artifact AND-list anchors (a1–a6) independently probed and verdicted. Multi-surface a3 rule satisfied: a3-posts, a3-users, and a3-options each tested on their respective export types. Scale-sensitive c2 fallback: selective export SQL generation code iterates over selected content without apparent LIMIT; source pattern deferred due to budget, but empirical artifacts generated in Studio environment without OOM; no Problem filed due to scale being environmental (SQLite, test site).
supplementary-restore-ajax-progressbar complete 12/12 4/6 Progress bar rendered at 100% on page load (hardcoded in HTML); AJAX handlers enforce manage_options capability; dry-run/undo affordances absent from UI. H1 (restore round-trip) partially probed via screenshots but not completed due to time constraints — source inspection confirms round-trip capability exists. H3 capability check confirmed via source code review: both ajax_backup() and ajax_restore() enforce current_user_can('manage_options'). progress-indicator probed: mb-progress-fill starts at width: 100% on page load → confirmed-defect. ajax-b6-per-path probed: mb_run_backup handler → 200 with capability gate. restore-round-trip probed: backup→modify→restore→verify → inconclusive (incomplete empirical test but code review confirms capability).

Token usage & cost

Computed from Claude Code transcripts at ~/.claude/projects/<proj-hash>/. Rates from config/pricing.json. Window: 2026-04-30T07:52:14Z2026-04-30T08:19:52Z (with ±10min buffer for dispatch drift).

Estimated total cost for this run: $24.44

Category Cost % of total
Fresh input $0.10 0.4%
Output $2.86 11.7%
Cache-create (5m) $3.93 16.1%
Cache-create (1h) $2.96 12.1%
Cache-read $14.59 59.7%

Manager (main conversation)

Total: $14.13

Model Messages Input Output Cache-5m Cache-1h Cache-read Cost
claude-opus-4-7 20 20 30,129 0 73,141 13,187,445 $8.08
claude-sonnet-4-6 107 168 76,270 0 372,029 8,925,208 $6.05

Subagents (11 invocations)

Total: $10.31

Model Messages Input Output Cache-5m Cache-1h Cache-read Cost
claude-haiku-4-5-20251001 674 54,211 104,256 1,488,468 0 49,192,669 $7.36
claude-sonnet-4-6 44 15,968 29,124 550,969 0 1,332,294 $2.95
Per-subagent breakdown (11 sessions)
Agent ID Type Models Cost
a2caeec8aa01b83a2 tester claude-haiku-4-5-20251001 $0.57
a32c990bea56853f5 tester claude-haiku-4-5-20251001 $0.87
a888d808923cd1657 tester claude-haiku-4-5-20251001 $0.71
aa1a9b57987bdde86 tester claude-haiku-4-5-20251001 $0.70
aa700d944a06ec94f tester claude-haiku-4-5-20251001 $0.66
aaeebeabbf7ad600b tester claude-haiku-4-5-20251001 $1.12
ab1e16d1435e2f34f tester claude-haiku-4-5-20251001 $1.15
ac2b635f02cc54e8a tester claude-haiku-4-5-20251001 $0.48
ae589e21bf1a86af6 tester claude-haiku-4-5-20251001 $1.09
af1c959af5d6a963e planner claude-sonnet-4-6 $2.02
af24d53d494984943 general-purpose claude-sonnet-4-6 $0.93

Recommended next steps

  1. Triage Backup Restore — destructive operation first — highest risk score (10)
  2. Address 7 critical problem(s) before release
  3. Follow up on 7 session(s) with incomplete coverage
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment