Skip to content

fix(downgrade): block during protected array shrink#2698

Open
elibosley wants to merge 2 commits into
masterfrom
codex/parity-protected-shrink-downgrade-guard
Open

fix(downgrade): block during protected array shrink#2698
elibosley wants to merge 2 commits into
masterfrom
codex/parity-protected-shrink-downgrade-guard

Conversation

@elibosley

@elibosley elibosley commented Jul 16, 2026

Copy link
Copy Markdown
Member

Summary

An OS downgrade can strand a parity-protected array shrink on a release that cannot recover it. This adds a shared OS-level interlock plus a durable downgrade marker and blocks downgrade staging while any Core intent or daemon proof exists.

Resolution

  • Acquire the same exclusive /var/run/unraid-os-storage-interlock file lock used by Core before either side checks or publishes its durable intent/marker.
  • Create /var/run/unraid-os-downgrade exclusively while holding that lock, then inspect persistent state.
  • Scan the production Core intent and all daemon proof paths.
  • Return HTTP 409 and remove the marker when protected removal is active or recoverable, without moving boot artifacts.
  • Leave the marker through successful downgrade staging so Core and emhttpd reject new protected work until reboot.

Reviewer considerations

  • The shared flock makes downgrade-marker publication and protected-shrink authorization one cross-process decision; the durable marker/intent keeps the loser fenced afterward.
  • emhttpd rechecks the marker after the preparing proof is durable and before destructive writes.
  • The Core intent path is /boot/config/unraid/storage/parity-protected-shrink.json; daemon proofs live under /boot/config/storage.
  • Rollout order is unraidd PR 3 → this guard → Core PR 290. Reverse rollback is allowed only after every intent/proof is absent.

Verification

  • Exact head 59b2cc06bcf9bfc8206584eaa9de02ca23fbe5b9 contains latest fetched upstream master and is mergeable.
  • PHP syntax checks and parity_protected_shrink_guard_test.php passed, including lock handoff behavior.
  • Build Plugin, CodeQL, and CodeRabbit checks are green.
  • The paired live Core/unraidd recovery (465) passed the final staged-topology hard-power scenario in 9.2 minutes; daemon-restart transaction fencing (509) passed in 3.0 minutes.

Risk

Low code volume but safety-critical ordering. The shared lock and marker fail closed; deployment must follow the daemon-first sequence.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The downgrade flow now checks interlock availability, concurrent downgrade markers, and parity-protected shrink activity before continuing. A reusable guard module handles filesystem coordination, with tests covering status transitions, marker cleanup, and lock handoff.

Changes

Downgrade protection

Layer / File(s) Summary
Parity-protected shrink guard
emhttp/plugins/dynamix.plugin.manager/include/ParityProtectedShrinkGuard.php
Defines protected-shrink proof paths, detects active files, and provides shared interlock acquisition and release helpers.
Downgrade runtime gating
emhttp/plugins/dynamix.plugin.manager/include/Downgrade.php, emhttp/plugins/dynamix.plugin.manager/include/ParityProtectedShrinkGuard.php
Creates the downgrade marker under an interlock, blocks pending or active operations, and returns HTTP 503 or 409 responses as appropriate.
Guard validation
emhttp/plugins/dynamix.plugin.manager/tests/parity_protected_shrink_guard_test.php
Tests proof-file states, marker publication and removal, status codes, and interlock handoff behavior.

Estimated code review effort: 3 (Moderate) | ~15–30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Downgrade
  participant ShrinkGuard
  participant Filesystem
  Downgrade->>ShrinkGuard: begin downgrade with proof paths
  ShrinkGuard->>Filesystem: acquire interlock and create marker
  Filesystem-->>ShrinkGuard: return marker and lock status
  ShrinkGuard->>Filesystem: inspect protected shrink files
  Filesystem-->>ShrinkGuard: return active or idle state
  ShrinkGuard-->>Downgrade: return guard status
Loading

Poem

A rabbit guards the downgrade door,
While proof files hop across the floor.
Locks say “wait,” markers say “not yet,”
Safe paths keep the system set.
Tests twitch noses, green and bright—
The guarded flow now runs just right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: blocking downgrades during protected array shrink operations.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/parity-protected-shrink-downgrade-guard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown

🔧 PR Test Plugin Available

A test plugin has been generated for this PR that includes the modified files.

Version: 2026.07.19.0557
Build: View Workflow Run

📥 Installation Instructions:

Install via Unraid Web UI:

  1. Go to Plugins → Install Plugin
  2. Copy and paste this URL:
https://preview.dl.unraid.net/pr-plugins/pr-2698/webgui-pr-2698.plg
  1. Click Install

Alternative: Direct Download

⚠️ Important Notes:

  • Testing only: This plugin is for testing PR changes
  • Backup included: Original files are automatically backed up
  • Easy removal: Files are restored when plugin is removed
  • Conflicts: Remove this plugin before installing production updates
  • Post-merge behavior: This preview stays available after merge until preview storage expires or it is manually cleaned up

📝 Modified Files:

