Skip to content

Merge Reliability Matrix

Status: 2026-05-20

Audience: ForkPress maintainers. For user workflow documentation, start with Merging and Conflict Review.

Current Summary

  • Merge policy remains conservative: apply source exactly, preserve target, or leave an auditable conflict.
  • Strong coverage exists for WordPress semantic graphs, plugin validator audit metadata, filesystem safety, branch birth, ID bands, stale reviews, crash recovery, and Git publication paths.
  • Review-first behavior is still intentional for ambiguous plugin-owned repairs, unsafe media regeneration, cyclic or semantically unclear schema changes, and platform-specific crash windows that need broader product kill harnesses.
  • Release-gate notes in this page are point-in-time evidence. Check GitHub releases and release workflow runs when making a current release claim.

ForkPress COW merge is intentionally conservative: it should either apply a source change exactly, preserve target state, or leave an auditable conflict. It should not silently rewrite WordPress data to make conflicts disappear.

This page tracks what is already covered and where merge reliability still depends on review, future validators, or broader end-to-end tests.

Objective Audit

This audit maps the current reliability objective to concrete artifacts. It is intentionally stricter than “tests pass”: an item is only treated as covered when there is a test or document that exercises the specific merge invariant.

Recent additions in the current merge-reliability work:

  • Branch reset now finalizes birth metadata against the private staged tree before publishing it: DB merge base, file merge base, row identities, and AUTOINCREMENT ID-band reservations are all complete before the reset branch becomes routable/listed again.
  • Plugin validators now receive first-class pre-merge target context: FORKPRESS_MERGE_TARGET_BEFORE_DB and, when file context exists, FORKPRESS_MERGE_TARGET_BEFORE_ROOT. The focused tests/cow/plugin_validator.php contract probe verifies that a validator can compare the candidate target with the target-before snapshot.
  • tests/cow/plugin_validator.php now includes a WooCommerce HPOS-shaped validator for real plugin graph semantics: order addresses, order metadata, order items, itemmeta, cached order options, and _product_id itemmeta pointing at wp_wc_product_meta_lookup rows. Source deletes that leave order or product graphs dangling stay reviewable with plugin logical identities instead of being treated as generic row merges.
  • The same plugin-validator suite now includes a Gravity Forms-shaped field map validator: entry metadata whose meta_key points at a form field removed from wp_gf_form_meta.display_meta stays reviewable with a plugin logical form-field identity.
  • The same plugin-validator suite now includes ACF-shaped field metadata coverage: hidden field-key postmeta that points at deleted acf-field definition posts and serialized relationship field values that point at deleted WordPress posts stay reviewable with plugin logical identities.
  • The same plugin-validator suite now includes an Elementor-shaped widget media validator: _elementor_data JSON image widgets that still point at a deleted attachment row or upload file stay reviewable with a plugin logical widget attachment identity.
  • The same plugin-validator suite now includes a Yoast SEO-shaped permalink validator: independently merged wp_yoast_indexable rows with the same object type and canonical permalink stay reviewable with a plugin logical permalink identity.
  • The same Yoast-shaped validator now covers wp_yoast_indexable_hierarchy rows, holding hierarchy edges whose child or ancestor indexable was removed while preserving target-side hierarchy edits for review.
  • tests/cow/wp_semantic_validator.php now includes a built-in WordPress global-styles validator case where source and target each add a published wp_global_styles row with the same style key. The merge stays reviewable, the audit payload includes both candidate rows, and preexisting duplicate style keys on target do not block unrelated source rows.
  • tests/cow/merge_smoke.php now also fast-gates the clean counterpart: independent source and target wp_global_styles rows with distinct style keys merge with zero conflicts and audit both sides. The same smoke gate keeps source-edited/target-deleted wp_global_styles rows reviewable.
  • tests/cow/wp_semantic_validator.php now includes built-in WordPress Site Editor object validator cases for duplicate published wp_template and wp_template_part rows with the same theme object key. These stay reviewable with WordPress-scoped audit payloads, while preexisting duplicate template keys do not block unrelated source templates.
  • Built-in WordPress semantic validation now cross-checks attachment upload metadata against the merged filesystem. Source-side deletes of generated-file and original_image uploads still referenced by merged attachment metadata are blocked as file conflicts before the files disappear, and preexisting missing generated files on target do not block unrelated merges.
  • tests/cow/wp_semantic_validator.php now includes a built-in WordPress term route validator case where source and target create taxonomy terms with the same taxonomy, parent, and slug. Newly introduced duplicates stay reviewable, while preexisting duplicate term routes do not block unrelated source terms.
  • tests/cow/wp_semantic_validator.php now includes a built-in WordPress user login validator case where source and target create users with the same case-insensitive login identity. Newly introduced duplicates stay reviewable, while preexisting duplicate user logins do not block unrelated source users.
  • tests/cow/wp_semantic_validator.php now includes a plugin custom-post-type graph validator case where a deleted CPT row leaves plugin custom-table JSON, serialized PHP, and option references stale. The merge stays reviewable and plugin-scoped audit output identifies both stale graph owners.
  • Source deletes of core WordPress owner rows are now held when target-edited dependent rows still point at them, including posts/postmeta, posts/comments, users/usermeta, users/comments, terms/term taxonomy, terms/termmeta, comments/commentmeta, and threaded comment parents. The target remains coherent before review instead of deleting the owner row and relying on a later missing-owner validator finding.
  • Attachment deletes are also held when the target branch adds or edits _thumbnail_id postmeta that points at the attachment. This covers the common featured-image reference stored as a metadata value rather than an owner column.
  • Source page and attachment deletes are held when the target branch changes scalar WordPress options that point at them, currently page_on_front, page_for_posts, and site_icon.
  • Source child-row deletes for target-referenced WordPress owners now inherit the same guard where the child is needed to keep the target-owned object coherent. A target-edited nav menu option keeps both the wp_terms row and its wp_term_taxonomy row before review, and target-edited attachment option references keep attachment-owned wp_postmeta rows from being silently removed.
  • Built-in WordPress semantic validation now covers stale post_parent references from revisions as well as child pages and attachments, so a source-deleted parent post cannot leave target-edited revision rows dangling without a review conflict.
  • Source post, term, and menu-item deletes are held when target-edited nav menu metadata points at them through _menu_item_object_id or _menu_item_menu_item_parent, with type-aware sibling metadata checks to avoid conflating taxonomy IDs with post IDs.
  • Reviewed table rebuild conflicts now retain rebuild-plan evidence for direct source indexes/triggers, dependent views, and dependent view triggers. tests/cow/schema_review.php proves dependency-only source drift returns the reviewed conflict to needs-action even when the table DDL itself did not change.
  • Applied plugin merge drivers now rerun discovered plugin validators before recording a plugin-driver resolution. If the same plugin/conflict type is still reported for the same validator object or logical_identity, the driver is rejected instead of closing an uncleared validator finding; ForkPress-owned driver execution also rolls back target DB/files.
  • The built-in WordPress semantic validator now scans block.json file: references in active plugins and records review conflicts when a merge leaves standard block metadata pointing at a missing or unsafe plugin asset.
  • The built-in WordPress attachment upload validator now rejects upload metadata paths after normalization if they escape wp-content/uploads, so generated-size metadata such as ../../../../database/.ht.sqlite cannot be treated as a valid managed database file.
  • The built-in WordPress attachment upload validator now also rejects upload metadata paths that resolve to symlinks or other non-regular filesystem entries, so generated-size metadata cannot satisfy an attachment reference by pointing at a special upload entry.
  • The built-in WordPress attachment upload validator now records review conflicts when multiple attachment rows claim the same regular upload file, so duplicate ownership is caught even without a plugin media validator.
  • The built-in WordPress attachment upload validator now records review conflicts when one attachment has duplicate _wp_attached_file or _wp_attachment_metadata rows, so branch merges cannot hide ambiguous media state behind whichever postmeta row WordPress reads last.
  • The built-in WordPress attachment upload validator also records review conflicts when distinct upload paths differ only by case, so merges cannot introduce media metadata that is ambiguous on default macOS or Windows filesystems.
  • The built-in WordPress attachment upload validator now records review conflicts when a safe _wp_attachment_metadata.file path disagrees with the safe _wp_attached_file path for the same attachment.
  • The built-in WordPress attachment upload validator now records review conflicts when an image attachment has a safe _wp_attached_file upload path but no _wp_attachment_metadata row.
  • The built-in WordPress attachment upload validator now records review conflicts when backup-size metadata has non-positive or non-numeric dimensions, or when backup-size filesize metadata no longer matches the referenced upload file, matching the existing original/generated-size metadata checks.
  • The built-in WordPress attachment upload validator now records review conflicts when attachment post_mime_type, generated-size mime-type, or backup-size mime-type metadata disagrees with the referenced upload file extension for common WordPress media extensions.
  • The same validator also records review conflicts when recognizable upload bytes for original, generated-size, or backup-size files disagree with common WordPress media file extensions, such as PDF bytes behind a .jpg path.
  • The built-in WordPress attachment upload validator now records review conflicts when generated-size, original_image, or backup-size child metadata filenames contain safe subdirectories instead of basenames. It resolves those child paths relative to the attachment directory for file evidence, matching WordPress behavior, while still rejecting unsafe .., empty segment, absolute, URL-like, and drive-letter child paths.
  • The built-in WordPress attachment upload validator now records review conflicts when a present _wp_attachment_metadata.original_image value is empty or not a string, so malformed scaled-image metadata cannot skip the original-image filename checks.
  • The built-in WordPress attachment upload validator now records generated-size and backup-size child mime-type metadata as invalid shape when the field is present but empty or non-string. tests/cow/media_validator.php covers empty and numeric child MIME values and verifies those cases do not inflate the built-in MIME-drift queue.
  • Non-AUTOINCREMENT INTEGER PRIMARY KEY plugin-table parent collisions now also hold dependent source child rows when a foreign key would otherwise bind them to a different target parent row with the same key.
  • Plugin AUTOINCREMENT parent rows with branch-band IDs but colliding plugin-defined unique/logical keys now hold dependent source child rows with an auditable parent unique-collision reason instead of surfacing only a low-level constraint failure.
  • Explicit out-of-band parent page imports now have focused coverage for wp_posts.post_parent: an in-band child page behind a held explicit parent stays review-held, so ForkPress does not merge a dangling page hierarchy.
  • Release v0.1.43 was published from trunk commit 01f0e423614e89f6404c20a16e7dd2306a43d4b0 after all five release targets built, packaged, and smoke-tested, including aarch64-apple-darwin.
  • Existing non-main Git branch updates now reject missing branch SQLite databases before staging or publishing pushed files, matching the runtime write guard for incomplete branch-birth metadata.

