
    js)                        d Z ddlmZ ddlZddlZddlZddlmZmZmZ ddl	m
Z
 ddlmZmZmZmZ ddlmZ dZd	Zd
ZdZdZdZdZdZddZef	 	 	 	 	 ddZ	 d	 	 	 	 	 	 	 ddZ	 ddddd	 	 	 	 	 	 	 	 	 	 	 ddZddZd dZdd	 	 	 	 	 	 	 d!dZ y)"u  utils.canonical_root_resolver — ANU collector canonical-root resolver.

task-2636 — callback collector canonical-root resolver (Core hardening).

Spec: memory/specs/system_callback_collector_canonical_root_spec_260523.md
sha256: 6f0b04810cc458ea4cb12f3c1c9c511d14b1439917b7ef4f1ef91982e32d92c1

회장 verbatim (task md §3):
    CANONICAL_ROOT_RESOLUTION_ORDER:
      1. envelope.canonical_root (explicit override)
      2. CANONICAL_ROOT_DEFAULT = "/home/jay/workspace"
      3. ★ cwd-based lookup 사용 금지 (primary path 에서 0)

    PATH_RESOLUTION_RULE:
      absolute_path → 그대로 사용 (canonical_root 무시)
      relative_path → os.path.join(canonical_root_resolved, relative_path)
      empty_or_none → MISSING classification

    CONTEXT_MISMATCH_DETECTION:
      if os.getcwd() != canonical_root_resolved:
          log CALLBACK_COLLECTOR_CONTEXT_MISMATCH event
          proceed with canonical_root_resolved (do not use cwd)

ANCHOR-1 (spec §14): envelope.canonical_root 우선 · default /home/jay/workspace
· cwd-based primary lookup 코드 경로 0.
ANCHOR-2: cwd != canonical_root 시 CALLBACK_COLLECTOR_CONTEXT_MISMATCH 기록
+ canonical_root 사용 (cwd 결정 권한 0).
    )annotationsN)datetime	timedeltatimezone)Path)AnyDictListOptional)CANONICAL_ROOT_DEFAULTz utils.canonical_root_resolver.v1z&callback_collector_context_mismatch.v1z7memory/events/callback_collector_context_mismatch.jsonlPROCEED_WITH_CANONICAL_ROOTFOUNDMISSING_PATH_FIELDMISSING_BOTH_PATHS	NOT_FOUNDc                     t        j                  t        j                        j	                  t        t        d                  j                  d      S )z?KST ISO timestamp (asia/seoul = UTC+9). Avoid tz-DB dependency.	   )hoursz%Y-%m-%dT%H:%M:%S+09:00)r   nowr   utc
astimezoner   strftime     N/home/jay/workspace/.worktrees/task-2644-dev1/utils/canonical_root_resolver.py_now_iso_kstr   7   s<     	X\\"--YQ'(	

