
    !i,                        d Z ddlmZ ddlZddlZddlmZ ddlmZm	Z	  ej                  e      ZdZdZ G d d	      Zy)
u7  
core/merge/semantic_merge_interceptor_v2.py

SwarmMergeInterceptor — sends conflicting SwarmResults to Opus 4.6 for
semantic resolution and returns a single merged dict.

Note on naming
--------------
This is the Track A counterpart to ``SemanticMergeInterceptor`` in
``core/merge/semantic_merge_interceptor.py`` (Track B).  Track B operates
on RFC 6902 StateDelta patches with ``ConflictDetector``.  THIS class
operates on ``SwarmResult`` output dicts with ``SwarmConflictDetector``.
The class is named ``SwarmMergeInterceptor`` (not ``SemanticMergeInterceptor``)
to avoid name collision.

Flow
----
1. ``SwarmConflictDetector.get_conflicts(results)``
2a. No conflicts  → dict-union of all results' outputs (no Opus call)
2b. Conflicts     → for each conflicting (result_a, result_b, key),
                    format MERGE_PROMPT and call Opus 4.6
                    → parse JSON response → insert resolved_value
3. Assemble final dict, attach ``_merge_reasoning`` when conflicts existed.
4. (optional) ``reduce_and_commit()`` writes merged dict to Redis under
   ``aiva:results:{session_id}`` with a 600-second TTL so AIVA can consume it.

Opus is ONLY called when at least one conflict exists.

# VERIFICATION_STAMP
# Story: 6.02
# Verified By: parallel-builder
# Verified At: 2026-02-25
# Tests: 14/14
# Coverage: 100%
    )annotationsN)Any)SwarmConflictDetectorSwarmResultiX  a  
You are the Genesis Semantic Reducer. Multiple swarm workers have produced conflicting outputs.

SESSION: {session_id}
WORKER A ({worker_a}): {output_a}
WORKER B ({worker_b}): {output_b}
CONFLICT ON KEY: {conflict_key}

Analyze both outputs and return the most semantically correct resolution.
Respond ONLY with valid JSON: {{"resolved_value": "...", "reasoning": "...", "winner": "A|B|MERGE"}}
c                  Z    e Zd ZdZdd	dZd
dZ	 	 	 	 	 	 ddZ	 	 	 	 	 	 	 	 	 	 ddZddZy)SwarmMergeInterceptoraj  
    Resolves conflicts between SwarmResult objects using Opus 4.6.

    Parameters
    ----------
    opus_client:
        Client used to call Opus 4.6 for conflict resolution.  Must expose
        one of:
        - ``generate_content_async(prompt) -> response`` (async preferred)
        - ``generate_content(prompt) -> response``       (sync fallback)
        Response objects must have a ``.text`` attribute or be str-coercible.
        Opus is ONLY called when at least one conflict exists.
    redis_client:
        Optional Redis client (e.g. ``redis.Redis`` or ``redis.StrictRedis``).
        When provided, ``reduce_and_commit()`` writes the merged result to
        ``aiva:results:{session_id}`` via ``setex`` with ``RESULT_TTL`` seconds.
        When ``None``, ``reduce_and_commit()`` silently skips the Redis write
        and still returns the merged dict.
    Nc                >    || _         || _        t               | _        y N)opusredisr   	_detector)selfopus_clientredis_clients      A/mnt/e/genesis-system/core/merge/semantic_merge_interceptor_v2.py__init__zSwarmMergeInterceptor.__init__d   s    	!
.0    c           
       K   | j                   j                  |      }i }|D ]  }|j                  |j                          |s|S g }|D ]  \  }}}t        j                  |j                  |j                  t        j                  |j                        |j                  t        j                  |j                        |      }	| j                  |	|||       d{   \  }
}|
||<   |j                  |        ||d<   |S 7 &w)u  
        Merge *results* into a single resolved dict.

        Algorithm
        ---------
        1. Run SwarmConflictDetector.get_conflicts(results).
        2. Compute base merge: union of all output dicts (later results
           overwrite earlier ones for non-conflicting keys).
        3. For each conflict:
           a. Format MERGE_PROMPT with the two conflicting results and key.
           b. Call Opus 4.6 for the resolved_value.
           c. On JSON parse failure → fall back to the value from the
              result with the higher confidence score.
           d. Store per-conflict reasoning in ``_merge_reasoning``.
        4. Apply resolved values over the base merge.
        5. Return final dict.  If any conflicts existed, ``_merge_reasoning``
           key is present.

        Parameters
        ----------
        results:
            List of SwarmResult objects, possibly spanning multiple workers
            for the same session.

        Returns
        -------
        dict
            Merged output dict.  Contains ``_merge_reasoning`` key only
            when at least one conflict was resolved via Opus.
        )
