Repository navigation
feat(free): add conflict-aware Undo and bounded history - #117
Merged
Merged
Conversation
…tiation serialization
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #110
Refs #106
Refs #107
Refs #108
Refs #109
Refs #111
Conflict-aware Undo for every journal-proven WriteLeash APPLIED item, plus bounded Free history, retention and lifecycle. Undo restores exactly one stored regular-price value through Woo CRUD only when fresh state still proves the applied value and post-apply fingerprint; otherwise typed UNDO_CONFLICT with zero overwrite. Undo is NOT rollback: no reversal of emails, webhooks, remote calls, orders or plugin side effects is claimed.
Undo identity/state model:
Eligibility/fingerprint:
Transaction/fence design:
Crash matrix (real SIGKILL, both engines, default+Redis): kill before save / after save before journal / after journal before COMMIT all roll back to applied with Undo not UNDONE; kill after COMMIT recovers UNDONE with zero replay; lost acknowledgement -> UNDO_NEEDS_REVIEW, later adopted as ALREADY_UNDONE without save.
Partial Undo results: exact eligible/undone/conflict/failed/needs_review counts; UNDO_COMPLETED_WITH_ISSUES, never generic success. Duplicate workers: one restore save, one UNDONE.
History schema: backend recent-jobs/job/item models with apply+Undo counts, undo_eligible, undo_expires_at, typed reasons; bounded pagination; creator/approver/admin scoping.
Retention policy: 30 days (filterable), clock = terminal apply completed_at extended by Undo completion. Bounded keyset-paginated purge of terminal expired history only; active/paused/review/incomplete never purged. Exact COUNT(*) storage accounting.
Purge safety, deactivation/reactivation: in-flight items finish at the fence boundary; no new claims after deactivation; reactivation reconciles stale Undo leases without mutating products.
Uninstall race proof: uninstall.php removes exactly the owned option names (incl. 2 new Undo options; 13-option classifier updated in lockstep) and performs zero DDL by reviewed constraint, so tables/evidence survive a live worker; worker drains at its fence boundary; Woo rows, AS tables and Guard/Strict objects untouched; no auto-restore. Deliberate documented deviation: durable Free tables remain operator lifecycle (as #109 job tables), keeping the evidence-deletion kill condition structurally false.
External side-effect boundary: hook fixture proves apply 1 / Undo 1 / ALREADY_UNDONE 0; original markers never erased.
Explicit #111 non-goals: no Admin screens, preview wizard, history page, progress dashboard. One narrow POST-only start endpoint for tests/#111.
Verification: new wordpress-woo-undo workflow (MySQL 8.0.44 + MariaDB 10.11.15 x default + Redis 7.4.2, Woo 11.1.2, WP 7.1.2, PHP 8.2); existing #107/#108/#109/foundation/adapter suites re-ran green locally.
Do NOT merge. #106 remains OPEN. #110 remains OPEN until maintainer merge. #111 not started.
Repair round (audited blockers 1-4)
Audited head
234126a→ repair head pushed on this branch. No #107/#108/#109 contract change; Woo CRUD, journal, fresh-observer, lease-generation and transactional fencing model preserved.Undo_Repository::history_items()no longer reads one 100-row apply page and slices/filters it in memory. It runs one identifier-qualifiedLEFT JOINwithapply_state/undo_statepredicates in SQL, deterministicsequence ASC, product_id ASC, boundedlimit <= 100, and exposestotal/next_offsetfor the filtered set. Regression: a 170-item job pages0/50/100/150and160,limit=100; Undo/apply-state matches seeded after item 100 are returned and paginated over the filtered logical set.purge_job_atomic()replaces the separate DELETEs: one short transaction per candidate, deterministic job-row-first lock order, eligibility re-evaluated from locked rows, reviewed logical delete order, every DELETE must affect exactly the locked row count, and any error/invariant/lost-transaction rolls the whole candidate back and reportspurge_failed. Regression: ten deterministic failure-injection points (before/after each of the five deletion stages) each reportpurge_failedwith a fresh-observer oracle proving job/job-items/journal/Undo-operation/Undo-items survive; a later clean purge removes the complete history.UNDO_NOT_ELIGIBLE, zero orphan Undo rows — no lock timeout, no partial deletion, no product mutation.Verification at the repaired head: conflict-aware Undo matrix on MySQL 8.0.44 + MariaDB 10.11.15 × default + Redis 7.4.2 (Woo 11.1.2 / WP 7.1.2 / PHP 8.2) green; #107/#108/#109, foundation (both engines), Redirection adapter, engine research, MySQL/MariaDB feasibility, Native PG16 suite and the V0.1 release matrix re-run green.
Lifecycle repair round (fail-closed authority, owned-only scheduler cleanup)
Repair head
dad6853. No #107/#108/#109/#110 domain semantics changed; only the lifecycle gate is tightened.Job_Worker::runner_active()andUndo_Worker::runner_active()now authorize only an explicit durableactivevalue (return 'active' === $value;) and document the intentional fail-closed behavior. Missing (including after uninstall deletes the option), empty,deactivated, malformed and unknown values all stop a worker before its next claim; an item transaction that already acquired its authoritative fence may still finish that one boundary.uninstall.phpnow fail-closes the runner gate first, then cancels exactly the three owned Action Scheduler groups (writeleash-jobs,writeleash-undo,writeleash-maintenance) through the publicActionScheduler::store()->cancel_actions_by_group()API. Unrelated groups/actions and all AS tables are untouched; if AS is unavailable the cleanup is skipped safely and the lifecycle gate still stops new claims. Uninstall stays DDL-free and never drops durable Free tables.UNDONE/ item 2UNDO_PENDINGwith zero second save, and Apply item 1APPLIED/ item 2PENDINGwith the journal stillPENDING.absent,'',deactivatedand an unknown value, each producing zero claims, zero Woo saves, zero price mutation and typedDEACTIVATEDpauses.Woo_Undo_Mutator(lock order is now job row -> Undo operation row).Final lifecycle linearization repair (
d0fd124)The existing indexed
writeleash_runner_stateoption remains the only lifecycle authority. A claim now runs in a short same-handle transaction:LOCK IN SHARE MODEon the existing InnoDBoption_nameunique-index row, lock/verify live lease generation (apply job; Undo parent job then operation), then conditional item UPDATE with both lease EXISTS andoption_value='active'EXISTS. The independent Woo mutation transaction takes that same shared lifecycle record lock before job/Undo/journal/Woo locks and holds it through COMMIT/ROLLBACK. Deactivation's direct indexed UPDATE and uninstall's first indexed DELETE require an exclusive lock; a missing row is never treated as a lock/authority. Uninstall verifies the active row is gone before touching further evidence or AS. Global order: lifecycle -> parent apply job -> Undo operation -> item/journal -> Woo. Initiation/purge remain job-first without holding lifecycle, preserving their audited order.A claimed item whose mutation loses lifecycle authority returns typed
DEACTIVATED, is CAS-released to PENDING with claim token cleared and attempt count refunded, and pauses its job/operation; journal remains PENDING and zero Woo save occurs. Lease-generation loss remainsFENCE_LOST, and unknown outcomes still require review. Chunk-end checks retain review truth when shutdown and budget expiry overlap. The sole adjacent #108 correction detects a coherent different stored regular/active/lookup price as a typed optimistic CONFLICT when a concurrent Woo object read lags storage; malformed/lookup-divergent evidence remains NEEDS_REVIEW and no save or direct Woo SQL is introduced. Undo RUNNING -> PENDING is an explicit legal bounded-chunk transition (existing worker path).Deterministic real-process A/B/C races for both Apply and Undo cover: active PHP read followed by uninstall before claim (zero claim); durable claim followed by uninstall/deactivation before mutation (refund, zero save); and already-fenced item transaction followed by uninstall (indexed shutdown DELETE visibly waits until item COMMIT, then next item blocked). Tests use an independent fresh DB observer,
FOR UPDATE NOWAITand live process-list SQL-wait evidence on MySQL 8.0.44 and MariaDB 10.11.15, default and Redis caches. The previously audited multi-item uninstall, purge/Undo initiation, pagination, atomic purge, #109 takeover and owned-only AS suites remain green. No table drops and no #111 work.