Detailed Objective Coverage

Real WordPress semantic merge coverage

Evidence on trunk

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, synced pattern rows, page-to-reusable-block refs, branch-local wp_template_part, wp_template, and wp_global_styles Site Editor rows, 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. The branch UI E2E also submits branch create without Accept: application/json or X-ForkPress-Async, and fails if WordPress HTML reaches the caller instead of ForkPress JSON. tests/cow/merge_smoke.php now fast-gates page create/create, edit/create, delete/create, page-plus-postmeta create/create, page-plus-comment create/create, page-plus-custom-post-type create/create, Events Calendar-shaped event/venue/organizer CPT create/create and edit/delete with event metadata, taxonomy, and option JSON refs, page-plus-taxonomy create/create, page-plus-menu create/create, page-plus-reusable-block create/create, page-plus-navigation-block create/create, page-plus-template-part create/create, page-plus-template create/create, global-styles create/create, page-plus-attachment create/create, page-plus-image-block create/create, page-plus-gallery create/create, page-plus-file-block create/create, page-plus-media-text create/create, composite page graph create/create across reusable block refs, image block refs, featured image metadata, taxonomy, nav menu locations, JSON/serialized options, and upload files, and page-plus-options JSON/serialized create/create invariants without starting WordPress: independent main inserts must survive while branch page inserts, edits, deletes, postmeta graph rows, comment users, usermeta, comments, commentmeta, plugin-like custom post type rows, event plugin CPT rows, CPT postmeta, CPT taxonomy relationships, CPT option refs, terms, term-taxonomy rows, page-term relationships, nav menu terms, menu item posts/postmeta/relationships, merged theme-mod menu locations, reusable wp_block rows referenced from block comments, wp_navigation rows referenced from navigation comments, wp_template_part rows referenced from template-part comments, wp_template rows referenced from page template assignments, independent wp_global_styles rows, attachment rows, featured-image and attachment metadata, core/image, core/gallery, core/file, and core/media-text block JSON refs, upload files, and JSON/serialized option references apply cleanly with zero conflicts while preserving branch-specific IDs without rewrite; denormalized built-in category counts, nav-menu counts, and post comment_count values are recomputed from merged relationships/comments while custom/plugin taxonomy counts are left alone. It also fast-gates same-object page/postmeta edit-vs-delete conflicts, user/usermeta edit-vs-delete conflicts, comment/commentmeta edit-vs-delete conflicts, custom-post-type edit-vs-delete conflicts across CPT rows, CPT postmeta, CPT option indexes, and CPT taxonomy relationships, taxonomy-term edit-vs-delete conflicts across terms, term-taxonomy rows, termmeta, and page-term relationships, reusable-block edit-vs-delete conflicts across the wp_block row and target page cleanup, navigation-block edit-vs-delete conflicts across the wp_navigation row and target page cleanup, template-part edit-vs-delete conflicts across the wp_template_part row and target page cleanup, template edit-vs-delete conflicts across the wp_template row and target page template assignment cleanup, global-styles edit-vs-delete conflicts across wp_global_styles rows, navigation-menu edit-vs-delete conflicts across menu terms, term-taxonomy rows, menu item posts, menu item metadata, relationships, serialized nav-widget options, and theme-mod location cleanup, attachment edit-vs-delete conflicts across attachment rows, metadata rows, original files, and generated files, plus JSON and serialized option edit-vs-delete conflicts. 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, synced patterns, navigation blocks, or template parts, child pages, attachments, or revisions left pointing at deleted post_parent rows, posts or attachments left pointing at deleted post_author users, postmeta left pointing at deleted posts, usermeta left pointing at deleted users, nav menu items left pointing at deleted parent menu items, pages, or taxonomy terms, featured-image postmeta left pointing at deleted attachment rows/files, core/audio, core/cover, core/file, core/image, core/video, core/media-text, and core/gallery block JSON left pointing at deleted attachment rows/files, classic wp-image-* and [gallery ids="..."] content left pointing at deleted attachment rows/files, core/avatar and core/latest-posts block JSON left pointing at deleted users, core/navigation-link and core/navigation-submenu block JSON left pointing at deleted pages or taxonomy terms, core/query block JSON including taxQuery and core/latest-posts category filters left pointing at deleted author users or taxonomy terms, term relationships left pointing at deleted taxonomy terms, term taxonomy rows left pointing at deleted terms, child taxonomy terms left pointing at deleted parent terms, termmeta left pointing at deleted terms, comments left pointing at deleted posts/users/parent comments, commentmeta left pointing at deleted comments, and options/widgets/theme mods, including block, text, and custom HTML widget content, media widgets, pages widgets, and nav menu auto-add options, left pointing at deleted WordPress objects. It also proves the built-in WordPress page-route validator holds duplicate route-visible page slugs under the same parent, the built-in term-route validator holds duplicate taxonomy/parent/slug routes, the built-in user-login validator holds duplicate case-insensitive wp_users.user_login identities, the built-in global-styles validator holds duplicate published wp_global_styles style keys, and the built-in Site Editor validator holds duplicate published wp_template and wp_template_part object keys as review conflicts with structured audit payloads, and the built-in block asset validator holds active-plugin block.json file: references to missing, URL-like, drive-letter, or otherwise unsafe script, style, render, or other standard plugin-local assets as review conflicts, while ignoring duplicates that predate the merge. Built-in WordPress semantic findings are filterable as semantic_scope=wordpress instead of being indistinguishable from ordinary plugin-owned validator findings. tests/cow/merge.php adds deterministic WordPress row fingerprint and validator coverage.

Remaining gap

Add broader concurrent edit/delete matrices for complete WP objects and deterministic repair policies only where the owner object is unambiguous.

Plugin-specific merge semantics

Evidence on trunk

