Skip to content

fix: update source_mappings hash on project sync to prevent stale validation#1543

Open
amasolov wants to merge 1 commit into
ansible:mainfrom
amasolov:fix/update-source-mappings-hash-on-project-sync
Open

fix: update source_mappings hash on project sync to prevent stale validation#1543
amasolov wants to merge 1 commit into
ansible:mainfrom
amasolov:fix/update-source-mappings-hash-on-project-sync

Conversation

@amasolov
Copy link
Copy Markdown
Contributor

@amasolov amasolov commented May 1, 2026

Summary

  • Update _sync_rulebook() to rewrite the rulebook_hash inside each affected activation's source_mappings when rulebook content changes during a project sync
  • Remove the workaround in _check_and_restart_activation() that skipped auto-restart for activations with source_mappings, since the hash is now kept in sync
  • Add integration test covering the sync-updates-source-mappings scenario
  • Update existing auto-restart tests to reflect the new behaviour

Test plan

  • ruff check and ruff format --check pass
  • test_sync_updates_source_mappings_hash verifies hash is updated after sync
  • test_auto_restart_restarts_activation_with_source_mappings verifies auto-restart now proceeds
  • test_auto_restart_mixed_source_mappings_activations verifies both mapped and unmapped activations are restarted
  • Existing test suite passes

Tracking

Closes #1542

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Rulebook sync now refreshes related activations when rulebook content changes and preserves existing rule set content for activations with mapped sources.
    • Invalid or unparseable source mappings now mark activations as errored with a descriptive status.
  • Tests

    • Added integration tests covering rulebook sync scenarios, activation refreshes, and error handling for malformed source mappings.

@amasolov amasolov requested a review from a team as a code owner May 1, 2026 06:11
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 1, 2026

Warning

Rate limit exceeded

@amasolov has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 49 minutes and 58 seconds before requesting another review.

To continue reviewing without waiting, purchase usage credits in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 94d41676-c175-4543-8482-e0f5dd648885

📥 Commits

Reviewing files that changed from the base of the PR and between d695eaa and a5ac27a.

📒 Files selected for processing (5)
  • src/aap_eda/services/project/imports.py
  • src/aap_eda/tasks/project.py
  • tests/integration/services/data/project-07/extensions/eda/rulebooks/hello_events.yml
  • tests/integration/services/test_project_import.py
  • tests/integration/tasks/test_projects.py
📝 Walkthrough

Walkthrough

Adds an explicit activation-update workflow after syncing a rulebook: bulk-refreshes activations with empty source_mappings, rewrites rulebook_hash inside YAML source_mappings for others, and marks activations ERROR with a message when YAML parsing fails.

Changes

Rulebook Activation Sync Logic