session_idworker_aoutput_aworker_boutput_bconflict_key)promptresult_aresult_br   N_merge_reasoning)r   get_conflictsupdateoutputMERGE_PROMPTformatr   worker_namejsondumps_resolve_conflictappend)r   results	conflictsmergedresultreasoning_logr   r   r   r   resolved_valueentrys               r   reducezSwarmMergeInterceptor.reducem   s    @ NN009	 "$ 	)FMM&--(	) M %'09 	(,Hh!((#..!--HOO4!--HOO4) ) F +/*@*@!!)	 +A + %!NE $2F<   '%	(( &3!"%s   CDD'Dc                   K   | j                  |       d{   }| j                  7| j                  j                  d| t        t	        j
                  |             |S 7 Iw)a  
        Merge *results* and write the final dict to Redis for AIVA consumption.

        Algorithm
        ---------
        1. Calls ``reduce(results)`` to obtain the merged dict.
        2. If ``redis_client`` is set, serialises the merged dict to JSON and
           calls ``redis.setex(key, RESULT_TTL, value)`` where::

               key = f"aiva:results:{session_id}"
               RESULT_TTL = 600  (10 minutes)

        3. Returns the merged dict (identical to what ``reduce()`` returned).

        Notes
        -----
        - If ``redis_client`` is ``None`` (not injected), the Redis write is
          silently skipped and ``reduce()`` still returns its result.
        - ``setex`` is used (not ``set`` + ``expire``) so the key write and TTL
          assignment are atomic.

        Parameters
        ----------
        results:
            List of SwarmResult objects to merge.
        session_id:
            Unique session identifier.  The Redis key written is
            ``aiva:results:{session_id}``.

        Returns
        -------
        dict
            The fully merged output dict (same object returned by ``reduce()``).
        Nzaiva:results:)r0   r   setex
RESULT_TTLr%   r&   )r   r)   r   r+   s       r   reduce_and_commitz'SwarmMergeInterceptor.reduce_and_commit   s[     N {{7++::!JJ
|,

6"
  ,s   A#A!A
A#c                  K   	 | j                  |       d{   }t        j                  |      }|d   }|j                  dd      }|j                  dd      }	|dk(  r|j                  j                  ||      }n!|dk(  r|j                  j                  ||      }|||	|j
                  |j
                  d	}
||
fS 7 # t        $ r}t        j                  d
|t        |      j                  |       |j                  |j                  kD  r|}n|}|j                  j                  |      }||j
                  d| d|j
                  |j
                  d	}
||
fcY d}~S d}~ww xY ww)a$  
        Call Opus and parse its JSON response for one conflicting key.

        Returns
        -------
        (resolved_value, reasoning_entry)

        On JSON parse failure, falls back to the value from the result
        with the higher confidence score (result_a wins on tie).
        Nr.   winnerMERGE	reasoning AB)keyr6   r8   r   r   un   SwarmMergeInterceptor: Opus resolution failed for key %r (%s: %s) — falling back to higher-confidence resultzOpus parse failure: z; used higher-confidence result)
_call_opusr%   loadsgetr!   r$   	Exceptionloggerwarningtype__name__
confidence)r   r   r   r   r   	opus_textparsedr.   r6   r8   r/   excfallback_resultfallback_values                 r   r'   z'SwarmMergeInterceptor._resolve_conflict   sr    ".	)"oof55IZZ	*F#$45NZZ'2F

;3I }!)!4!4\>!R3!)!4!4\>!R $ &$00$00E "5((+ 6.  	)NN@S	"" ""X%8%88"*"*,3377EN#)553C58WX$00$00E "5((-	)sF   E(C  B>B$C  =E(>C   	E%	BE E%E( E%%E(c                   K   t        | j                  d      r$| j                  j                  |       d{   }n| j                  j                  |      }t        |d      r|j                  S t        |      S 7 Cw)z
        Dispatch *prompt* to the injected Opus client.

        Supports both async (``generate_content_async``) and sync
        (``generate_content``) client interfaces for compatibility with
        different SDK versions and test mocks.
        generate_content_asyncNtext)hasattrr   rL   generate_contentrM   str)r   r   responses      r   r=   z SwarmMergeInterceptor._call_opus'  sf      49967!YY==fEEHyy11&9H8V$== 8} Fs   5A=A;AA=r
   )r   r   r   r   returnNone)r)   list[SwarmResult]rR   dict)r)   rT   r   rP   rR   rU   )
r   rP   r   r   r   r   r   rP   rR   ztuple[Any, dict])r   rP   rR   rP   )	rD   
__module____qualname____doc__r   r0   r4   r'   r=    r   r   r   r   O   sp    (1CJ.". . 
	.h?)?) ?) 	?)
 ?) 
?)Br   r   )rX   
__future__r   r%   loggingtypingr   core.merge.swarm_resultr   r   	getLoggerrD   rA   r3   r"   r   rY   r   r   <module>r_      sH   "H #    F			8	$ 

$g gr   