docs/plugin-merge-validators.md defines the validator contract, including rejecting contradictory status/finding output and optional first-class severity and logical_identity evidence for plugin-defined object identity. scripts/cow/merge.php discovers active plugin and mu-plugin validators, records active plugins that did not ship a discoverable validator as durable plugin-validator-unchecked audit decisions, runs explicit validators, records plugin-scoped conflicts, validates first-class plugin validator identity, severity, review-guidance, and logical-identity fields, filters plugin audit queues by plugin, plugin object, plugin severity, and plugin logical identity, groups plugin conflict, event, and resolution queues by the same first-class fields, rolls back inline validator failures, and runs explicit plugin drivers with conflict context, reruns discovered plugin validators before recording applied plugin-driver resolutions, fingerprints the pre-driver validator files so a mutating driver cannot delete or rewrite the validator it is supposed to satisfy, rejects repairs that leave the same validator finding open, and records plugin-driver resolution evidence without allowing generic source/target resolution of plugin conflicts. Plugin-driver resolution of replacement validator evidence now requires the previous reviewed conflict to have a latest current replacement-evidence revalidation that points at the replacement conflict; stale originals, unrevalidated replacements, incompatible revalidations, and drifted replacement evidence are rejected. Ordinary cross-run plugin conflict lineage remains resolvable without pretending it is replacement evidence. wp-plugin/forkpress-wp.php exposes configured and plugin-shipped active plugin drivers in the branch switcher and full Branches wp-admin page with opaque allowlist keys, can run an approved driver for a plugin conflict row, and refreshes the needs-action queue after the driver records its result. 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 including URL-like and Windows drive-letter plugin file references, stale serialized/JSON asset references, real-plugin-shaped WooCommerce HPOS, Gravity Forms, ACF, Elementor, Yoast SEO, and The Events Calendar graph validators, identical validator rerun dedupe, contradictory validator output rejection, malformed validator identity rejection, replacement-evidence revalidation when validator findings change after review, guarded plugin-driver application of the current replacement conflict, cross-run plugin conflict lineage resolution, explicit plugin source-evidence drift recorded by validator reruns, plugin severity audit fields, plugin object/severity/logical-identity filters and groupings, generic merge-resolve rejection for plugin conflicts, explicit plugin-driver runner and direct recorder repair resolution audit, non-clearing applied driver rejection, ForkPress-owned driver rollback, validator deletion/rewrite bypass rejection, and logical_identity drift returning reviewed findings to needs-action. tests/cow/branch_ui.php fast-gates the WordPress UI allowlist and active-plugin discovery so browser requests cannot choose arbitrary driver paths. tests/cow/merge.php covers clean custom-table graph merges, validator findings, plugin and plugin-severity conflict grouping, audit/review grouping, validator rerun evidence, file-root context, active-plugin discovery, unchecked active-plugin coverage reporting, 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.

Remaining gap

Promote the tested plugin-shaped validators into production/plugin-shipped validators where maintainers can own semantics, and add merge drivers only for plugin-owned repairs that can prove correctness.

Remaining review-only schema cases

Evidence on trunk

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 including trigger WHEN clauses apply in dependency order, source-added triggers can depend on source-added views in trigger bodies and WHEN clauses, source-changed views and triggers apply automatically when the target kept the base object and validation passes, source-changed views/triggers can depend on source-added tables that materialize earlier in the same merge, source-changed triggers can depend on source-added views in trigger bodies or WHEN clauses, source-changed view rewrites that would invalidate target trigger bodies stay reviewable, cyclic source-added views/triggers stay reviewable, source-added triggers with missing target dependencies stay gated until the dependency is restored, source-added table/view drops with unresolved dependent target views/triggers/schema objects stay reviewable with blocked source choices until the dependencies are resolved, source-added expression unique indexes blocked by target rows stay reviewable until the blocking rows are removed, and reviewed source-added or source-changed 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. Source-added/source-changed index, view, and trigger source drift can be guarded and source-applied after revalidation when the current source SQL validates against the current target. Dropped-table restore source drift can be guarded and source-applied after revalidation when the target table is still absent and a dry-run restore of the current source payload validates. Table rebuild conflicts also retain rebuild-plan evidence for direct indexes/triggers, dependent views, and dependent view triggers, so dependency-only source drift returns reviewed conflicts to needs-action; compatible table rebuild source drift can now replace prior source-applied rebuild dependencies and source-apply after revalidation when the dry-run planner proves the current source rebuild works. It also covers target-side SQL drift for reviewed source-added or source-changed view, trigger, and index conflicts, source-dropped index conflicts, dropped-table restore, and table rebuild conflicts, including current target SQL evidence; source-added/source-changed index, source-dropped index, view, and trigger target drift can be classified as compatible-schema-*-target-drift and applied with --after-revalidate when dry-run source replacement or drop validates against the current target. tests/cow/merge.php covers broader cyclic/invalid view and trigger dependency handling, source-added dependent view/trigger/index ordering, and rebuild validation cases.

Remaining gap

Improve dependency planning for more safe reorderings. Add guarded schema revalidation resolution flows beyond validated source/target drift cases where the schema planner can prove compatibility. Cyclic or semantically ambiguous cases should stay review-only.

Filesystem merge hardening

Evidence on trunk

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 symlinks can merge, unsafe symlinks to absolute paths, root-escaping paths, self-references, and ForkPress-managed paths remain conflicts, reviewed source resolution cannot force-apply unsafe symlinks, reviewed source directory-subtree resolution cannot force-apply unsafe symlinks nested inside a replacement directory, and unsupported special source filesystem entries remain review-held and cannot be source-applied on platforms with FIFO support. Directory/file and file/directory replacements get type-specific review conflicts, unchanged target descendants and source descendants under reviewed replacements are held until review, source directory deletions with target-side descendants are held with source resolution blocked before any descendant deletion, reviewed source replacements can apply supported file/dir/symlink changes including safe directory subtrees, reviewed filesystem source drift can be source-applied after revalidation only when the latest source and target payloads still match the revalidation record, and WordPress upload files still referenced by the merged attachment metadata are protected from source-side deletes as file conflicts before semantic validation runs. WordPress E2E links attachment rows to original and generated-size upload files, plugin-shaped E2E checks branch-owned file contents, and PHP coverage uses discovered and built-in validators to cross-check attachment metadata against merged upload files, missing required attachment metadata rows, invalid serialized attachment metadata, malformed image_meta, invalid original/generated/backup dimensions, original/generated/backup filesize drift, attachment post_mime_type, byte-signature MIME drift, and generated-size/backup mime-type drift against known upload file extensions including AVIF and PDF, missing or empty metadata-side original file fields, generated-size, original_image, and backup filename drift, upload paths that exist as non-file entries, missing original/generated upload files including metadata-side original, original_image, and backup image files, attached-file metadata drift, duplicate original/generated/backup upload ownership, and unsafe primary/metadata/generated/original_image/backup upload paths, including root-escaping, dot or empty path segments, URL-like primary/metadata/generated, and Windows drive-letter primary/metadata upload metadata. 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 and backup attachment file checks, original/generated dimension drift, malformed image_meta, 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.

Remaining gap

Add stricter uploads-specific validators for more drift shapes and implement a deterministic media repair driver only if WordPress can prove exact regeneration.

Crash consistency across DB/files/metadata/Git

Evidence on trunk

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/plugin_validator.php covers a plugin-driver failpoint after a mutating driver returns but before plugin-driver resolution metadata is written, proving target DB/files are restored and no resolution is recorded for ordinary pre-resolution failures. It also kills the plugin-driver runner at the same boundary, verifies the target DB/files may be partially mutated before recovery, verifies the pending plugin-driver-resolution crash artifact blocks retries, exposes that artifact through merge-audit --records crash-recovery, and restores target DB/files through recover-crash --restore-target-db --restore-files. crates/forkpress-storage/src/lib.rs and scripts/cow/git_server.php publish post-merge and Git-created-branch DB/filesystem merge-base snapshots by atomic replacement instead of remove-before-rename, with focused storage and Git-server tests proving failed publication keeps the previous snapshot. 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, restores a pending public merge crash through the WordPress branch UI restore action, and covers actual smart-HTTP Git-created branch pushes interrupted before branch-birth metadata, after branch-birth metadata but before branch tree publication, after branch storage publication, 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, object-prune interruption, and atomic merge-base file publication.