(,
-r   c                   t        | t              s/t        j                  j	                  |      st        d|      |S | j                  d      }|r`t        |t              s!t        dt        |      j                         t        j                  j	                  |      st        d|      |S t        j                  j	                  |      st        d|      |S )u(  Return the canonical root path for an envelope (spec §3, §5.1).

    Resolution order:
      1. ``envelope.canonical_root`` — explicit override (must be absolute)
      2. ``default`` — fallback CANONICAL_ROOT_DEFAULT (must be absolute)

    ★ cwd is never consulted (spec ANCHOR-1).
    z.canonical_root default must be absolute path: canonical_rootz)envelope.canonical_root must be str, got z/envelope.canonical_root must be absolute path: )
isinstancedictospathisabs
ValueErrorgetstr	TypeErrortype__name__)envelopedefaultexplicits      r   resolve_canonical_rootr-   @   s     h%ww}}W%@L  ||,-H(C(;DN<S<S;TU  ww}}X&A(N  77==!<WKH
 	
 Nr   c                   |t        |       }t        j                  j                  |      st	        d|      t        | t              sy| j                  |      }||dk(  ryt        |t              s$t        d|dt        |      j                         t        j                  j                  |      r|S t        j                  j                  t        j                  j                  ||            S )u  Resolve a single path field (spec §3 PATH_RESOLUTION_RULE).

    Rules:
      * absolute path → passthrough (canonical_root ignored)
      * relative path → os.path.join(canonical_root, path)
      * empty / None → "" (MISSING classification by caller)
    z!canonical_root must be absolute:  z	envelope[z] must be str, got )r-   r!   r"   r#   r$   r   r    r%   r&   r'   r(   r)   normpathjoin)r*   
path_fieldr   raws       r   resolve_pathr4   f   s     /977==(//AB
 	
 h%
,,z
"C
{cRic3
~%8c9K9K8LM
 	
 
ww}}S
77BGGLL=>>r   T)log_rootwrite_eventclockc               @   t        |       }|t        j                         }t        j                  j	                  |      t        j                  j	                  |      k(  rd||dt
        dS |r!t        j                         j                  dd nd}|r|r |       n	t               }t        ||t        | t              r| j                  d      nd|t        | t              r| j                  d      nd|t        | |      t
        d	}t        ||       d	|||t
        dS )
u  Detect cwd vs canonical_root mismatch (spec §3, §6).

    If mismatch → append a `CALLBACK_COLLECTOR_CONTEXT_MISMATCH` event
    record at ``<log_root>/memory/events/callback_collector_context_mismatch.jsonl``
    and return decision=PROCEED_WITH_CANONICAL_ROOT.

    Returns:
        {
            "mismatch": bool,
            "cwd": str,
            "canonical_root": str,
            "recorded_event_id": Optional[str],
            "decision": "PROCEED_WITH_CANONICAL_ROOT",
        }
    NF)mismatchcwdr   recorded_event_iddecision   task_idr   )	schemaevent_idts_kstr>   r:   canonical_root_envelopecanonical_root_resolveddelta_pathsr<   )r5   T)r-   r!   getcwdr"   r0   MISMATCH_DECISIONuuiduuid4hexr   MISMATCH_EVENT_SCHEMAr   r    r%   _delta_paths_append_mismatch_event)	r*   current_cwdr5   r6   r7   r   r@   tsrecords	            r   detect_context_mismatchrP      s   . ,H5Niik	ww$(8(8(HH,!%)
 	
 )4tzz|$HUW<>+ 2<Xt2Lx||I.RV2<Xt2L-.RV'5'.A)
 	v9 (%% r   c           	     ^   g }t        | t              s|S dD ]  }| j                  |      }t        |t              s%|s(t        j
                  j                  |      rH|j                  t        j
                  j                  t        j
                  j                  ||                    |S )z>Resolved paths that would differ if cwd had been used instead.)result_pathreport_path)
r   r    r%   r&   r!   r"   r#   appendr0   r1   )r*   r   outfieldr3   s        r   rK   rK      s    Ch%
/ Lll5!c3Cc0BJJrww''^S(IJKL Jr   c                L   	 |rt        |      nt        t              }|t        z  }|j                  j	                  dd       |j                  dd      5 }|j                  t        j                  | dd      dz          d	d	d	       y	# 1 sw Y   y	xY w# t        $ r Y y	w xY w)
u  Append a single JSONL record to the mismatch event log.

    ``log_root`` overrides /home/jay/workspace as the parent of memory/events
    (so regression tests can use tmp_path without touching the live tree).

    Gemini medium 대응(round-1): I/O 실패는 diagnostic 이므로 graceful — collector
    동작은 PROCEED_WITH_CANONICAL_ROOT 로 계속 (read-only fs/permissions/full disk 등).
    T)parentsexist_okazutf-8)encodingF)ensure_ascii	sort_keys
N)
r   r   MISMATCH_EVENT_LOG_RELparentmkdiropenwritejsondumpsOSError)rO   r5   baselog_pathfhs        r   rL   rL      s    	!)tH~t4J/K00dT:]]3]1 	TRHHTZZUdKdRS	T 	T 	T  	s0   AB +BB BB B 	B#"B#)	fs_existsc               *   t        | t              sddg t        dS | j                  |      }||dk(  rddg t        dS ||nt        j
                  j                  }t        |       }g }t        | ||      }|r$|j                  |        ||      rd||t        dS t        j
                  j                  |      sF|t        k7  r=t        | |t              }|r)||k7  r$|j                  |        ||      rd||t        dS d|r|d   nd|t        dS )u  Apply COLLECTOR_LOOKUP_ORDER (spec §3) and report the outcome.

    Lookup order:
      1. canonical_root (from envelope or default) + path_field
      2. fallback CANONICAL_ROOT_DEFAULT + path_field (if differs)
      3. absolute passthrough (handled implicitly by resolve_path)

    Returns:
        {
            "found": bool,
            "resolved_path": str,
            "lookup_attempts": [paths tried],
            "classification": one of FOUND / MISSING_PATH_FIELD / NOT_FOUND,
        }
    Fr/   )foundresolved_pathlookup_attemptsclassification)r   Tr   )r   r    CLASS_MISSING_PATH_FIELDr%   r!   r"   existsr-   r4   rT   CLASS_FOUNDr#   r   CLASS_NOT_FOUND)	r*   r2   rj   r3   rq   primary_rootattemptsprimaryfallbacks	            r   find_artifactrx      s6   * h%!6	
 	
 ,,z
"C
{cRi!6	
 	
 $/YRWW^^F)(3LH8ZMG '?!(#+"-	  77==,2H"Hj1G
 G+OOH%h!%-'/&1	  (0!b#)	 r   )returnr&   )r*   r   r+   r&   ry   r&   )N)r*   r   r2   r&   r   Optional[str]ry   r&   )r*   r   rM   rz   r5   rz   r6   boolr7   Optional[Any]ry   Dict[str, Any])r*   r   r   r&   ry   z	List[str])rO   r}   r5   rz   ry   None)r*   r   r2   r&   rj   r|   ry   r}   )!__doc__
__future__r   rd   r!   rG   r   r   r   pathlibr   typingr   r	   r
   r   utils.callback_envelope_schemar   RESOLVER_SCHEMArJ   r_   rF   rr   rp   CLASS_MISSING_BOTH_PATHSrs   r   r-   r4   rP   rK   rL   rx   r   r   r   <module>r      sC  8 #  	  2 2  , , B4@ R 1 / /  *### 	#R %)??? "? 		?F "&< #<<< 	<
 < < <~	2  $	JJJ 	J
 Jr   