
    i*                        d Z ddlmZ ddlZddlmZmZ ddlmZm	Z	  ej                  e      Z G d de      Ze G d d	             Z G d
 d      Zy)u  
core/merge/compensating_transaction.py

CompensatingTransaction — Partial-Fail Recovery for failed saga deltas.

Generates inverse RFC 6902 patches for failed agent deltas and applies
rollback operations so that the shared state can be wound back to a
consistent pre-saga baseline.

Supported inverse mapping (RFC 6902):
    add     -> remove  (delete the key that was inserted)
    remove  -> add     (re-insert with original value from current_state)
    replace -> replace (restore original value from current_state)
    move    -> move    (swap path and from)
    copy    -> IrreversibleOperationError (cannot determine original)
    unknown -> IrreversibleOperationError

Design notes:
  - Patch operations are inverted in REVERSE order so the rollback
    correctly undoes each step starting from the most recent.
  - If a cold_ledger is supplied, a "compensation_applied" event is
    appended to the ledger after collecting all inverse ops.
  - Partial compensation (some ops irreversible) sets success=False
    and final_status="partial_compensation" but still returns the
    reversible ops so callers can apply best-effort rollback.

# VERIFICATION_STAMP
# Story: 7.06
# Verified By: parallel-builder
# Verified At: 2026-02-25
# Tests: 9/9
# Coverage: 100%
    )annotationsN)	dataclassfield)AnyOptionalc                      e Zd ZdZy)IrreversibleOperationErrorue  
    Raised when an RFC 6902 operation cannot be reliably inverted.

    Currently applies to:
      - 'copy'   — we cannot know what was at the destination before the copy
      - unknown  — unrecognised op type

    Callers should catch this during compensation and record partial-compensation
    status rather than aborting the entire rollback.
    N)__name__
__module____qualname____doc__     </mnt/e/genesis-system/core/merge/compensating_transaction.pyr	   r	   1   s    	r   r	   c                  F    e Zd ZU dZded<    ee      Zded<   dZded	<   y
)CompensationResulta  
    Outcome from CompensatingTransaction.compensate().

    Attributes:
        success:          True if ALL deltas were fully inverted with no
                          IrreversibleOperationError encountered.
        compensating_ops: Flat list of RFC 6902 inverse operation dicts,
                          ready to be applied to restore state.
        final_status:     One of "fully_compensated" | "partial_compensation" |
                          "no_ops".
    boolsuccess)default_factorylistcompensating_opsunknownstrfinal_statusN)	r
   r   r   r   __annotations__r   r   r   r   r   r   r   r   r   C   s(    
 M"48d8!L#!r   r   c                  b    e Zd ZdZd	d
dZ	 	 	 	 	 	 ddZddZddZedd       Z	edd       Z
y)CompensatingTransactiona  
    Generates and records inverse RFC 6902 patches for failed saga deltas.

    Usage::

        ct = CompensatingTransaction(cold_ledger=ledger)
        result = ct.compensate(failed_deltas, current_state)
        if result.success:
            # apply result.compensating_ops to restore state
            ...

    Args:
        cold_ledger: Optional ColdLedger instance.  If provided, a
                     "compensation_applied" event is written after
                     collecting inverse ops.
    Nc                    || _         y N)ledger)selfcold_ledgers     r   __init__z CompensatingTransaction.__init__m   s	    !r   c                   g }g }|D ]:  }| j                  |      }|s	 | j                  ||      }|j                  |       < |s|sd}	n|rd}	nd}	| j                  r,|r*	 | j                  j                  ddt        |      |	d       t        t        |      d