Remaining gap

Broaden external kill harness coverage across the remaining platform-specific Git publication and APFS/cleanup checkpoints, then verify post-crash state from a fresh process.

Branch birth always captures merge bases

Evidence on trunk

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, decision/run metadata, branch-birth decision cleanup, DB merge-base sidecar cleanup, file-base cleanup, cleanup/rollback paths, and existing non-main Git branch updates rejecting missing branch DB, DB merge base, filesystem merge base, or branch-birth metadata before writes publish. 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.

Remaining gap

Keep every new creation/reuse/reset path under the same invariant and add regressions whenever a new branch publication path is introduced.

ID-band enforcement beyond happy paths

Evidence on trunk

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, review-held non-AUTOINCREMENT INTEGER PRIMARY KEY plugin collisions including ordinary implicit rowid allocation where both branches independently receive id=1, and audit/text output that names non-bandable plugin tables and explains the missing durable sqlite_sequence reservation point. tests/cow/explicit_ids.php fast-gates in-band explicit AUTOINCREMENT imports that should merge without ID rewrite, fresh band allocation when an in-band explicit import reaches the previous band end before the next implicit insert, out-of-band AUTOINCREMENT inserts and primary-key rewrites for WordPress and plugin tables, paired source deletes held behind explicit inserts, inserted or updated wp_posts rows with post_author, core/avatar, and core/query author references behind out-of-band explicit wp_users imports, inserted or updated wp_usermeta and wp_comments rows held behind out-of-band explicit wp_users imports, inserted or updated wp_commentmeta plus threaded comments behind out-of-band explicit wp_comments imports, inserted or updated featured-image postmeta, image-block content, classic wp-image-* and [gallery ids="..."] content, site_icon, theme-mod custom_logo, and media/block/text/custom HTML widget options held behind out-of-band explicit attachment imports, pages-widget options and inserted or updated core/navigation-link, core/navigation-submenu, and core/navigation post_content refs behind out-of-band explicit page/term/wp_navigation imports, and inserted or updated wp_termmeta, wp_term_taxonomy, wp_term_relationships, serialized theme-mod menu locations, nav-menu widgets, and nav_menu_options options held behind out-of-band explicit wp_terms imports. 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.

Remaining gap

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.

Better stale-audit workflow

Evidence on trunk

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, latest revalidation status checks, 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, source/target row-context payloads for new database cell conflicts, current --resolution-choice and --blocked-resolution-choice conflict queue filters, --event-type, conflict-event grouping, blocked resolution attempt events, resolver-contract filtering/grouping by resolution strategy, generic resolver support, and after-revalidate support, --latest-revalidation-status, --stale-status, and matching group-by queue filters, and text/JSON revalidation summaries that name the carried and already-open needs-action conflict ids, classifiers, drift reasons, revalidation records, and replacement conflict ids. The CLI also exposes forkpress branch conflicts as a shortcut for merge-audit --records conflicts, with the same filters and revalidation contract, so conflict review queues do not require remembering the lower-level audit record selector. 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, actionable revalidation summaries, audit-visible revalidation classes, latest revalidation current/drifted status output, live stale-status filtering/grouping, 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; source-added view/trigger source drift can get compatible classes and guarded --after-revalidate source resolution when current source SQL now validates, while source-added index, view, and trigger target drift get compatible classes and guarded source resolution only when dry-run source replacement validates. Table rebuild payloads include direct indexes/triggers, dependent views, and dependent view triggers, so dependency-only source drift is visible even when table SQL is unchanged. It also covers target-side source-added view, trigger, index, dropped-table restore, and table rebuild 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, live source-applicable and source-blocked audit filters, conflict event-type filtering and grouping, blocked resolution attempt events, resolver-contract filtering/grouping, idempotent carried notes, plugin validator rerun evidence through direct and merge-audit --revalidate paths, duplicate identical validator rerun handling, and replacement validator conflict ids.

Remaining gap

Add guarded plugin/schema-specific resolution flows beyond currently validated schema drift classes where a plugin or schema planner can prove compatibility.

Release gates

Evidence on trunk

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. The last explicitly recorded release-gate audit in this matrix is release v0.1.48, workflow run 26067459265; that run passed release packaging, artifact smoke checks, tag creation, GitHub release publication, and Homebrew formula update for all release targets, including aarch64-apple-darwin. The mac APFS e2e gate still tolerates only the known transient hdiutil compact “Resource temporarily unavailable” failure after storage has already detached.

Remaining gap

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

  • Branches have separate SQLite files and separate file trees.
  • AUTOINCREMENT branch ID bands prevent routine ID collisions before JSON or serialized references are written.
  • The DB merge records source-applied, target-kept, target-wins, conflict, resolution, ID-band, row-identity, and rollback-failure metadata outside the WordPress database.
  • Source row inserts and updates are verified after SQLite accepts the write. If target-side triggers rewrote or removed the row, the write is rolled back and the merge remains reviewable.
  • Row target-constraint conflicts expose source choices as blocked while current target foreign-key state, target-side CHECK constraints, or target trigger rewrites make the audited source row invalid.
  • Reviewed source conflict resolutions use the same row postcondition guard.
  • File changes are merged separately from DB rows and unsafe paths remain conflicts.
  • Conflict resolution validates that the target still matches the audited value. If the target drifted, resolution stops and asks for a fresh audit.

Reliability Gaps

Recent focused coverage also tightens three roadmap edges:

  • tests/cow/merge_smoke.php verifies WordPress nav_menu counts are recomputed from published nav_menu_item relationships only, while draft menu items and non-menu post relationships remain preserved without inflating the count.
  • tests/cow/plugin_validator.php includes a Yoast SEO-shaped wp_yoast_indexable fixture where an index row points at a deleted WordPress post and target SEO title/description edits stay reviewable. The same validator now holds duplicate canonical permalink groups for review when source and target independently add indexables for the same URL, and hierarchy edges whose child or ancestor indexable was removed.
  • The same suite includes an Elementor-shaped _elementor_data fixture where plugin-owned JSON points at a deleted attachment row/upload file while target widget edits remain visible for review.
  • The same suite includes Events Calendar-shaped _EventVenueID and _EventOrganizerID fixtures where scalar event postmeta points at deleted tribe_venue and tribe_organizer posts while target event/date edits remain visible for review.
  • The Events Calendar-shaped validator also covers plugin option/cache JSON: tribe_events_recent_event_ids can preserve target-side option edits while holding stale deleted-event IDs as plugin-scoped conflicts with logical identities.
  • crates/forkpress-storage/src/lib.rs covers the branch-reset crash window after reset birth metadata has been finalized but before the reset branch is published, proving old metadata/base snapshots are restored and reset can be retried cleanly.

Detailed Reliability Gaps

WordPress semantic objects

Current state