Layer / File(s) Summary
Core save & trigger
src/aap_eda/services/project/imports.py
After persisting new rulebook content and sha256, now calls _update_activations_for_rulebook(rulebook, new_sha256, git_hash) instead of only updating rulebook fields inline.
Activation update workflow
src/aap_eda/services/project/imports.py
Adds _update_activations_for_rulebook(...) which: bulk-updates activations having restart_on_project_update=False and empty source_mappings to refresh rulebook_rulesets, rulebook_rulesets_sha256, and git_hash; iterates activations with non-empty source_mappings to update embedded rulebook_hash; leaves restart_on_project_update=True activations untouched.
YAML rewrite helper
src/aap_eda/services/project/imports.py
Adds static `_update_source_mappings_hash(source_mappings: str, new_hash: str) -> str
Error signaling
src/aap_eda/services/project/imports.py
Imports ActivationStatus and marks activations as ActivationStatus.ERROR with a descriptive status_message when source_mappings parsing fails.

Task logging and docs

Layer / File(s) Summary
Logging strings & docstring
src/aap_eda/tasks/project.py
Consolidates multi-line logger f-strings into single-line f-strings and refines _auto_restart_activations docstring wording; no control-flow or behavior changes.

Tests & Fixtures

Layer / File(s) Summary
Fixture
tests/integration/services/data/project-07/extensions/eda/rulebooks/hello_events.yml
Adds a new EDA rulebook fixture (hello_events.yml) used by integration tests.
Integration tests
tests/integration/services/test_project_import.py
Adds tests asserting: source_mappings rulebook_hash is rewritten on rulebook content change while preserving rulebook_rulesets and their sha256; activations become ERROR with a message on unparseable source_mappings; auto-restart activations with empty source_mappings are skipped (deferred to restart logic).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title concisely describes the main fix: updating source_mappings hash on project sync to prevent stale validation.
Description check ✅ Passed Description covers what is changed, why (stale hash issue), how it addresses the problem, and includes test plan and tracking details, following template structure well.
Linked Issues check ✅ Passed Changes fully address #1542 requirements: rulebook_hash in source_mappings is updated on sync, auto-restart skip guard preserved for mapped activations, error handling for unparseable mappings.
Out of Scope Changes check ✅ Passed All code changes are directly aligned with fixing the stale source_mappings hash issue; docstring updates and test additions support the primary objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@amasolov amasolov force-pushed the fix/update-source-mappings-hash-on-project-sync branch from 1afb23e to bb6c136 Compare May 1, 2026 07:04
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/aap_eda/services/project/imports.py (1)

225-251: ⚡ Quick win

Limit this loop to activations that actually need per-record work.

This queryset now walks every activation for the rulebook, including restart_on_project_update=True rows with no source_mappings, which are a no-op here because _auto_restart_activations() handles them later. Filtering those out would reduce ORM churn on large syncs. As per coding guidelines, Focus on major issues impacting performance, readability, maintainability and security.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/aap_eda/services/project/imports.py` around lines 225 - 251, The loop is
iterating all models.Activation rows for a rulebook including entries with
restart_on_project_update=True and no source_mappings which are no-ops here;
restrict the queryset before the loop to only activations that need per-record
work by filtering for either restart_on_project_update=False OR
source_mappings__isnull=False (or equivalent emptiness check) so the loop only
processes activations requiring updates (those handled by this codepath that
call _update_source_mappings_hash or update rulebook_rulesets/git_hash), leaving
rows that need auto-restart to _auto_restart_activations().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/aap_eda/services/project/imports.py`:
- Around line 241-248: The current code silently ignores invalid or unparseable
activation.source_mappings because _update_source_mappings_hash can return None;
change the flow so invalid mappings are surfaced: when
_update_source_mappings_hash returns None (or raises a parse/shape error) mark
the activation as failed/invalid (e.g., set an activation.failure_reason or
activation.invalid_source_mappings flag) and ensure this activation is removed
from restart/sync paths (do not append "source_mappings" to update_fields and
explicitly record the error on the activation so callers know to skip it). Apply
the same change for the analogous block handled by _update_source_mappings_hash
in lines 253-273 so all parse failures are recorded and such activations are
excluded from restart logic.

---

Nitpick comments:
In `@src/aap_eda/services/project/imports.py`:
- Around line 225-251: The loop is iterating all models.Activation rows for a
rulebook including entries with restart_on_project_update=True and no
source_mappings which are no-ops here; restrict the queryset before the loop to
only activations that need per-record work by filtering for either
restart_on_project_update=False OR source_mappings__isnull=False (or equivalent
emptiness check) so the loop only processes activations requiring updates (those
handled by this codepath that call _update_source_mappings_hash or update
rulebook_rulesets/git_hash), leaving rows that need auto-restart to
_auto_restart_activations().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: e2df5ff1-ae5a-418f-811c-aed0eb524cca

📥 Commits

Reviewing files that changed from the base of the PR and between 3680b55 and bb6c136.

📒 Files selected for processing (5)
  • src/aap_eda/services/project/imports.py
  • src/aap_eda/tasks/project.py
  • tests/integration/services/data/project-07/extensions/eda/rulebooks/hello_events.yml
  • tests/integration/services/test_project_import.py
  • tests/integration/tasks/test_projects.py

Comment thread src/aap_eda/services/project/imports.py Outdated
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 1, 2026

Codecov Report

❌ Patch coverage is 96.55172% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 92.00%. Comparing base (9aba91d) to head (a5ac27a).

Files with missing lines Patch % Lines
src/aap_eda/services/project/imports.py 96.55% 1 Missing ⚠️
@@           Coverage Diff           @@
##             main    #1543   +/-   ##
=======================================
  Coverage   91.99%   92.00%           
=======================================
  Files         241      241           
  Lines       10958    10984   +26     
=======================================
+ Hits        10081    10106   +25     
- Misses        877      878    +1     
Flag Coverage Δ
unit-int-tests-3.11 92.00% <96.55%> (+<0.01%) ⬆️
unit-int-tests-3.12 92.00% <96.55%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/aap_eda/tasks/project.py 98.93% <ø> (ø)
src/aap_eda/services/project/imports.py 92.59% <96.55%> (+0.56%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@hsong-rh
Copy link
Copy Markdown
Contributor

hsong-rh commented May 1, 2026

@amasolov Thanks for the contribution! The skip guard this PR removes was added intentionally in AAP-65811 (PR #1480).

The reason auto-restart is skipped for activations with source_mappings is that source_mappings are tied to the OLD rulebook version. After sync, the new rulebook may have different source names, making the existing mappings invalid. More critically, swap_event_stream_sources() replaces ansible.eda.webhook sources with ansible.eda.pg_listener at activation creation time and persists the swapped content to activation.rulebook_rulesets. Overwriting rulebook_rulesets with the raw new rulebook content (as this PR does at line 288) destroys that swap — ansible-rulebook would then try to bind to the original webhook port, which is the exact AAP-72873 crash this is trying to fix.

Updating rulebook_hash inside source_mappings keeps the metadata in sync but doesn't re-apply the source swap. The activation would restart with unswapped rulesets containing ansible.eda.webhook + port binding instead of ansible.eda.pg_listener.

The designed solution (AAP-65811) is: skip auto-restart for source-mapped activations, leave them running with old-but-working swapped content, and surface a computed warnings field on the activation detail API when SHA256 divergence is detected. The user then resolves by updating the activation through the normal serializer flow, which re-applies the swap safely. This is already implemented and live in ActivationReadSerializer.to_representation().

@hsong-rh
Copy link
Copy Markdown
Contributor

hsong-rh commented May 1, 2026

@AlexSCorey, could you please check if the warning message is visible in the UI?

@amasolov
Copy link
Copy Markdown
Contributor Author

amasolov commented May 1, 2026

@hsong-rh Thank you for the detailed review. You're absolutely right that the skip guard was intentional, and I missed the swap_event_stream_sources() contract. Overwriting rulebook_rulesets for source-mapped activations would destroy the pg_listener swap.

I've revised the approach in d7427ea:

  • Restored the auto-restart skip guard for source-mapped activations
  • Only update rulebook_hash inside source_mappings during project sync (not rulebook_rulesets) so the post-swap content is preserved
  • Non-mapped activations without auto-restart still get the bulk rulesets update as before
  • Activations with unparseable source_mappings are now marked as ERROR

This fixes the original bug (stale rulebook_hash in source_mappings blocks manual enable/restart via _validate_sources_with_event_streams) while preserving the AAP-65811 warnings design and the swap contract.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/integration/services/test_project_import.py (1)

450-454: 💤 Low value

Add a status guard to the happy-path assertions

The test verifies that source_mappings is updated but never asserts activation.status != ActivationStatus.ERROR. If the sync implementation accidentally writes ERROR and still updates the YAML (e.g., partial execution before the guard), the test passes incorrectly.

🛡️ Suggested addition
 activation.refresh_from_db()
 updated_mappings = yaml.safe_load(activation.source_mappings)
+assert activation.status != ActivationStatus.ERROR
 assert updated_mappings[0]["rulebook_hash"] == new_hash
 assert activation.rulebook_rulesets == swapped_rulesets
 assert activation.rulebook_rulesets_sha256 == old_hash
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/services/test_project_import.py` around lines 450 - 454,
After refreshing the activation (activation.refresh_from_db()), add an assertion
that the activation's status is not ERROR to prevent false-positive happy-paths;
specifically assert activation.status != ActivationStatus.ERROR (or assert
activation.status == ActivationStatus.ACTIVE if that aligns with expected state)
and ensure ActivationStatus is imported/available in the test so the new guard
references the ActivationStatus enum rather than a raw string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/integration/services/test_project_import.py`:
- Around line 450-454: After refreshing the activation
(activation.refresh_from_db()), add an assertion that the activation's status is
not ERROR to prevent false-positive happy-paths; specifically assert
activation.status != ActivationStatus.ERROR (or assert activation.status ==
ActivationStatus.ACTIVE if that aligns with expected state) and ensure
ActivationStatus is imported/available in the test so the new guard references
the ActivationStatus enum rather than a raw string.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 4b9377fc-a412-434f-b6dd-f0e73e3fad57

📥 Commits

Reviewing files that changed from the base of the PR and between bb6c136 and d7427ea.

📒 Files selected for processing (3)
  • src/aap_eda/services/project/imports.py
  • src/aap_eda/tasks/project.py
  • tests/integration/services/test_project_import.py
✅ Files skipped from review due to trivial changes (1)
  • src/aap_eda/tasks/project.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/aap_eda/services/project/imports.py

hsong-rh
hsong-rh previously approved these changes May 5, 2026
@B-Whitt
Copy link
Copy Markdown
Contributor

B-Whitt commented May 5, 2026

Missing from description:

@B-Whitt
Copy link
Copy Markdown
Contributor

B-Whitt commented May 5, 2026

/run-e2e

Comment thread src/aap_eda/services/project/imports.py
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/integration/services/test_project_import.py (1)

458-503: ⚡ Quick win

Cover the non-dict source_mappings regression path.

This adds coverage for syntactically invalid YAML, but the new guard in src/aap_eda/services/project/imports.py at Line 288 is hit by valid YAML with the wrong shape, e.g. - 1. Adding that case here would lock in the recent TypeError fix and close the remaining uncovered branch.

Proposed test tweak
-@pytest.mark.django_db
-def test_sync_marks_error_on_unparseable_source_mappings(
+@pytest.mark.django_db
+@pytest.mark.parametrize(
+    "source_mappings",
+    [
+        "{{{ not valid yaml",
+        yaml.dump([1], default_flow_style=False),
+    ],
+)
+def test_sync_marks_error_on_invalid_source_mappings(
+    source_mappings: str,
     default_organization: models.Organization,
     storage_save_patch,
     service_tempdir_patch,
 ):
-    """An activation whose source_mappings cannot be parsed as YAML
-    is marked with ActivationStatus.ERROR after a content-changing sync."""
+    """Invalid source_mappings are marked with ActivationStatus.ERROR
+    after a content-changing sync."""
     git_mock_v1 = _mock_git_clone(
         "project-02", "aaa111aaa111aaa111aaa111aaa111aaa111aaa1"
     )
@@
     activation = models.Activation.objects.create(
         name="bad-mappings-activation",
         project=project,
         organization=default_organization,
         rulebook=rulebook,
         rulebook_name=rulebook.name,
         rulebook_rulesets=rulebook.rulesets,
         rulebook_rulesets_sha256=get_rulebook_hash(rulebook.rulesets),
-        source_mappings="{{{ not valid yaml",
+        source_mappings=source_mappings,
         is_enabled=True,
         restart_on_project_update=False,
     )
🤖 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 `@tests/integration/services/test_project_import.py` around lines 458 - 503,
Update the test_sync_marks_error_on_unparseable_source_mappings test to also
cover the non-dict source_mappings regression: create a second Activation (or
modify the existing one) whose source_mappings is syntactically valid YAML but
not a mapping (e.g. "- 1" or "[]"), call ProjectImportService.sync_project (or
service_v2.sync_project) to trigger the same import flow, then refresh the
activation and assert activation.status == ActivationStatus.ERROR and that the
status_message contains "source_mappings could not be updated"; this ensures the
guard added in src/aap_eda/services/project/imports.py (around the check
handling non-dict parsed YAML) and the Activation handling are tested.
🤖 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 `@tests/integration/services/test_project_import.py`:
- Around line 458-503: Update the
test_sync_marks_error_on_unparseable_source_mappings test to also cover the
non-dict source_mappings regression: create a second Activation (or modify the
existing one) whose source_mappings is syntactically valid YAML but not a
mapping (e.g. "- 1" or "[]"), call ProjectImportService.sync_project (or
service_v2.sync_project) to trigger the same import flow, then refresh the
activation and assert activation.status == ActivationStatus.ERROR and that the
status_message contains "source_mappings could not be updated"; this ensures the
guard added in src/aap_eda/services/project/imports.py (around the check
handling non-dict parsed YAML) and the Activation handling are tested.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 165799e6-6c60-4e5f-ac38-6f0271d15207

📥 Commits

Reviewing files that changed from the base of the PR and between d7427ea and d695eaa.

📒 Files selected for processing (2)
  • src/aap_eda/services/project/imports.py
  • tests/integration/services/test_project_import.py

@amasolov amasolov force-pushed the fix/update-source-mappings-hash-on-project-sync branch from d695eaa to a9c4579 Compare May 5, 2026 23:35
…idation

When a project sync changes rulebook content, the rulebook_hash embedded
in each activation's source_mappings was not updated. This caused
restart and enable operations to fail with:

  "Rulebook has changed since the sources were mapped.
   Please reattach event streams"

Add _update_activations_for_rulebook() which:
- Bulk-updates non-mapped, non-auto-restart activations as before
- Iterates source-mapped activations to rewrite the embedded
  rulebook_hash inside source_mappings YAML
- Marks activations with unparseable source_mappings as ERROR
- Preserves rulebook_rulesets for source-mapped activations (the
  post-swap content from swap_event_stream_sources())

Also updates the _auto_restart_activations docstring to clarify the
swap_event_stream_sources() contract and consolidates multi-line
logger f-strings.

Closes ansible#1542

Signed-off-by: Alexey Masolov <amasolov@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@amasolov amasolov force-pushed the fix/update-source-mappings-hash-on-project-sync branch from a9c4579 to a5ac27a Compare May 5, 2026 23:37
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 5, 2026

@B-Whitt
Copy link
Copy Markdown
Contributor

B-Whitt commented May 6, 2026

/run-e2e

@ttuffin ttuffin requested review from a team, hsong-rh, mkanoor and wfealdel May 7, 2026 13:49
@ttuffin
Copy link
Copy Markdown
Contributor

ttuffin commented May 7, 2026

@mkanoor @hsong-rh please review this

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.

Rulebook activation restart fails with stale source_mappings hash after project sync to new Git ref

6 participants