Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/merge-reliability.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ when there is a test or document that exercises the specific merge invariant.
| 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, 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, reset/reuse protection that allocates fresh bands when a branch DB drops below its old reservation, and review-held non-AUTOINCREMENT `INTEGER PRIMARY KEY` plugin collisions. `tests/cow/explicit_ids.php` fast-gates 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, plugin validator replacement evidence, and plugin replacement conflict links. `tests/cow/stale_audit.php` is a focused fast gate for reviewed cell conflicts, target drift detection, source cell/row drift detection, `needs-action` carry-forward, idempotent revalidation, audit-visible revalidation classes, and guarded `--after-revalidate` source resolution. `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 broader plugin/schema source-drift evidence, more custom logical-identity classifiers, 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, plugin validator replacement evidence, and plugin replacement conflict links. `tests/cow/stale_audit.php` is a focused fast gate for reviewed cell conflicts, target drift detection, source cell/row drift detection, WordPress semantic replacement classified as `incompatible`, `needs-action` carry-forward, idempotent revalidation, audit-visible revalidation classes, and guarded `--after-revalidate` source resolution. `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 broader plugin/schema source-drift evidence, more custom logical-identity classifiers, 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. Trunk CI run `25952935740` passed `Build production dist bundle` and release-built COW APFS sparsebundle E2E on `aarch64-apple-darwin` and `x86_64-apple-darwin`, plus Linux COW E2E and Windows checks. The mac APFS e2e now 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 before treating Mac artifacts as trustworthy; do not treat a transient compact skip as proof that compaction itself succeeded. |

## Current Guarantees
Expand Down
53 changes: 53 additions & 0 deletions tests/cow/stale_audit.php
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,59 @@ function create_stale_audit_db(string $path): void {
'source payload changed after latest merge revalidation',
'after-revalidate row resolution fails if the source row changed after review'
);

$post_identity_base = $tmp . '/post-identity-base.sqlite';
$post_identity_source = $tmp . '/post-identity-source.sqlite';
$post_identity_target = $tmp . '/post-identity-target.sqlite';
$post_identity_metadata = $tmp . '/.forkpress/cow/merge/post-identity-metadata.sqlite';
foreach ([$post_identity_base, $post_identity_source, $post_identity_target] as $path) {
$db = open_db($path);
$db->exec('CREATE TABLE wp_posts (ID INTEGER PRIMARY KEY, post_type TEXT, post_title TEXT, post_content TEXT)');
$db->close();
}
$db = open_db($post_identity_source);
$db->exec("INSERT INTO wp_posts (ID, post_type, post_title, post_content) VALUES (10, 'page', 'Source page', 'source page content')");
$db->close();
$db = open_db($post_identity_target);
$db->exec("INSERT INTO wp_posts (ID, post_type, post_title, post_content) VALUES (10, 'page', 'Target page', 'target page content')");
$db->close();

$post_identity_merge = cow_merge_databases($post_identity_base, $post_identity_source, $post_identity_target, $post_identity_metadata, 'feature-post-identity-review', 'main');
$post_identity_run_id = (int)$post_identity_merge['run_id'];
assert_same($post_identity_merge['status'], 'completed_with_conflicts', 'post semantic identity fixture starts with a same-ID row conflict');
$post_identity_conflict_id = (int)scalar($post_identity_metadata, "SELECT id FROM merge_conflicts WHERE table_name = 'wp_posts' AND conflict_type = 'row-insert-collision'");
assert_true($post_identity_conflict_id > 0, 'post semantic identity fixture records the row conflict');
cow_merge_review_record(
$post_identity_metadata,
'conflict',
$post_identity_conflict_id,
'reviewed',
'Review source page before applying over target page.',
'cow-test'
);

$db = open_db($post_identity_target);
$db->exec("UPDATE wp_posts SET post_type = 'attachment', post_title = 'Target attachment', post_content = 'target attachment content' WHERE ID = 10");
$db->close();

$post_identity_revalidated = cow_merge_revalidate_reviewed_conflicts($post_identity_metadata, $post_identity_run_id, 'cow-revalidate');
assert_same($post_identity_revalidated['checked'], 1, 'post semantic revalidation checks the reviewed row conflict');
assert_same($post_identity_revalidated['stale'], 1, 'post semantic revalidation detects target semantic replacement');
assert_same($post_identity_revalidated['carried'], 1, 'post semantic revalidation carries semantically replaced rows to needs-action');
assert_same(
scalar($post_identity_metadata, "SELECT revalidation_class FROM merge_revalidations WHERE conflict_id = $post_identity_conflict_id ORDER BY id DESC LIMIT 1"),
'incompatible',
'post semantic revalidation classifies changed post_type as incompatible'
);
$post_identity_audit = cow_merge_audit_report($post_identity_metadata, $post_identity_run_id, 10, ['records' => 'conflicts']);
$post_identity_conflicts = array_values(array_filter($post_identity_audit['conflicts'], fn($row) => (int)($row['id'] ?? 0) === $post_identity_conflict_id));
assert_same($post_identity_conflicts[0]['revalidation_class'] ?? null, 'incompatible', 'post semantic audit exposes incompatible replacement');
assert_true(str_contains((string)($post_identity_conflicts[0]['stale_reason'] ?? ''), 'semantic identity'), 'post semantic stale reason explains semantic identity drift');
assert_throws(
fn() => cow_merge_resolve_conflict($post_identity_metadata, $post_identity_conflict_id, 'source', true, 'Do not apply source page over replacement attachment.', 'cow-test', true),
'latest merge revalidation is incompatible',
'after-revalidate blocks source resolution over an incompatible post semantic replacement'
);
} finally {
remove_tree($tmp);
}
Expand Down
Loading