Tests cover real post creation, postmeta references, users, usermeta, post/comment authors, threaded comments and commentmeta references, branch-local page edits/deletes with edited content/author assertions, same-object page/postmeta edit-vs-delete conflicts with auditable target-wins defaults, same-object user/usermeta edit-vs-delete conflicts that preserve target deletion before review, same-object comment/commentmeta edit-vs-delete conflicts that preserve target deletion before review, same-object custom-post-type edit-vs-delete conflicts across CPT rows, CPT postmeta, CPT option indexes, and CPT taxonomy relationships, same-object taxonomy-term edit-vs-delete conflicts across terms, term-taxonomy rows, termmeta, and page-term relationships, attachment uploads plus original and generated-size files, attachment metadata, attachment-to-page parent links, same-object reusable-block edit-vs-delete conflicts across the wp_block row and target page cleanup, same-object navigation-menu edit-vs-delete conflicts across menu terms, term-taxonomy rows, menu item posts, menu item metadata, relationships, serialized nav-widget options, and theme-mod location cleanup, same-object attachment edit-vs-delete conflicts across attachment rows, metadata rows, original files, and generated files, core/audio, core/cover, core/file, core/image, core/video, core/media-text, and core/gallery block references and classic wp-image-*/[gallery ids="..."] content references plus featured-image postmeta references to media attachments, hierarchical taxonomy terms, page-linked nav menus with menu-location assignments, reusable blocks and synced patterns, options with embedded object IDs including branch user refs, JSON option payloads with embedded object IDs including branch user refs, custom post types, plugin AUTOINCREMENT tables, keyless plugin tables, unique collisions, file additions, nested plugin-owned custom-table/JSON/serialized/file graphs, branch merge visibility, a clean zero-conflict semantic E2E merge requirement, a discovered media validator that reports missing original/generated upload files including metadata-side original, original_image, and backup image files, duplicate attachment claims on the same upload file including same-attachment generated-file duplicates, unreadable or NUL-corrupted attachment metadata, malformed image_meta, empty or unsafe primary/metadata/generated/original_image/backup upload metadata paths, original/generated/backup dimension drift, malformed or incomplete generated-size and backup-size metadata, generated-size, original_image, and backup filename drift, and _wp_attached_file versus _wp_attachment_metadata file drift, fast discovered block-reference, post-parent-reference, post-author-reference, postmeta-reference, usermeta-reference, menu-parent-reference, menu-reference, featured-image, image-block, media-block, avatar-navigation-link-block, gallery-block, query-block, term-relationship, term-taxonomy-reference, term-parent-reference, termmeta-reference, and option-reference validators that report pages/posts left pointing at deleted reusable blocks, synced patterns, navigation blocks, or template parts, child pages or attachments left pointing at deleted post_parent rows, posts or attachments left pointing at deleted post_author users, postmeta rows left pointing at deleted posts, usermeta rows left pointing at deleted users, nav menu items left pointing at deleted parent menu items or deleted post/taxonomy objects, _thumbnail_id postmeta left pointing at deleted attachment objects/files, core/audio, core/cover, core/file, core/image, core/video, core/media-text, or core/gallery block JSON plus classic wp-image-* and [gallery ids="..."] content left pointing at deleted attachment objects/files, core/avatar and core/latest-posts block JSON left pointing at deleted users, core/navigation-link and core/navigation-submenu block JSON left pointing at deleted pages or taxonomy terms, core/query block JSON including taxQuery and core/latest-posts category filters left pointing at deleted author users or taxonomy terms, wp_term_relationships left pointing at deleted taxonomy term rows, term taxonomy rows left pointing at deleted terms, child taxonomy terms left pointing at deleted parent terms, termmeta rows left pointing at deleted terms, serialized theme mods left pointing at deleted post objects, deleted nav-menu terms, or deleted custom-logo attachments plus serialized nav menu widgets, serialized nav menu auto-add options, serialized block/text/custom HTML widget content, serialized media-image/audio/video/gallery and pages widgets, serialized sidebar-widget placements, scalar site_icon/page_on_front/page_for_posts options, and serialized sticky_posts options left pointing at deleted objects, and docs/merge-repair-policy.md defines when semantic repairs must remain review-only.

Missing reliability work

Add broader concurrent object matrices, implement only the repair policies that have deterministic owners, and broaden plugin-owned graph conflict/drift cases.

Plugin-specific semantics

Current state

Generic SQLite merge is table/row/cell based and does not rewrite embedded IDs. docs/plugin-merge-validators.md defines the validator boundary and first test shape. PHP unit and E2E coverage now cover the clean happy path for a plugin-owned custom-table graph with parent/child rows, child JSON payload references, serialized option/postmeta references, referenced CPT data, and a referenced file. The PHP unit suite also covers the metadata/audit foundation for plugin-scoped validator conflicts, including review queues, first-class plugin filters, and queue grouping. Normal branch merges discover validators from active plugin, network-active plugin, and mu-plugin locations in the staged candidate target; discovered custom-table graph validators can abort and roll back a candidate with a broken JSON reference, or complete the merge with plugin-scoped review conflicts for broken serialized graph row/file references, unsafe URL-like or drive-letter plugin file references, and target-conflicting graph state. The focused plugin validator gate now includes serialized and JSON plugin option/postmeta references left pointing at a deleted plugin asset row/file or an unsafe plugin-owned file path, first-class plugin severity, validator identity, and logical_identity contract validation, plugin/object/severity/logical-identity/file filters and groupings, and logical_identity drift evidence for semantic identities that are not expressed as SQLite keys. forkpress branch run-plugin-validator, forkpress branch record-plugin-validator-conflicts, and forkpress branch merge --plugin-validator <path> expose explicit validator execution and findings recording, while rejecting contradictory valid-with-findings output or malformed validator identities before they become conflict metadata. Validator failures after DB/files have staged roll back the merge.

Missing reliability work

Add broader plugin-owned validators for more real plugins and plugin merge drivers only where a plugin can prove an automatic repair is safe.

Review-only schema cases

Current state

Acyclic source-added dependent views, views that depend on source-added tables, trigger programs, and triggers that depend on source-added tables including trigger WHEN clauses can apply automatically in dependency order. Source-changed views and triggers also apply automatically when the target kept the base object and the rewritten object validates, including rewrites that depend on source-added tables materialized earlier in the merge. Source-added expression unique indexes that target rows would violate stay reviewable until those rows are removed. Source-dropped table/view conflicts expose source as blocked while current target views, triggers, schema objects, or foreign-key children still depend on the object, then advertise source after those dependency conflicts are resolved. Reviewed source-added or source-changed index/view/trigger conflicts, source-dropped index conflicts, dropped-table restores, and table rebuild conflicts can be revalidated for changed source/target SQL evidence and return to needs-action; compatible index/view/trigger source drift, compatible index/view/trigger target drift including source-dropped index target drift, compatible table-restore source drift, compatible table-rebuild source drift, and compatible table-rebuild target drift can be guarded and source-applied after revalidation when replacement, drop, restore, or rebuild validates. Table rebuild revalidation includes dependency-plan evidence for direct indexes/triggers, dependent views, and dependent view triggers, so dependency-only source drift is not hidden behind unchanged table SQL. Cyclic views/triggers, source-added triggers with unresolved dependencies, invalid preserved trigger/view dependencies, and some rebuild dependency chains are held as auditable conflicts.

Missing reliability work

Improve dependency planning so more safe schema reorderings can apply automatically. Add guarded schema revalidation resolution flows beyond currently validated source/target drift cases where the schema planner can prove compatibility. Keep non-deterministic or semantically ambiguous cases review-only.

Filesystem semantics

Current state

File additions/deletions/conflicts are audited; binary file changes/conflicts are hash-verified, safe relative symlinks can merge, unsafe symlinks to absolute paths, root-escaping paths, self-references, and ForkPress-managed paths remain conflicts, reviewed source resolution cannot force-apply unsafe symlinks, reviewed source directory-subtree resolution cannot force-apply unsafe symlinks nested inside a replacement directory, and unsupported special source filesystem entries remain review-held and cannot be source-applied on platforms with FIFO support. Directory/file and file/directory replacements get type-specific review conflicts, unchanged target descendants and source descendants under reviewed replacements are held until review, source directory deletions with target-side descendants are held with source resolution blocked before any descendant deletion, reviewed source replacements can apply supported file/dir/symlink changes including safe directory subtrees, WordPress E2E links attachment rows to original and generated-size upload files, plugin-shaped E2E checks branch-owned file contents, and PHP coverage uses discovered and built-in validators to cross-check attachment metadata against merged upload files, missing required attachment metadata rows, invalid serialized attachment metadata, malformed image_meta, invalid original/generated/backup dimensions, original/generated/backup filesize drift, attachment post_mime_type, byte-signature MIME drift, and generated-size/backup mime-type drift against known upload file extensions including AVIF and PDF, missing or empty metadata-side original file fields, generated-size, original_image, and backup filename drift, upload paths that exist as non-file entries, missing original/generated upload files including metadata-side original, original_image, and backup image files, attached-file metadata drift, duplicate original/generated/backup upload ownership, and unsafe primary/metadata/generated/original_image/backup upload paths, including root-escaping, dot or empty path segments, URL-like primary/metadata/generated, and Windows drive-letter primary/metadata upload metadata.

Missing reliability work

Add stricter uploads-specific validators for more conflict/drift shapes, including attachment metadata regeneration decisions.

Crash consistency

Current state

