Skip to content

Commit b0a564f

Browse files
committed
fix: regression in apply_policy on deepcopied objects
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
1 parent e838117 commit b0a564f

3 files changed

Lines changed: 28 additions & 5 deletions

File tree

cpex/framework/hooks/policies.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,23 @@ def apply_policy(
7373
original: BaseModel,
7474
modified: BaseModel,
7575
policy: HookPayloadPolicy,
76+
*,
77+
apply_to: Optional[BaseModel] = None,
7678
) -> Optional[BaseModel]:
7779
"""Apply policy-based controlled merge.
7880
7981
Only fields listed in ``policy.writable_fields`` are accepted from
8082
*modified*; all other changes are silently discarded.
8183
8284
Args:
83-
original: The original (or current) payload.
85+
original: The baseline payload to diff against (what the plugin received).
8486
modified: The payload returned by the plugin.
8587
policy: The policy defining which fields are writable.
88+
apply_to: The target payload to apply accepted changes to. When
89+
``None`` (the default), changes are applied to *original*. This
90+
is useful when the plugin receives an isolated (CoW / deepcopy)
91+
snapshot but accepted changes should be merged back into the
92+
canonical pipeline payload.
8693
8794
Returns:
8895
An updated payload with only the allowed changes applied, or
@@ -103,6 +110,7 @@ def apply_policy(
103110
>>> result.secret
104111
's'
105112
"""
113+
target = apply_to if apply_to is not None else original
106114
updates: dict[str, Any] = {}
107115
rejected: list[str] = []
108116
for field in type(modified).model_fields:
@@ -123,4 +131,4 @@ def apply_policy(
123131
rejected.append(field)
124132
if rejected:
125133
logger.warning("Policy rejected modifications to non-writable fields: %s", rejected)
126-
return original.model_copy(update=updates) if updates else None
134+
return target.model_copy(update=updates) if updates else None

cpex/framework/manager.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,14 @@ async def _run_serial_phase(
454454
if result.modified_payload is not None:
455455
if apply_modifications:
456456
current_payload, decision_plugin_name = self._apply_payload_modification(
457-
hook_ref, result, effective_payload, policy, hook_type, current_payload, decision_plugin_name
457+
hook_ref,
458+
result,
459+
plugin_input,
460+
policy,
461+
hook_type,
462+
current_payload,
463+
decision_plugin_name,
464+
apply_to=effective_payload,
458465
)
459466
else:
460467
logger.debug(
@@ -510,9 +517,17 @@ def _apply_payload_modification(
510517
hook_type: str,
511518
current_payload: Optional[PluginPayload],
512519
decision_plugin_name: Optional[str],
520+
*,
521+
apply_to: Optional[PluginPayload] = None,
513522
) -> tuple[Optional[PluginPayload], Optional[str]]:
514523
"""Apply a plugin's payload modification, respecting the hook policy.
515524
525+
Args:
526+
effective_payload: The baseline payload the plugin received (may be
527+
an isolated/CoW copy). Used for diffing to detect changes.
528+
apply_to: The canonical pipeline payload to merge accepted changes
529+
into. When ``None``, changes are applied to *effective_payload*.
530+
516531
Returns:
517532
Updated (current_payload, decision_plugin_name) tuple.
518533
"""
@@ -521,7 +536,7 @@ def _apply_payload_modification(
521536
effective_payload, BaseModel
522537
):
523538
# Same-type BaseModel payload — apply field-level policy filtering
524-
filtered = apply_policy(effective_payload, result.modified_payload, policy)
539+
filtered = apply_policy(effective_payload, result.modified_payload, policy, apply_to=apply_to)
525540
if filtered is not None:
526541
return filtered, hook_ref.plugin_ref.name
527542
else:

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)