k(  ||	      S # t        $ r:}t        j                  d|       |j                  t        |             Y d}~d}~ww xY w# t        $ r t        j                  d	       Y w xY w)a  
        Build inverse patches for every delta in *failed_deltas*.

        For each delta:
          1. Extract the RFC 6902 patch (handles StateDelta objects and plain
             dicts transparently).
          2. Call invert_patch() to generate the inverse ops in reverse order.
          3. Append inverse ops to the running collection; accumulate errors.

        After processing all deltas, optionally write a ledger event, then
        return a CompensationResult.

        Args:
            failed_deltas: List of StateDelta objects (or plain dicts with a
                           ``patch`` key) representing the deltas that failed
                           or need to be rolled back.
            current_state: The current shared state dict used as the source of
                           truth for original values when inverting remove/replace
                           ops.

        Returns:
            CompensationResult with success flag, inverse ops, and status string.
        z'Irreversible op during compensation: %sNno_opspartial_compensationfully_compensatedcompensationcompensation_applied)	ops_countstatusz0Failed to write compensation event to ColdLedgerr   )r   r   r   )_extract_patchinvert_patchextendr	   loggerwarningappendr   r    write_eventlen	Exceptionr   )
r!   failed_deltascurrent_stateall_opserrorsdeltapatchinverseexcr+   s
             r   
compensatez"CompensatingTransaction.compensatet   s   8 !" 
	(E''.E(++E=Aw'
	( vF+F(F ;;7S''"*"%g,&A "K1$$
 	
/ . (H#Nc#h''((  SQRSs)   #B$!)C* $	C'-0C""C'*D
Dc                p    g }t        |      D ]%  }| j                  ||      }|j                  |       ' |S )a  
        Generate the RFC 6902 inverse of *patch* against *original_state*.

        Operations are processed in REVERSE order so that the generated
        rollback sequence correctly undoes each step from last to first
        (matching the contract for saga compensation).

        Args:
            patch:          List of RFC 6902 operation dicts to invert.
            original_state: Dict containing the values that existed before
                            the patch was applied; used to restore remove and
                            replace ops.

        Returns:
            List of inverse RFC 6902 operation dicts.

        Raises:
            IrreversibleOperationError: if any op in *patch* cannot be
                                        reliably inverted.
        )reversed_invert_single_opr1   )r!   r:   original_stateinverse_opsopinvs         r   r-   z$CompensatingTransaction.invert_patch   sD    * #%5/ 	$B((^<Cs#	$ r   c                X   |j                  dd      }|j                  dd      }|dk(  rd|dS |dk(  r| j                  ||      }d||dS |dk(  r| j                  ||      }d||dS |d	k(  r|j                  d
d      }d	||dS |dk(  rt        d|d      t        d|d|d      )a  
        Invert a single RFC 6902 operation dict.

        Mapping:
            add     -> remove (same path)
            remove  -> add    (same path, value restored from original_state)
            replace -> replace (same path, value restored from original_state)
            move    -> move   (swap path and from)
            copy    -> IrreversibleOperationError
            other   -> IrreversibleOperationError
        rC    pathaddremove)rC   rG   )rC   rG   valuereplacemovefrom)rC   rG   rM   copyzCannot invert 'copy' at path u.    — destination state before copy is unknown.zUnknown RFC 6902 op type z	 at path .)get_get_value_at_pathr	   )r!   rC   rA   op_typerG   original_value	from_paths          r   r@   z)CompensatingTransaction._invert_single_op   s     &&r"vvfb!e"D11 !44^TJNGG	!!44^TJN#TNKKvr*I !)TBB,/x 8< <  -+G;ixqI r   c                    t        | d      r| j                  }n%t        | t              r| j	                  dg       }ng }t        |t
        t        f      rt        |      S g S )z
        Return the patch list from *delta*, normalising both StateDelta objects
        (which store patch as a frozen tuple) and plain dicts.

        Returns an empty list if no patch can be extracted.
        r:   )hasattrr:   
isinstancedictrP   r   tuple)r9   r:   s     r   r,   z&CompensatingTransaction._extract_patch  sS     5'"KKEt$IIgr*EEedE]+;	r   c                    |j                  d      D cg c]  }|s|	 }}| }|D ]%  }t        |t              r|j                  |      }% y |S c c}w )u  
        Navigate a nested dict by RFC 6902 JSON Pointer path and return the
        value found at that location.

        Examples:
            path="/config/mode"  ->  state["config"]["mode"]
            path="/name"         ->  state["name"]

        Returns None if any key along the path is missing or if *state* is not
        a dict (fail-safe for compensation — we still emit the inverse op, just
        with None as the restore value).
        /N)splitrW   rX   rP   )staterG   ppartscurrentparts         r   rQ   z*CompensatingTransaction._get_value_at_path  s\     !JJsO1qq11 	D'4(!++d+		
  2s
   AAr   )r"   zOptional[Any]returnNone)r5   r   r6   rX   rb   r   )r:   r   rA   rX   rb   r   )rC   rX   rA   rX   rb   rX   )r9   r   rb   r   )r]   rX   rG   r   rb   r   )r
   r   r   r   r#   r=   r-   r@   staticmethodr,   rQ   r   r   r   r   r   [   se    ""B
B
 B
 
	B
H>)V  $  r   r   )r   
__future__r   loggingdataclassesr   r   typingr   r   	getLoggerr
   r/   r4   r	   r   r   r   r   r   <module>rj      s\    D #  (  			8	$
 
$ " " ".O Or   