DB, metadata, file, rollback-failure, ID-band, plugin-driver, and Git publication paths have targeted rollback tests. DB merge process-death coverage includes crashes before/after target DB commit and before metadata commit, with pending crash artifacts that block later merges until explicit recovery. Whole-branch DB+file merges now keep recoverable target DB, metadata DB, and filesystem-root snapshots across the file phase, so a hard exit after DB commit, before file mutation, or after an individual file operation blocks later merges until recover-crash --restore-target-db --restore-files restores a coherent pre-merge state. Plugin-driver runs now leave a crash-recovery artifact if the runner dies after target DB/files mutate but before driver resolution metadata is recorded; retries are blocked until recovery restores target DB/files. Public E2E now drives merge/create/reset/recover crash paths through forkpress commands, restores a pending public merge crash through the WordPress branch UI restore action, and covers actual smart-HTTP Git-created branch pushes interrupted before branch-birth metadata, after branch-birth metadata but before branch tree publication, after branch storage publication, before branch-list publication, and after branch-list publication, followed by fresh server restart verification and merge retry/mergeback. Git server process-death coverage includes created-branch metadata/storage/public-link/list publication with retry ref reconciliation after list publication, existing-branch update publication, delete staging, and object pruning. docs/merge-crash-consistency.md maps covered boundaries and missing product-level failpoint work.

Missing reliability work

Broaden the external product-level kill harness across the remaining platform-specific Git-push failpoints, plus platform-specific coverage around sparsebundle detach/compact and rollback-artifact cleanup.

Branch birth

Current state

CLI, Git-created, and remote-cache branches allocate ID bands and capture merge base metadata. Branch-birth validation rejects missing ID bands and keyless row identities, filesystem base capture records user content/uploads while excluding managed DB/config/Git files, and rollback cleanup removes only the failed branch metadata and preserves unrelated branch birth metadata. Git-created branches finalize birth metadata before publishing the branch tree, so a pre-metadata crash cannot expose a branch without ID bands or row identities. Existing branches reused by automation or updated through Git publication must still have a database, DB merge base, filesystem merge base, and required birth metadata before reuse/update. The WordPress admin branch create/merge UI is covered as a thin wrapper over the same CLI paths, including validation, CLI failure surfacing, and real runtime E2E create/merge requests. Remote-cache branch E2E coverage registers a materialized main cache, creates remote-cache-branch, verifies branch storage, inserts into the runtime AUTOINCREMENT probe inside the branch band, then merges branch DB and filesystem changes back to main.

Missing reliability work

Keep branch create, Git ref create, remote-cache branch, reset, UI creation, and Git-updated existing branches on one invariant: DB base, file base, ID bands, and metadata must exist before user writes.

ID bands

Current state

AUTOINCREMENT bands protect common WordPress and plugin tables. Normal branch reuse inside the reserved band refreshes existing metadata instead of allocating fresh IDs; reaching the previous band end allocates a fresh band before the next implicit insert while preserving prior reserved ranges as valid for merge; reset below old bands gets fresh bands. Existing non-main Git branch updates with a WordPress SQLite database now validate DB merge base, filesystem merge base, and branch-birth metadata before replacing branch storage, so pushes cannot write into an ID-bearing branch that is missing its band reservation. Explicit source IDs outside the reserved branch band are review-held instead of applied automatically for core WordPress and plugin AUTOINCREMENT tables, paired source deletes in the same AUTOINCREMENT table are also held when an out-of-band explicit insert or primary-key rewrite is held, and source child wp_posts, owner/reference wp_postmeta including inserted and updated type-aware nav menu object refs, inserted or updated scalar/serialized/theme/widget wp_options references, inserted or updated wp_comments, wp_commentmeta, inserted or updated wp_termmeta, hierarchical and updated wp_term_taxonomy, inserted or primary-key-rewritten wp_term_relationships, inserted or updated wp_usermeta, inserted or updated post authors, inserted or updated reusable/media/avatar/latest-posts/navigation/query block post_content refs including taxQuery, inserted or updated classic attachment refs in post_content, inserted or updated taxonomy menu-item object refs, or inserted or updated comment user refs pointing at held explicit post/term/user IDs are review-held instead of leaving orphan WordPress child rows. Focused explicit-ID coverage now includes inserted or updated wp_posts rows with post_author, core/avatar, and core/query author references plus wp_usermeta and wp_comments rows behind held out-of-band explicit wp_users imports, inserted or updated wp_commentmeta and threaded comments behind held explicit wp_comments imports, inserted or updated featured-image postmeta, image-block content, classic wp-image-* and [gallery ids="..."] content, site_icon, theme-mod custom_logo, and media/block/text/custom HTML widget options behind held explicit attachment imports, pages-widget options and inserted or updated core/navigation-link, core/navigation-submenu, and core/navigation post_content refs behind held explicit page/term/wp_navigation imports, and inserted or updated wp_termmeta, wp_term_taxonomy, wp_term_relationships, serialized theme-mod menu locations, nav-menu widgets, and nav_menu_options options behind held explicit wp_terms imports. Non-AUTOINCREMENT INTEGER PRIMARY KEY plugin tables are marked non-bandable; non-colliding rows merge, while collisions are review-held and auditable. Dependent source child rows are also held when their foreign key points at a source parent row whose plain integer key collides with a different target parent.

Missing reliability work

Expand explicit-ID/import handling beyond covered AUTOINCREMENT row-insert cases and reject/review unsafe reuse for more plugin/custom logical identities that are not safely bandable.

Stale audits

Current state

Resolution fails if target no longer matches the audited payload. forkpress branch revalidate-reviews and forkpress branch merge-audit --revalidate carry stale reviewed conflicts back into needs-action while preserving prior reviewer intent, avoiding duplicate carried notes, recording revalidated payloads, linking plugin replacement validator conflicts, emitting actionable text/JSON summaries of carried and already-open conflict ids, and storing a conservative revalidation classifier such as compatible-target-drift, compatible-source-drift, missing, no-primary-key rowid-reuse incompatible, supported WordPress post/postmeta/option/term/term-taxonomy/termmeta/user/usermeta/comment/commentmeta primary-key semantic incompatible, custom/plugin non-PK UNIQUE logical-key incompatible, plugin replacement-evidence, schema compatible-schema-index-target-drift, compatible-schema-view-target-drift, compatible-schema-trigger-target-drift, compatible-schema-table-target-drift, or schema index/view/trigger/table-restore/table-rebuild unclassified drift. A latest revalidation-required event now overrides older unapplied validated choices, and bulk apply-reviewed-resolutions preflights live staleness, revalidates stale validated choices, reports them as skipped, and leaves the target unchanged instead of surfacing a raw resolver error or silently applying a stale reviewed choice. If a later revalidation shows the source and target payloads are fresh again, the prior pending or reviewed intent is restored and validated choices re-enter the apply-reviewed queue. New database cell conflicts also retain source/target row-context payloads, so custom/plugin logical-key replacement can be detected even when the reviewed scalar cell value is unchanged. forkpress branch merge-audit --resolution-choice source and --blocked-resolution-choice source expose the current executable/blocking contract for conflict queues. forkpress branch merge-audit --event-type recorded|review-pending|review-needs-action|review-reviewed|resolution-validated|resolution-applied|resolution-blocked|revalidation-required exposes the first-class conflict event history without scraping review notes, including failed resolver attempts that leave conflicts open, and forkpress branch merge-audit --records conflict-events --group-by event-type|lifecycle|conflict-key summarizes that history for UI queues. forkpress branch merge-audit --latest-revalidation-status current|source-drifted|target-drifted|source-and-target-drifted|unknown|none and --group-by latest-revalidation-status expose whether the latest recorded after-revalidate guard still matches live source/target state before the reviewer attempts a guarded resolution. forkpress branch merge-resolve conflict <id> --after-revalidate can apply reviewed stale database row/cell and filesystem conflicts only when the current source/target payloads still match the latest revalidation record, the latest classifier is not incompatible, and the original logical row still exists; source-added view/trigger compatible source drift, source-added schema index/view/trigger compatible target drift, compatible table-rebuild source drift, and compatible table-rebuild target drift can also source-apply after revalidation only when the planner recorded its matching compatible schema class. The fast stale-audit gate now covers deleted target rows, no-primary-key rowid reuse, supported WordPress semantic replacement classifiers, custom/plugin logical-key replacements inferred from non-PK UNIQUE indexes, row-context-backed cell conflict identity drift, stale validated choices being excluded or preflight-revalidated by batch apply, fresh-again restoration into the batch apply queue, live source-applicable/source-blocked audit filters, event-type output/filtering/grouping, latest revalidation status output/filtering/grouping, and the direct revalidation output shape. Plugin validator conflicts now return to needs-action when a validator rerun records changed evidence for the same plugin object, including explicit changed plugin source payload evidence or changed first-class logical_identity evidence. Generic source/target resolution remains blocked for plugin conflicts, but a plugin driver can resolve the current replacement conflict when the previous reviewed conflict has a latest current replacement-evidence revalidation pointing at that replacement; stale originals, unrevalidated replacements, incompatible revalidations, and drifted replacement evidence are rejected. Ordinary cross-run plugin conflict lineage remains outside that replacement-evidence guard. Reviewed schema index/view/trigger/table-restore/table-rebuild conflicts now return to needs-action with current SQL and, for table rebuilds, dependency-plan evidence when schema SQL or dependent object SQL drifts. Other schema conflicts still need planner-backed guarded resolution before they can be applied after revalidation. docs/stale-audit-workflow.md maps the current flow and the remaining richer classifier model.