Click to expand file list
emhttp/plugins/dynamix.plugin.manager/include/Downgrade.php
emhttp/plugins/dynamix.plugin.manager/include/ParityProtectedShrinkGuard.php
emhttp/plugins/dynamix.plugin.manager/tests/parity_protected_shrink_guard_test.php

🔄 To Remove:

Navigate to Plugins → Installed Plugins and remove webgui-pr-2698, or run:

plugin remove webgui-pr-2698

🤖 This comment is automatically generated and will be updated with each new push to this PR.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
emhttp/plugins/dynamix.plugin.manager/tests/parity_protected_shrink_guard_test.php (1)

28-28: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use double quotes for newline escape sequences.

The string 'stage=prepared\n' uses single quotes, which means \n is interpreted as literal characters \ and n rather than a newline character. While this doesn't impact the is_file() check in this test, it's best to use double quotes if a true newline was intended.

🧽 Proposed nitpick
-file_put_contents($testFiles[2], 'stage=prepared\n');
+file_put_contents($testFiles[2], "stage=prepared\n");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@emhttp/plugins/dynamix.plugin.manager/tests/parity_protected_shrink_guard_test.php`
at line 28, Update the file_put_contents call in
parity_protected_shrink_guard_test to use a double-quoted string so \n is
written as an actual newline, preserving the existing content and test behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@emhttp/plugins/dynamix.plugin.manager/tests/parity_protected_shrink_guard_test.php`:
- Line 28: Update the file_put_contents call in
parity_protected_shrink_guard_test to use a double-quoted string so \n is
written as an actual newline, preserving the existing content and test behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8e288f21-1229-4663-b6f8-e20811c64a73

📥 Commits

Reviewing files that changed from the base of the PR and between 369f0b2 and ff17ec2.

📒 Files selected for processing (3)
  • emhttp/plugins/dynamix.plugin.manager/include/Downgrade.php
  • emhttp/plugins/dynamix.plugin.manager/include/ParityProtectedShrinkGuard.php
  • emhttp/plugins/dynamix.plugin.manager/tests/parity_protected_shrink_guard_test.php

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
emhttp/plugins/dynamix.plugin.manager/tests/parity_protected_shrink_guard_test.php (2)

37-38: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Test the downgrade_pending status.

The test correctly validates the success state but misses an opportunity to verify the downgrade_pending response that occurs when the marker already exists. Testing this state improves coverage of the guard's contract.

🧪 Proposed refactor to add the test case
 assert_same(true, is_file($testMarker), 'A successful downgrade decision must retain its marker.');
+assert_same(
+  'downgrade_pending',
+  parity_protected_shrink_begin_downgrade($testFiles, $testMarker, $testInterlock),
+  'A concurrent or subsequent downgrade attempt must report downgrade_pending.'
+);
 unlink($testMarker);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@emhttp/plugins/dynamix.plugin.manager/tests/parity_protected_shrink_guard_test.php`
around lines 37 - 38, Extend the test around the existing $testMarker assertions
to cover the downgrade_pending response when the marker already exists. After
establishing the marker, invoke the guard again and assert the returned status
is downgrade_pending before cleanup, while preserving the existing success-state
and marker-retention checks.

64-65: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clean up temporary directories to prevent resource leaks.

The test creates temporary nested directories ($testRoot/unraid/storage and $testRoot/storage) but only cleans up specific files at the end. This leaves dangling directories in the system temp directory after every test run.

Consider replacing the individual file unlink calls with a recursive removal of the entire test root to ensure all temporary artifacts are thoroughly cleaned up.

♻️ Proposed refactor for complete cleanup
-unlink($testFiles[2]);
-unlink($testInterlock);
+exec('rm -rf ' . escapeshellarg($testRoot));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@emhttp/plugins/dynamix.plugin.manager/tests/parity_protected_shrink_guard_test.php`
around lines 64 - 65, Update the cleanup at the end of the test to recursively
remove the entire temporary test root instead of only unlinking $testFiles[2]
and $testInterlock. Ensure the removal covers all nested directories and files
created under $testRoot, leaving no temporary artifacts after the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@emhttp/plugins/dynamix.plugin.manager/tests/parity_protected_shrink_guard_test.php`:
- Around line 37-38: Extend the test around the existing $testMarker assertions
to cover the downgrade_pending response when the marker already exists. After
establishing the marker, invoke the guard again and assert the returned status
is downgrade_pending before cleanup, while preserving the existing success-state
and marker-retention checks.
- Around line 64-65: Update the cleanup at the end of the test to recursively
remove the entire temporary test root instead of only unlinking $testFiles[2]
and $testInterlock. Ensure the removal covers all nested directories and files
created under $testRoot, leaving no temporary artifacts after the test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 43dc4d10-502c-47f3-9f75-f1bdc0fb15f2

📥 Commits

Reviewing files that changed from the base of the PR and between ff17ec2 and 59b2cc0.

📒 Files selected for processing (3)
  • emhttp/plugins/dynamix.plugin.manager/include/Downgrade.php
  • emhttp/plugins/dynamix.plugin.manager/include/ParityProtectedShrinkGuard.php
  • emhttp/plugins/dynamix.plugin.manager/tests/parity_protected_shrink_guard_test.php

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant