From bfd1287cbb260461003c489e6fb8cd2ead3c2023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Sat, 16 May 2026 13:51:43 +0200 Subject: [PATCH] Cover schema target SQL drift revalidation --- docs/merge-reliability.md | 4 +-- tests/cow/schema_review.php | 65 +++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/docs/merge-reliability.md b/docs/merge-reliability.md index 83201410..f60aef7f 100644 --- a/docs/merge-reliability.md +++ b/docs/merge-reliability.md @@ -19,12 +19,12 @@ when there is a test or document that exercises the specific merge invariant. | --- | --- | --- | | 1. Real WordPress semantic merge coverage | `tests/cow/e2e.sh` creates source and target branches through runtime WordPress requests, validates each branch-local graph before merge, then merges pages, branch-local page edits/deletes with edited content and authors, postmeta, users/usermeta, authors, comments/commentmeta, hierarchical taxonomy terms, nav menus and menu locations, reusable `wp_block` rows, page-to-reusable-block refs, `core/image` block refs and featured-image refs to media attachments, options and JSON options with branch user/object IDs, media uploads with attachment parents plus generated-size metadata/files, a CPT-like `forkpress_note`, and plugin-shaped custom tables/files. The semantic E2E merge now requires `status: completed` and a zero-conflict merge run, so runtime-only state cannot hide behind a surviving object graph. `tests/cow/merge_smoke.php` now fast-gates the basic "page created on branch plus page created on main both survive merge" invariant without starting WordPress. `tests/cow/wp_semantic_validator.php` is a focused fast gate for discovered WordPress semantic validators that catch pages left pointing at deleted reusable blocks or synced patterns, nav menu items left pointing at deleted pages, featured-image postmeta left pointing at deleted attachment rows/files, `core/image` block JSON left pointing at deleted attachment rows/files, term relationships left pointing at deleted taxonomy terms, comments left pointing at deleted posts/users/parent comments, commentmeta left pointing at deleted comments, and options/widgets/theme mods left pointing at deleted WordPress objects. `tests/cow/merge.php` adds deterministic WordPress row fingerprint and validator coverage. | Add broader concurrent edit/delete matrices for complete WP objects and deterministic repair policies only where the owner object is unambiguous. | | 2. Plugin-specific merge semantics | `docs/plugin-merge-validators.md` defines the validator contract, including rejecting contradictory status/finding output and optional first-class `logical_identity` evidence for plugin-defined object identity. `scripts/cow/merge.php` discovers active plugin and mu-plugin validators, runs explicit validators, records plugin-scoped conflicts, and rolls back inline validator failures. `tests/cow/plugin_validator.php` is a focused fast gate for discovered validator review of plugin-owned DB/JSON/file graphs and serialized/JSON option/postmeta/file graphs, plugin-scoped audit output for incoherent JSON, missing or unsafe file references, stale serialized/JSON asset references, identical validator rerun dedupe, contradictory validator output rejection, replacement-evidence revalidation when validator findings change after review, explicit plugin source-evidence drift recorded by validator reruns, and plugin `logical_identity` drift returning reviewed findings to `needs-action`. `tests/cow/merge.php` covers clean custom-table graph merges, validator findings, audit/review grouping, validator rerun evidence, file-root context, active-plugin discovery, explicit-ID plugin graph validation, contradictory validator output rejection, and failed-validator rollback. `tests/cow/e2e.sh` covers a runtime plugin-shaped graph across custom table parent/child rows, child JSON payload refs, JSON, serialized data, options, postmeta, CPT data, and branch-owned file contents. | Add validators for real plugins and add merge drivers only for plugin-owned repairs that can prove correctness. | -| 3. Remaining review-only schema cases | `scripts/cow/merge.php` validates source-added views/triggers/indexes, preserves invalid dependency cases as conflicts, and supports safe schema object resolution for deterministic subsets. `tests/cow/schema_review.php` is a focused fast gate proving acyclic source-added dependent views, views depending on source-added tables, trigger programs, and triggers depending on source-added tables apply in dependency order, cyclic source-added views/triggers stay reviewable, source-added triggers with missing target dependencies stay gated until the dependency is restored, source-added expression unique indexes blocked by target rows stay reviewable until the blocking rows are removed, and reviewed source-added indexes/views/triggers, dropped-table restores, and table rebuild conflicts return to `needs-action` with current source SQL evidence when the reviewed source SQL changes after review. `tests/cow/merge.php` covers broader cyclic/invalid view and trigger dependency handling, source-added dependent view/trigger/index ordering, and rebuild validation cases. | Improve dependency planning for more safe reorderings. Add richer schema revalidation evidence for dependency rebuild plans. Cyclic or semantically ambiguous cases should stay review-only. | +| 3. Remaining review-only schema cases | `scripts/cow/merge.php` validates source-added views/triggers/indexes, preserves invalid dependency cases as conflicts, and supports safe schema object resolution for deterministic subsets. `tests/cow/schema_review.php` is a focused fast gate proving acyclic source-added dependent views, views depending on source-added tables, trigger programs, and triggers depending on source-added tables apply in dependency order, cyclic source-added views/triggers stay reviewable, source-added triggers with missing target dependencies stay gated until the dependency is restored, source-added expression unique indexes blocked by target rows stay reviewable until the blocking rows are removed, and reviewed source-added indexes/views/triggers, dropped-table restores, and table rebuild conflicts return to `needs-action` with current source SQL evidence when the reviewed source SQL changes after review. It also covers target-side SQL drift for reviewed source-added view conflicts, including current target SQL evidence and conservative `unclassified` revalidation. `tests/cow/merge.php` covers broader cyclic/invalid view and trigger dependency handling, source-added dependent view/trigger/index ordering, and rebuild validation cases. | Improve dependency planning for more safe reorderings. Add richer schema revalidation evidence for dependency rebuild plans. Cyclic or semantically ambiguous cases should stay review-only. | | 4. Filesystem merge hardening | `tests/cow/filesystem.php` is a focused fast gate for safe source text/binary file application, conflicting binary file edits staying target-kept with hash payload metadata instead of text decoding, safe relative symlink changes/additions, unsafe absolute/self-referential/managed-path symlinks staying as auditable file conflicts, directory/file type replacements staying review-held until an explicit audited source resolution applies them, and source directory deletions staying review-held when target descendants exist. `tests/cow/media_validator.php` fast-gates discovered upload validators for invalid serialized attachment metadata, invalid original/generated dimensions, malformed or incomplete generated-size metadata, generated-size filename drift, missing original/generated upload files from both `_wp_attached_file` and `_wp_attachment_metadata['file']`, duplicate original/generated upload ownership, unsafe primary/metadata/generated upload paths, and `_wp_attached_file` versus `_wp_attachment_metadata['file']` drift. `tests/cow/merge.php` covers file adds/deletes/conflicts, binary hash comparisons, symlink safety, directory/file and file/directory replacement review, rollback artifacts, upload-file validators, generated attachment file checks, original/generated dimension drift, generated-size filename drift, featured-image/image-block/media metadata drift, and unsafe metadata paths. `tests/cow/e2e.sh` verifies real merged upload originals and generated thumbnails. | Add stricter uploads-specific validators for more drift shapes and explicit attachment-regeneration decisions. | | 5. Crash consistency across DB/files/metadata/Git | `docs/merge-crash-consistency.md` lists the covered boundaries. `tests/cow/merge.php` covers target DB, metadata, file, rollback-failure, ID-band, and whole-branch rollback paths. `tests/cow/e2e.sh` drives public merge/create/reset/recover crash/retry flows for DB, metadata, before-file, after-file, recovery-cleanup, branch-birth, branch-reset publication failpoints, and actual smart-HTTP Git-created branch pushes interrupted before branch-birth metadata, before branch-list publication, and after branch-list publication, each verified after a fresh server restart. `tests/cow/git_server.php` covers Git-created branch birth, Git update/delete, stale cleanup, and object-prune interruption. | Broaden external kill harness coverage across the remaining Git-push failpoints and platform-specific APFS/cleanup checkpoints, then verify post-crash state from a fresh process. | | 6. Branch birth always captures merge bases | `crates/forkpress-storage/src/lib.rs` requires branch birth metadata for branch reuse/merge and blocks pending reset states. `tests/cow/branch_birth.php` fast-gates required ID bands, keyless row identities, filesystem merge-base capture as a frozen pre-write snapshot with managed DB/config/Git exclusions, cleanup of rollback metadata, and cleanup isolation for unrelated branch metadata. `tests/cow/git_server.php` covers Git-created branch DB/file base, ID-band, row identity, and cleanup/rollback paths. `tests/cow/e2e.sh` covers public create retry after interrupted birth metadata, public reset retry after interrupted reset publication, and remote-cache branch creation followed by AUTOINCREMENT-band insertion and mergeback to `main`. | Keep every new creation/reuse/reset path under the same invariant and add regressions whenever a new branch publication path is introduced. | | 7. ID-band enforcement beyond happy paths | `tests/cow/id_bands.php` is a focused fast gate for separate branch AUTOINCREMENT bands, JSON/serialized references that keep branch IDs distinct without rewrite, normal in-band branch reuse that refreshes existing bands instead of allocating fresh ones, reset protection that allocates fresh bands when a branch DB drops below its old reservation, non-colliding non-AUTOINCREMENT `INTEGER PRIMARY KEY` plugin rows, and review-held non-AUTOINCREMENT `INTEGER PRIMARY KEY` plugin collisions. `tests/cow/explicit_ids.php` fast-gates in-band explicit AUTOINCREMENT imports that should merge without ID rewrite, out-of-band AUTOINCREMENT inserts and primary-key rewrites for WordPress and plugin tables, including paired source deletes held behind explicit inserts. `tests/cow/merge.php` covers AUTOINCREMENT allocation, rollback, reset below old bands, independent branch IDs, explicit out-of-band source IDs, child rows behind held explicit post/term/user IDs, inserted and updated scalar/serialized/theme/widget `wp_options`, `wp_posts`, `wp_postmeta`, `wp_comments`, `wp_commentmeta`, `wp_usermeta`, `wp_termmeta`, `wp_term_taxonomy`, `wp_term_relationships`, post-author, taxonomy menu-item, reusable/media/avatar/navigation/query block `post_content`, and comment-user references behind held explicit post/term/user IDs, JSON/serialized references that keep branch IDs distinct, plugin validator review for no-FK child rows behind held explicit plugin AUTOINCREMENT parents, and non-AUTOINCREMENT `INTEGER PRIMARY KEY` plugin graph collisions as review-held. `tests/cow/e2e.sh` verifies runtime branch post IDs fall inside branch bands and requires an independently banded source/target WordPress post merge to finish with `status: completed` and zero recorded conflicts while preserving embedded JSON/serialized post IDs. | Expand explicit-ID/import handling beyond currently covered AUTOINCREMENT row-insert/rewrite cases and enforce review for more plugin/custom logical identities that are not safely bandable. | -| 8. Better stale-audit workflow | `docs/stale-audit-workflow.md` describes the revalidation model. `scripts/cow/merge.php` implements `revalidate-reviews`, `merge-audit --revalidate`, `merge-resolve --after-revalidate`, revalidation classes, source/target drift checks, schema index/view/trigger/table-restore/table-rebuild source/target drift checks, plugin validator replacement evidence, plugin replacement conflict links, WordPress semantic fingerprints, conservative non-PK `UNIQUE` logical-key drift detection for custom/plugin tables, and source/target row-context payloads for new database cell conflicts. `tests/cow/stale_audit.php` is a focused fast gate for reviewed cell conflicts, target drift detection, source cell/row drift detection, deleted target rows classified as `missing`, no-primary-key rowid replacement classified as `incompatible`, WordPress post, postmeta, option, term, term taxonomy, termmeta, user, usermeta, comment, and commentmeta semantic replacements classified as `incompatible`, custom/plugin `UNIQUE` logical-key replacement on source or target classified as `incompatible`, row-context-backed source/target logical-key replacement for cell conflicts where the reviewed cell value itself is unchanged, `needs-action` carry-forward, idempotent revalidation, audit-visible revalidation classes, and guarded `--after-revalidate` source resolution. `tests/cow/plugin_validator.php` fast-gates plugin validator replacement evidence when rerun findings change, including explicit changed plugin `source` payload evidence and changed first-class plugin `logical_identity` evidence. `tests/cow/schema_review.php` fast-gates source-added schema index/view/trigger, dropped-table restore, and table rebuild source drift returning reviewed schema conflicts to `needs-action` with current source SQL evidence while remaining `unclassified`. `tests/cow/merge.php` covers stale row/cell/file drift, source drift, deleted targets, no-PK rowid replacement, supported WordPress semantic fingerprints, guarded resolution, idempotent carried notes, plugin validator rerun evidence through direct and `merge-audit --revalidate` paths, duplicate identical validator rerun handling, and replacement validator conflict ids. | Add richer schema evidence for dependency rebuild plans and guarded plugin/schema-specific resolution flows where appropriate. | +| 8. Better stale-audit workflow | `docs/stale-audit-workflow.md` describes the revalidation model. `scripts/cow/merge.php` implements `revalidate-reviews`, `merge-audit --revalidate`, `merge-resolve --after-revalidate`, revalidation classes, source/target drift checks, schema index/view/trigger/table-restore/table-rebuild source/target drift checks, plugin validator replacement evidence, plugin replacement conflict links, WordPress semantic fingerprints, conservative non-PK `UNIQUE` logical-key drift detection for custom/plugin tables, and source/target row-context payloads for new database cell conflicts. `tests/cow/stale_audit.php` is a focused fast gate for reviewed cell conflicts, target drift detection, source cell/row drift detection, deleted target rows classified as `missing`, no-primary-key rowid replacement classified as `incompatible`, WordPress post, postmeta, option, term, term taxonomy, termmeta, user, usermeta, comment, and commentmeta semantic replacements classified as `incompatible`, custom/plugin `UNIQUE` logical-key replacement on source or target classified as `incompatible`, row-context-backed source/target logical-key replacement for cell conflicts where the reviewed cell value itself is unchanged, `needs-action` carry-forward, idempotent revalidation, audit-visible revalidation classes, and guarded `--after-revalidate` source resolution. `tests/cow/plugin_validator.php` fast-gates plugin validator replacement evidence when rerun findings change, including explicit changed plugin `source` payload evidence and changed first-class plugin `logical_identity` evidence. `tests/cow/schema_review.php` fast-gates source-added schema index/view/trigger, dropped-table restore, and table rebuild source drift returning reviewed schema conflicts to `needs-action` with current source SQL evidence while remaining `unclassified`, plus target-side source-added view drift with current target SQL evidence. `tests/cow/merge.php` covers stale row/cell/file drift, source drift, deleted targets, no-PK rowid replacement, supported WordPress semantic fingerprints, guarded resolution, idempotent carried notes, plugin validator rerun evidence through direct and `merge-audit --revalidate` paths, duplicate identical validator rerun handling, and replacement validator conflict ids. | Add richer schema evidence for dependency rebuild plans and guarded plugin/schema-specific resolution flows where appropriate. | | 9. Release gate issue | `scripts/build-dist.sh` and release preflight tests fail earlier when static-PHP prerequisites are missing, avoid macOS bash empty-array expansion under `set -u` while wrapping Apple Silicon `spc` commands in `arch -arm64`, and `docs/merge-reliability.md` tracks aarch64 macOS as a release gate. Release `v0.1.17` was published from workflow run `25960944930` after all five release targets built and smoke-tested: `aarch64-apple-darwin`, `x86_64-apple-darwin`, `aarch64-unknown-linux-musl`, `x86_64-unknown-linux-musl`, and `x86_64-pc-windows-msvc`. The release includes an Apple Silicon archive at `https://github.com/Automattic/forkpress/releases/download/v0.1.17/forkpress-aarch64-apple-darwin.tar.gz`, uploaded with the other release assets and `SHA256SUMS`. The mac APFS e2e gate still tolerates only the known transient `hdiutil compact` "Resource temporarily unavailable" failure after storage has already detached. | Keep aarch64 macOS release and APFS sparsebundle E2E green on trunk for future releases; do not treat a transient compact skip as proof that compaction itself succeeded. | ## Current Guarantees diff --git a/tests/cow/schema_review.php b/tests/cow/schema_review.php index 3216e771..c969fb2e 100644 --- a/tests/cow/schema_review.php +++ b/tests/cow/schema_review.php @@ -226,6 +226,71 @@ function create_schema_trigger_order_db(string $path): void { assert_same($conflict['revalidation_class'] ?? null, 'unclassified', 'schema object audit exposes conservative unclassified revalidation'); } + $schema_target_drift_base = $tmp . '/schema-target-drift-base.sqlite'; + $schema_target_drift_source = $tmp . '/schema-target-drift-source.sqlite'; + $schema_target_drift_target = $tmp . '/schema-target-drift-target.sqlite'; + $schema_target_drift_metadata = $tmp . '/.forkpress/cow/merge/schema-target-drift-metadata.sqlite'; + + $db = open_db($schema_target_drift_base); + $db->exec('CREATE TABLE plugin_schema_target_drift_items (label TEXT NOT NULL)'); + $db->exec("INSERT INTO plugin_schema_target_drift_items (label) VALUES ('target drift anchor')"); + $db->close(); + copy($schema_target_drift_base, $schema_target_drift_source); + copy($schema_target_drift_base, $schema_target_drift_target); + + $source_db = open_db($schema_target_drift_source); + $source_db->exec('CREATE VIEW plugin_schema_target_drift_view AS SELECT label FROM plugin_schema_target_drift_view'); + $source_db->close(); + + $schema_target_drift_result = cow_merge_databases( + $schema_target_drift_base, + $schema_target_drift_source, + $schema_target_drift_target, + $schema_target_drift_metadata, + 'feature-schema-target-drift', + 'main' + ); + $schema_target_drift_run_id = (int)$schema_target_drift_result['run_id']; + assert_same($schema_target_drift_result['status'], 'completed_with_conflicts', 'schema target-drift fixture starts with a reviewed source-added view conflict'); + $schema_target_drift_conflict_id = (int)scalar($schema_target_drift_metadata, "SELECT id FROM merge_conflicts WHERE conflict_type = 'schema-source-added-view' AND column_name = 'plugin_schema_target_drift_view' ORDER BY id DESC LIMIT 1"); + assert_true($schema_target_drift_conflict_id > 0, 'schema target-drift fixture records the source-added view conflict'); + cow_merge_review_record( + $schema_target_drift_metadata, + 'conflict', + $schema_target_drift_conflict_id, + 'reviewed', + 'Review source-added view before target creates the same object.', + 'cow-test' + ); + + $target_db = open_db($schema_target_drift_target); + $target_db->exec('CREATE VIEW plugin_schema_target_drift_view AS SELECT label FROM plugin_schema_target_drift_items'); + $target_db->close(); + + $schema_target_drift_revalidated = cow_merge_revalidate_reviewed_conflicts($schema_target_drift_metadata, $schema_target_drift_run_id, 'cow-revalidate'); + assert_same($schema_target_drift_revalidated['checked'], 1, 'schema target SQL drift revalidation checks the reviewed conflict'); + assert_same($schema_target_drift_revalidated['reviewed'], 1, 'schema target SQL drift revalidation sees the reviewed conflict'); + assert_same($schema_target_drift_revalidated['stale'], 1, 'schema target SQL drift is treated as stale'); + assert_same($schema_target_drift_revalidated['carried'], 1, 'schema target SQL drift returns the conflict to needs-action'); + assert_true( + str_contains((string)scalar($schema_target_drift_metadata, "SELECT stale_reason FROM merge_revalidations WHERE conflict_id = $schema_target_drift_conflict_id ORDER BY id DESC LIMIT 1"), 'view target changed'), + 'schema target SQL drift explains that the target schema changed after review' + ); + $schema_target_drift_payload = cow_merge_decode_payload_json( + (string)scalar($schema_target_drift_metadata, "SELECT target_payload FROM merge_revalidations WHERE conflict_id = $schema_target_drift_conflict_id ORDER BY id DESC LIMIT 1"), + 'schema target-drift target payload' + ); + assert_true( + str_contains((string)$schema_target_drift_payload, 'SELECT label FROM plugin_schema_target_drift_items'), + 'schema target SQL drift records the current target SQL evidence' + ); + $schema_target_drift_audit = cow_merge_audit_report($schema_target_drift_metadata, $schema_target_drift_run_id, 10, [ + 'records' => 'conflicts', + 'review_status' => 'needs-action', + ]); + assert_same(count($schema_target_drift_audit['conflicts']), 1, 'schema target SQL drift is visible in the needs-action audit queue'); + assert_same($schema_target_drift_audit['conflicts'][0]['revalidation_class'] ?? null, 'unclassified', 'schema target SQL drift remains unclassified until a planner proves compatibility'); + $view_order_base = $tmp . '/view-order-base.sqlite'; $view_order_source = $tmp . '/view-order-source.sqlite'; $view_order_target = $tmp . '/view-order-target.sqlite';