Missing reliability work

Add more guarded revalidation resolution for plugin and schema conflicts where a plugin or schema planner can prove compatibility.

Release gates

Current state

Linux, Windows, x86_64 macOS, and aarch64 macOS production bundle artifacts built in the last checked release. macOS and Linux release workflows install static PHP build prerequisites up front, scripts/build-dist.sh now fails before cloning/building PHP if those tools are missing instead of letting static-php-cli mutate package-manager state during the release bundle step, avoids macOS bash empty-array expansion under set -u while wrapping Apple Silicon spc commands in arch -arm64, and the default static-php-cli checkout is pinned to a known upstream commit with an explicit override for deliberate upgrades. Release-verification PRs now run the same production bundle matrix even when the source branch is not release/v*, while keeping tag-state validation scoped to actual release branches. The last explicitly recorded release-gate audit in this matrix is release v0.1.48, workflow run 26067459265; that run passed release packaging, artifact smoke checks, tag creation, GitHub release publication, and Homebrew formula update for all release targets, including aarch64-apple-darwin. Trunk and release CI also run APFS sparsebundle E2E on both macOS targets with a bounded product retry and e2e-only tolerance for the known transient hdiutil compact unavailable condition.

Missing reliability work

Keep aarch64 macOS release and APFS sparsebundle E2E green on trunk for future releases. Add a separate product check if compact-success itself becomes release-critical.

Test Direction

Every new reliability claim should have one of these test shapes:

  • A PHP unit test in tests/cow/merge.php for deterministic DB merge behavior.
  • A COW E2E test in tests/cow/e2e.sh for real WordPress/runtime behavior.
  • A Rust unit test for CLI, storage, Git publication, release packaging, or platform-specific lifecycle behavior.
  • A CI workflow gate when the behavior only exists on a target platform, such as APFS sparsebundles or Windows ReFS.

For fast local iteration on merge logic, start with:

Terminal window
make test-cow-merge-smoke

This smoke gate includes regressions for pages created, edited, and deleted on a feature branch while main also creates content. Page-plus-postmeta, page-plus-comment, page-plus-threaded-comment, page-plus-author, page-plus-custom-post-type, page-plus-taxonomy, page-plus-menu, page-plus-child-page, page-plus-revision, page-plus-reusable-block, page-plus-synced-pattern, page-plus-navigation-block, page-plus-template-part, page-plus-template, global-styles, page-plus-attachment, page-plus-image-block, page-plus-gallery, page-plus-file-block, page-plus-media-text, page-plus-audio-cover-video, and page-plus-options cases must complete with zero conflicts while preserving branch-specific IDs inside author/user, comment/user, and threaded-comment graphs, plugin-like CPT graphs, menu relationships, parent/child and revision post_parent page graphs, theme-mod locations, reusable-block and synced-pattern comments plus synced-pattern metadata, core/navigation comments that reference wp_navigation rows, template-part comments that reference wp_template_part rows, page template assignments that reference wp_template rows, attachment metadata, core/image/core/gallery/core/file/core/media-text/ core/audio/core/cover/core/video block JSON, upload files, JSON payloads, and serialized payloads without rewrite. It also includes page/postmeta, child page, revision, user, comment, threaded comment, taxonomy term, reusable block, synced pattern, navigation block, template part, template, menu, attachment, media block page, and option edit/delete conflict cases that must stay reviewable and keep the target deletion until review. The page/postmeta case covers the wp_posts row and its wp_postmeta graph, the child page case covers a deleted child wp_posts row while preserving the unchanged parent page, the revision case covers a deleted revision row while preserving its unchanged parent page, the user case covers wp_users and wp_usermeta, the comment cases cover deleted parentless and reply comments while preserving unchanged authors and parent comments, the taxonomy case covers term rows, term-taxonomy rows, termmeta, and page-term relationships, the reusable block and synced pattern cases cover deleted wp_block rows and target page cleanup while synced patterns also preserve wp_pattern_sync_status metadata cleanup, the navigation block case covers the deleted wp_navigation row and target page cleanup, the template part case covers the deleted wp_template_part row and target page cleanup, the template case covers the deleted wp_template row and target page template assignment cleanup, the menu case covers menu terms, taxonomy rows, menu item posts, menu item metadata, relationships, and theme-mod location cleanup, the attachment case covers the DB and file graph, the media block page case covers a deleted page plus core/audio, core/cover, and core/video attachment rows, attachment metadata, and upload files, and the option cases cover JSON and serialized wp_options rows plus global front/posts page singleton, serialized sticky-post, and scalar site-icon option disagreements, plus serialized theme-mod custom-logo and media-image widget media/audio/video/gallery widgets, pages widget exclusions, nav-menu widget, nav-menu auto-add, content-bearing widgets, and menu-location disagreements, that must stay reviewable.

For filesystem merge behavior, including binary changes and conflicts, safe relative symlinks, unsafe absolute/root-escaping/self-referential/managed-path symlink conflicts, directory/file type replacement review, and source directory deletes with target descendants, run:

Terminal window
make test-cow-filesystem

For WordPress branch UI and router handling, including async create/merge requests and browser-like no-header submissions that should be answered before WordPress admin-post.php or admin-page bootstrap, run:

Terminal window
make test-cow-branch-ui

For explicit import IDs or primary-key rewrites at or outside a branch’s reserved AUTOINCREMENT ID band, run:

Terminal window
make test-cow-explicit-ids

For all cheap COW helper/UI/router checks, run:

Terminal window
make test-cow-fast

The CI e2e jobs run this fast target before building the production runtime bundle, so helper-level merge, Git publication, branch UI, and router regressions fail before the static PHP build.

For Git-created branch, branch-birth metadata, push rollback, and Git publication crash/failpoint changes, run:

Terminal window
make test-cow-git-server

For the default local iteration gate, let the changed-file planner choose the smallest focused COW/PHP checks for the current diff:

Terminal window
make test-cow-changed

See docs/merge-test-speed.md for the planner rules and CI preflight behavior.

For branch-birth metadata validation, frozen pre-write filesystem base snapshots, and cleanup isolation without the Git publication harness, run:

Terminal window
make test-cow-branch-birth

For ID-band allocation, in-band branch reuse, branch-reset reuse protection, JSON/serialized branch ID preservation, and non-bandable INTEGER PRIMARY KEY plugin collision checks, run:

Terminal window
make test-cow-id-bands

For a semantic-only fast gate that skips Git publication, branch UI, router, schema, and generic filesystem checks while still covering page smoke merges, ID bands, explicit IDs, media/upload validators, plugin validators, stale audits, and WordPress semantic reference validators, run:

Terminal window
make test-cow-semantic-fast

For the focused runtime WordPress semantic E2E slice, which starts a real ForkPress site and merges branch-created page/edit/delete, media upload, menus, reusable blocks, options, comments, users, terms, CPT data, and plugin-shaped DB/JSON/serialized/file graphs before exiting the larger E2E script, run:

Terminal window
make test-cow-e2e-semantic FORKPRESS_E2E_BIN=/path/to/forkpress

For schema dependency planning and review-only cases such as dependent source-added views/triggers, source-added views or triggers that depend on source-added tables, cyclic source-added views/triggers, source-added expression unique indexes blocked by target rows, or source-added triggers with missing dependencies, run:

Terminal window
make test-cow-schema-review

For WordPress upload/media validator changes, including missing required attachment metadata rows, missing original, metadata-side original, original_image, backup, or generated upload files, missing or empty metadata-side original file fields, invalid serialized attachment metadata, duplicate attachment upload metadata rows, duplicate original/generated upload ownership, invalid image_meta, invalid original/generated/backup dimensions, original/generated/backup filesize drift, attachment post_mime_type and generated-size/backup mime-type drift against known upload file extensions, attached-file metadata drift, generated-size, original_image, or backup filename drift, unsafe primary/metadata/generated/original_image/backup paths including URL-like and Windows drive-letter primary/metadata upload metadata, and malformed or incomplete generated-size or backup-size metadata, run:

Terminal window
make test-cow-media-validator

For WordPress semantic reference validators, including pages left pointing at deleted reusable blocks, synced patterns, or template parts, child pages or attachments left pointing at deleted post_parent rows, posts or attachments left pointing at deleted post_author users, postmeta rows left pointing at deleted posts, usermeta rows left pointing at deleted users, and nav menu items left pointing at deleted parent menu items or deleted pages, plus featured-image postmeta left pointing at deleted attachments/files, target-added or target-edited _thumbnail_id metadata pointing at source-deleted attachments, target-edited page_on_front, page_for_posts, or site_icon options pointing at source-deleted objects, target-edited _menu_item_object_id or _menu_item_menu_item_parent metadata pointing at source-deleted menu objects, and core/audio, core/cover, core/file, core/image, core/video, core/media-text, or core/gallery block JSON left pointing at deleted attachments/files from content-bearing custom post types as well as posts/pages, core/query block JSON including taxQuery and core/latest-posts filters left pointing at deleted author users or taxonomy terms, core/avatar block JSON left pointing at deleted users, core/navigation-link and core/navigation-submenu block JSON left pointing at deleted pages or taxonomy terms, term relationships left pointing at deleted taxonomy terms, term taxonomy rows left pointing at deleted terms, child taxonomy terms left pointing at deleted parent terms, termmeta rows left pointing at deleted terms, comments left pointing at deleted posts/users/parent comments, commentmeta left pointing at deleted comments, options/widgets/theme mods left pointing at deleted WordPress objects, the built-in WordPress page-route and term-route validators that hold duplicate route-visible page slugs and taxonomy term slugs for review, the built-in WordPress user-login validator that holds duplicate case-insensitive wp_users.user_login identities for review, and the built-in WordPress global-styles validator that holds duplicate published wp_global_styles style keys, plus duplicate published wp_template and wp_template_part Site Editor object keys, and the built-in attachment upload validator that holds exact or case-insensitive duplicate upload ownership for review, run:

Terminal window
make test-cow-wp-semantic-validator

For plugin-owned DB/JSON/file graph validator changes, including serialized or JSON option/postmeta references, unsafe plugin-owned file references including URL-like and Windows drive-letter file paths, validator runner contract checks, rerun replacement evidence, explicit plugin source-evidence drift, and first-class plugin logical-identity drift, run:

Terminal window
make test-cow-plugin-validator

For stale-audit revalidation, source/target drift, deleted target rows, no-primary-key rowid reuse, WordPress semantic identity replacement across posts, postmeta, options, terms, taxonomy, termmeta, users, usermeta, comments, and commentmeta, custom/plugin non-PK UNIQUE logical-key replacement, row-context-backed cell conflict identity drift, and guarded --after-revalidate resolution changes, run:

Terminal window
make test-cow-stale-audit

For the broader PHP merge gate without building ForkPress or starting the full WordPress E2E harness, run:

Terminal window
make test-cow-merge

Prefer adding validators before adding automatic conflict resolution for plugin or schema cases. A reliable reviewable conflict is better than an automatic merge that invents WordPress semantics.

See docs/plugin-merge-validators.md for the proposed plugin validator contract.

See docs/merge-repair-policy.md for the repair-versus-review policy for WordPress and plugin semantic graphs.

See docs/stale-audit-workflow.md for the proposed stale-audit revalidation workflow.

See docs/merge-crash-consistency.md for the merge crash-consistency boundary map.

Follow-Up Work

PR #46 should be treated as a merge-reliability hardening milestone, not the final proof that all merges are automatic or fully reliable. The next work should stay focused on these areas:

  • Add more validators for real plugins with known cross-table, serialized, JSON, and file graphs. Add plugin-owned merge drivers only when the plugin can prove a deterministic repair.
  • Build broader external kill harnesses for public Git push/serve entry points, then verify recovery from a fresh process after each interruption.
  • Expand explicit-ID/import handling beyond the currently covered AUTOINCREMENT row insert/rewrite and known WordPress reference cases.
  • Add guarded plugin/schema stale-audit resolution flows where the plugin or schema planner can prove the reviewed choice is still valid.
  • Keep conflict resolution modeled as a first-class contract in audit output: every conflict class should advertise its legal executable choices, resolution strategy, generic resolver support, and revalidation support before the CLI or UI offers an action. Audit output should also expose lifecycle and next-action fields so deferred, needs-action, reviewed, validated, and resolved conflicts are not inferred from free-form review notes. Conflict state changes now append merge_conflict_events, giving UI and API clients a durable event stream for recorded, reviewed, revalidated, and resolved conflicts; validation-only merge-resolve calls are persisted as validated resolutions with resolution-validated events, --apply-reviewed applies the latest unapplied validated choice, payload-specific blockers such as cyclic schema source resolution, unresolved target dependencies for source-dropped tables/views, foreign-key row, unique-collision row, and primary-key-addressable cell source choices blocked by current target state, target-side row CHECK constraints, target trigger rewrites, unsafe filesystem source payloads, and target-descendant directory deletions are exposed as blocked_resolution_choices, conflict rows expose a stable conflict_key for logical UI grouping plus previous_conflict_id lineage for recurring conflicts and plugin validator replacement evidence on the same source/target branch pair, conflict rows are scoped to the merge run so repeated unresolved conflicts and identical payload conflicts on different branch pairs receive their own rows/events, and blocked resolver attempts append resolution-blocked events that move the still-open conflict back to needs-action/manual-review queues instead of leaving UI clients to offer a stale validated apply, and merge-audit --conflict-key <key> can focus conflict, conflict-event, or resolution audit output on one logical conflict group, merge-review conflict-key <key> [--run <id>] can attach review status by logical key, merge-resolve conflict-key <key> [--run <id>] can validate or apply a conflict choice by logical key when that key is unambiguous, and merge-audit --lifecycle-state <state> can focus conflict queues and conflict-event history on unreviewed, deferred, needs-action, reviewed, validated, or resolved records without clients reimplementing lifecycle inference, merge-audit --resolution-choice source|target|plugin-driver and --blocked-resolution-choice source|target can focus queues using the same live contract that merge-resolve enforces, merge-audit --next-action <action> can focus queues by the action a UI should offer next, merge-audit --latest-revalidation-status <status> can focus conflicts whose latest revalidation guard is still current or has drifted again, and merge-audit --stale-status <status> can query live conflict staleness before revalidation. merge-audit --records conflicts --group-by lifecycle, --group-by next-action, --group-by conflict-key, --group-by latest-revalidation-status, and --group-by stale-status can summarize queue counts by lifecycle, required action, logical conflict, current guard status, or live stale status without client-side aggregation. merge-audit --records conflicts --group-by resolution-strategy, --group-by generic-resolver, and --group-by after-revalidate summarize which queues are generic-resolver ready and which require guarded revalidation before apply; the same names are also filters for focusing conflict and conflict-event records. merge-audit --records conflict-events --group-by event-type, --group-by lifecycle, --group-by conflict-key, and the plugin groupings can summarize recorded, review, revalidation, blocked-resolution, and resolution event history without client-side aggregation.
  • Improve deterministic schema dependency planning for safe view/trigger reorderings while keeping cyclic or semantic ambiguity review-only.
  • Keep aarch64 macOS release artifacts and APFS sparsebundle E2E runs green for each release. Refresh release-gate evidence from GitHub releases and workflow runs when this note changes; do not use this page as the source of truth for the current published version.