
    jV                    ~   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 ddl	m
Z
 ddlmZmZmZmZmZmZ dZdZd	Zd
Z e
d      Z e
d      ZdZdZdZdZdZ eeeeeh      Ze G d d             Ze G d d             Z ef	 	 	 	 	 d&dZ!d'd(dZ"edef	 	 	 	 	 	 	 	 	 	 	 d)dZ# ejH                  dejJ                        Z& ejH                  dejJ                        Z'd*dZ(ef	 	 	 	 	 d+dZ)eedeeed	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d,dZ*e G d d              Z+d!Z,	 	 	 	 	 	 d-d"Z-d.d#Z.ded$	 	 	 	 	 	 	 	 	 	 	 	 	 d/d%Z/y)0u  utils.callback_authority_4source_validator — 4-source callback authority gate.

task-2680 — CALLBACK_SELF_KEY_REGISTRATION_HARDENING_FIX_IMPLEMENTATION
chair_authorization_id: CHAIR-AUTH-CALLBACK-SELF-KEY-HARDENING-FIX-20260526-JJONGS-IMPLEMENT-001

★ 본 module 은 callback collector spawn 시점에 4-source 교차검증을 수행하는
   pure-function validator 이다. 코드 외부 (cokacdir CLI, schedule_history file)
   의 actual owner key 를 query 해 envelope text 의 owner_key 와 1:1 대조한다.

4 source (회장 verbatim · task-2677 4source_verify_doctrine §1.1):

   * S1 schedule_history — /home/jay/.cokacdir/schedule_history/<schedule_id>.log
     의 callback fire 시점 bot_key_verifier / workspace / prompt 채록.
   * S2 cron-history     — cokacdir --cron-history <schedule_id> --key <K>
     의 actual owner key 채록 (★ K = ANU key + executor self-key 2회 query).
   * S3 envelope         — S1.prompt 본문 (envelope text) 의 owner_key /
     self_key / schema 텍스트 채록.
   * S4 result artifact  — memory/events/<task_id>.*-result-*.json 의
     callback_cron_id / callback_registration_status / callback_role 채록.

분류 enum (회장 task-2680 수정 목표 4):
   * ANU_AUTHORITATIVE             — 4-source PASS, S2 owner == ANU key
   * NON_AUTHORITATIVE_SELF_COLLECTOR
                                   — S2 owner == executor self-key
                                     (★ self-key callback 자동 분류)
   * NON_AUTHORITATIVE_KEY_DRIFT   — S2 owner != ANU and != executor self-key
   * UNDETERMINED_HISTORY_GAP      — S1/S2 query 실패 (cron-history 부재)
   * PROMPT_DRIFT                  — S3 envelope text 의 owner_key 가 ANU 아님

Layer A · Read-only · NO-CRON (회장 9-R.1 1:1):
   본 module 은 cron register/remove, dispatch, subprocess cokacdir exec 0.
   external state query (file read, subprocess --cron-history) 만 수행.

★ ANU independent reverify trigger (회장 task-2680 수정 목표 5):
   classify_collector_authority 가 NON_AUTHORITATIVE_* / UNDETERMINED_* /
   PROMPT_DRIFT 를 반환하면 caller (collector helper integration) 가
   anu_independent_reverify_request 를 emit 해야 한다. 본 module 은 trigger
   결정만 노출 — 실 dispatch 는 별도 layer.
    )annotationsN)	dataclassfield)Path)AnyCallableDictListOptionalSequencez-utils.callback_authority_4source_validator.v1c119085addb0f8b7
6937032012z/usr/local/bin/cokacdirz$/home/jay/.cokacdir/schedule_historyz!/home/jay/workspace/memory/eventsANU_AUTHORITATIVE NON_AUTHORITATIVE_SELF_COLLECTORNON_AUTHORITATIVE_KEY_DRIFTUNDETERMINED_HISTORY_GAPPROMPT_DRIFTc                      e Zd ZU dZdZded<   dZded<   dZded<   dZded<   dZ	ded<    e
e	      Zd
ed<    e
e	      Zd
ed<   ddZy)FourSourceEvidencez5Raw + parsed evidence from each source (audit trail).NOptional[Dict[str, Any]]s1_schedule_historys2_anu_cron_historys2_self_cron_historys3_envelopes4_result_artifactdefault_factory	List[str]queried_pathsquery_errorsc           	         | j                   | j                  | j                  | j                  | j                  t        | j                        t        | j                        dS )N)r   r   r   r   r   r   r    )r   r   r   r   r   listr   r    selfs    -utils/callback_authority_4source_validator.pyto_jsonzFourSourceEvidence.to_jsonU   sU    #'#;#;#'#;#;$($=$=++"&"9"9!$"4"45 !2!23
 	
    returnDict[str, Any])__name__
__module____qualname____doc__r   __annotations__r   r   r   r   r   r"   r   r    r&    r'   r%   r   r   J   sc    ?481848185929,0K)03707$T:M9:#D9L)9	
r'   r   c                      e Zd ZU dZded<   ded<   ded<   ded<   ded<   ded<   d	ed
<   d	ed<    ee      Zded<   dZded<   dZ	ded<   ddZ
y)AuthorityClassificationz0Final 4-source classification + escalation hint.strschemaclassificationschedule_idtask_idexecutor_keyanu_keyboolis_authoritativerequires_anu_reverifyr   r   reasonsNzOptional[FourSourceEvidence]evidenceOptional[str]escalation_hintc                D   | j                   | j                  | j                  | j                  | j                  | j
                  | j                  | j                  t        | j                        | j                  r| j                  j                         nd | j                  dS )Nr4   r5   r6   r7   r8   r9   r;   r<   r=   r>   r@   )r4   r5   r6   r7   r8   r9   r;   r<   r"   r=   r>   r&   r@   r#   s    r%   r&   zAuthorityClassification.to_jsonp   s{    kk"11++|| --|| $ 5 5%)%?%?DLL)37==--/d#33
 	
r'   r(   )r+   r,   r-   r.   r/   r   r"   r=   r>   r@   r&   r0   r'   r%   r2   r2   a   s[    :KLLt4GY4-1H*1%)O])
r'   r2   c                h   ||  dz  }|j                         sy	 |j                  dd      5 }|j                         D cg c]#  }|j                         s|j                         % }}ddd       syt	        j
                  |d         S c c}w # 1 sw Y   )xY w# t        t        f$ r Y yw xY w)zRead /home/jay/.cokacdir/schedule_history/<schedule_id>.log (JSONL).

    Returns the LAST line parsed as JSON (most recent execution) or None
    if the file is missing / unreadable / empty.
    .logNrutf-8encoding)existsopen	readlinesstripjsonloadsOSError
ValueError)r6   history_dirlog_pathflnliness         r%   _read_schedule_historyrW      s     }D11H??]]3]1 	GQ*+++-FB288:RXXZFEF	Gzz%)$$ G	G 	G
 Z  sF   B BBB)B+
B 6B BBB B10B1c                F    t        j                  t        |       dd|      S )u   Default subprocess runner — calls cokacdir CLI.

    Returns subprocess.CompletedProcess (or equivalent). Callers may inject a
    fake runner for tests.
    T)capture_outputtexttimeout)
subprocessrunr"   )argvr[   s     r%   _default_cokacdir_runnerr_      s"     >>T
4dG r'   c                   |xs t         }|d| dt        |      d|g}	  ||d      }t        |d	d
      }t        |dd      xs dj	                         }	t        |dd      xs dj	                         }
|dk7  rdd| d|
dd  ddS |	sddddS 	 t        j                  |	j                         d         }t        |t              sddddS |S # t        $ r}dd|ddcY d}~S d}~ww xY w# t        t        f$ r dd|	dd  ddcY S w xY w)zQuery cokacdir --cron-history <schedule_id> --key <key>.

    Returns dict with parsed JSON output (or {"status":"error", ...} on
    failure). Schema: {status, count, history, id, ...}.
    z--cron-historyz--chatz--key   errorzsubprocess raised: N)statusrb   count
returncode   stdout stderrr   zcokacdir exit=z stderr=   zempty stdoutrI   z
bad json: z
not a dict)r_   r3   	ExceptiongetattrrM   rN   rO   
splitlinesrQ   
IndexError
isinstancedict)r6   keychat_idrunnercokacdir_pathr^   procexcrcrg   ri   payloads               r%   _query_cron_historyry      sd    //FGDZdB 
|Q	'BdHb)/R668FdHb)/R668F	Qw%bT&#,@
 	

 !NTJJX**V..045 gt$!L4HHN'  Z!.A#,ITXYYZ 
# X!j,GRVWWXs/   	C &C, 	C)C$C)$C),D
	D
z*owner_key[\"']?\s*[:=]\s*[\"']?([a-f0-9]+)z)self_key[\"']?\s*[:=]\s*[\"']?([0-9a-f]+)c                   | s	dddddddS 	 t        j                  |       }t        |t              rE|j	                  d      |j	                  d      |j	                  d      |j	                  d      d	d
dS 	 t        j                  |       }t        j                  |       }|r|j                  d      nd|r|j                  d      ndddt        |xs |      ddS # t
        t        f$ r Y ww xY w)zParse envelope text (the cron prompt body) for owner_key / self_key /
    schema. Tolerates JSON envelope or YAML-style envelope text.

    Returns dict with keys: owner_key, self_key, schema, task_id, parse_ok.
    NFzempty prompt)	owner_keyself_keyr4   r7   parse_okreasonr{   r|   r4   r7   TrN   )r{   r|   r4   r7   r}   formatrf   regex)rN   rO   ro   rp   getrQ   	TypeError_OWNER_KEY_REsearch_SELF_KEY_REgroupr:   )promptrx   owner_mself_ms       r%   _parse_envelope_from_promptr      s     !ttUnN 	N**V$gt$$[[5#KK
3!++h/";;y1    % ""6*G  (F)0W]]1%d'-FLLO4*F+  	" s   A)C C0/C0c                "   |j                         sy|  d}t        |j                  |      d      }|D ]6  }	 |j                  dd      5 }t	        j
                  |      cddd       c S  y# 1 sw Y   nxY wG# t        t        f$ r Y Xw xY w)zScan memory/events/<task_id>.*-result-*.json (most recent first).

    Returns the parsed dict of the most recent match or None if absent.
    Nz.*result*.jsonT)reverserE   rF   rG   )rJ   sortedglobrK   rN   loadrP   rQ   )r7   artifact_dirpatternmatchespathrT   s         r%   _read_result_artifactr      s      	(G\&&w/>G 	31 $Qyy|$ $ 	$ $ $$ 		s)   A<A/!	A</A8	4A<<BB)r9   rr   cokacdir_runnerrR   r   rt   c        	        ,   t               }	g }
t        | |      }||	_        |	j                  j	                  t        ||  dz               ||	j                  j	                  d|  d       t        | ||||      }t        | ||||      }||	_        ||	_	        d'd}|j                  d      d	k7  r ||      nd
}|j                  d      d	k7  r ||      nd
}|r|xs i j                  dd      nd}t        |      }||	_        t        ||      }||	_        |dk  r|dk  r|
j	                  d       t        }n|dk\  r'|dk  r"|
j	                  d| d| d| d       t         }n|dk\  r$|dk\  r|
j	                  d| d| d       t"        }ni|dk(  r|dk(  r|
j	                  d       t        }nG|dk\  r$|dk  r|
j	                  d| d| d       t$        }n|
j	                  d| d| d       t        }|t$        k(  r7|j                  d      r&|d   |k7  r|
j	                  d |d   d!       t&        }|t$        k(  }|t(        v }d}|t         k(  rd"}n#|t"        k(  rd#}n|t        k(  rd$}n|t&        k(  rd%}t+        t,        || ||||||
|	|&      S )(u  4-source cross-check at collector spawn time. Returns classification.

    ★ Transition table (task-2680 6 수정 목표 #3 + #4):

      | S2.anu_count | S2.self_count | S3.owner_key      | result               |
      | >=1          | 0             | == ANU            | ANU_AUTHORITATIVE     |
      | 0            | >=1           | *                 | NON_AUTHORITATIVE_SELF_COLLECTOR |
      | 0            | 0             | *                 | UNDETERMINED_HISTORY_GAP |
      | >=1          | >=1           | *                 | NON_AUTHORITATIVE_KEY_DRIFT (★ both registered = collision) |
      | *            | *             | != ANU (≠None)    | PROMPT_DRIFT (overrides PASS) |

    Read-only · NO-CRON · no mutation.
    )rR   rD   NzS1: schedule_history/z.log missing)rr   rs   rt   c                    | j                  d      }t        |t              r|S t        |t              r|j	                         rt        |      S y)Nrd   r   )r   ro   intr3   isdigit)rx   cs     r%   _countz,classify_collector_authority.<locals>._countE  s=    KK aHa!))+q6Mr'   rc   rb   rI   r   rh   )r   r   uu   S2 query failed for both ANU and executor self-key — cron-history unavailable (likely cokacdir CLI absent or auth).rf   zS2.cron-history with --key z returned count=z (>=1) AND ANU key count=uP    — executor self-key callback detected (★ NON_AUTHORITATIVE_SELF_COLLECTOR).zS2: BOTH ANU (count=z) AND executor self-key (count=ud   ) cron-history returned — registration drift / dual-owner collision (NON_AUTHORITATIVE_KEY_DRIFT).u   S2: neither ANU nor executor self-key cron-history returned a matching schedule — registration history gap (UNDETERMINED_HISTORY_GAP).zS2: ANU key cron-history count=z# (>=1) and executor self-key count=u    — actual owner is ANU.zS2: anu_count=z self_count=u1    — unhandled state, defaulting to UNDETERMINED.r{   zS3.envelope.owner_key=u5    != ANU key — envelope text drifted (PROMPT_DRIFT).u   ★ self-key callback detected — emit ANU independent reverify request (utils.callback_collector_helper_integration.emit_anu_independent_reverify_request) + 회장 chat 보고.uX   ★ dual-owner cron registration — escalate to chair, halt collector self-attestation.z?retry S2 query after backoff; if persistent, escalate to chair.u_   envelope text owner_key drifted from ANU canonical — reject envelope, cancel collector spawn.rB   )rx   r*   r)   r   )r   rW   r   r   appendr3   r    ry   r   r   r   r   r   r   r   r   r   r   r   r    REVERIFY_TRIGGER_CLASSIFICATIONSr2   VALIDATOR_SCHEMA)r6   r8   r7   r9   rr   r   rR   r   rt   r>   r=   s1s2_anus2_selfr   	anu_count
self_countr   s3s4clsis_authneeds_reverifyhints                           r%   classify_collector_authorityr     sR   2 "#HG 
 	EB#%H !!#k{m44H&H"IJ	z$$'<[M%VW !WgmF "\7mG $*H $+H! #)**X"6'"AvrI$+KK$9W$D"J .0bhB^^Hb)RF	$V	,BH 
w\	BB"$H 1}aM	
 ' 
qY!^),7Gl3I; ?ZZ	

 / 
aJ!O"9+ . \ "JJ	

 * 
aJ!O*	

 ' 
aJ!O-i[ 9''1l2KM	
   	YK|J< @; ;	
 ' BFF;$7B{Ow<V$R_$7 84 4	
  &&G<<ND
..K 	
 
+	+* 	 
(	(M 	 
	0 	
 #! , r'   c                  `    e Zd ZU dZded<   ded<   ded<   ded<   ded<   ded<   ded	<   dd
Zy)ReverifyRequestu  Descriptor for an ANU independent reverify task dispatch request.

    NO subprocess / NO dispatch — pure data object. The caller (collector
    helper integration) emits this to the ANU dispatch path or escalates
    to chair via extract_followup.send_anu_notify.
    r3   r4   r7   original_schedule_idr5   chair_authorization_idartifact_path_hintr~   c                    | j                   | j                  | j                  | j                  | j                  | j
                  | j                  dS )Nr4   r7   r   r5   r   r   r~   r   r#   s    r%   r&   zReverifyRequest.to_json  sE    kk||$($=$="11&*&A&A"&"9"9kk
 	
r'   Nr(   )r+   r,   r-   r.   r/   r&   r0   r'   r%   r   r     s5     KLK	
r'   r   z>utils.callback_authority_4source_validator.reverify_request.v1c           	         | j                   syd| j                   d}| j                  r| j                  d   nd| j                   }t	        t
        | j                  | j                  | j                  |||      S )a  Build a reverify request descriptor if classification triggers reverify.

    Returns None if classification == ANU_AUTHORITATIVE (no reverify needed).
    Otherwise returns a ReverifyRequest the caller can pass to ANU dispatch
    or to chair escalation.
    Nzmemory/events/z%.independent_anu_reverify.result.jsonr   zclassification=r   )r<   r7   r=   r5   r   REVERIFY_SCHEMAr6   )r5   r   artifact_hintprimary_reasons       r%   &build_anu_independent_reverify_requestr     s     //
//0 1/ 	/ 
 &4%;%;q!~<<=>  &&+77%445( r'   c                (    | j                   t        k(  S )zTrue if classification == NON_AUTHORITATIVE_SELF_COLLECTOR.

    Convenience predicate for callers that only care about the self-key
    case (the dominant Track A / Track J failure mode).
    )r5   r   )r5   s    r%   is_self_key_callbackr     s     ((,LLLr'   )envelope_owner_keyr9   c                ~   g }||j                  d       t        }n{||k(  r|j                  d|d       t        }nZ||k(  r:|r ||k7  r|j                  d|       t        }n3|j                  d       t        }n|j                  d|d       t
        }t        t        || ||||t        k(  |t        v |dd	      S )
zTest-friendly fast-path: classify from already-observed values without
    hitting the filesystem or subprocess.

    Used by regression suite when fixtures already capture the observed owner
    binding (e.g. parsed schedule_history fixtures).
    Nu*   observed_owner_key is None — history gapz$observed owner == executor self-key u%    — NON_AUTHORITATIVE_SELF_COLLECTORzenvelope text drift: owner_key=u+   observed owner == ANU key — authoritativezobserved owner u3    != ANU and != self — NON_AUTHORITATIVE_KEY_DRIFTrB   )	r   r   r   r   r   r   r2   r   r   )r6   r8   r7   observed_owner_keyr   r9   r=   r   s           r%   classify_from_observedr     s     G!CD&	|	+2<2B C/ /	
 /	w	&"4"?NN12D1GH CNNHI#C03 4* *	
 *"! 11!%EE r'   )r6   r3   rR   r   r)   r   )ra   )r^   zSequence[str]r[   r   r)   r   )r6   r3   rq   r3   rr   r3   rs   -Optional[Callable[[Sequence[str], int], Any]]rt   r3   r)   r*   )r   r3   r)   r*   )r7   r3   r   r   r)   r   )r6   r3   r8   r3   r7   r3   r9   r3   rr   r3   r   r   rR   r   r   r   rt   r3   r)   r2   )r5   r2   r   r3   r)   zOptional[ReverifyRequest])r5   r2   r)   r:   )r6   r3   r8   r3   r7   r3   r   r?   r   r?   r9   r3   r)   r2   )0r.   
__future__r   rN   rer\   dataclassesr   r   pathlibr   typingr   r   r	   r
   r   r   r   ANU_KEYDEFAULT_CHAT_IDCOKACDIR_CLISCHEDULE_HISTORY_DIRRESULT_ARTIFACT_DIRr   r   r   r   r   	frozensetr   r   r2   rW   r_   ry   compile
IGNORECASEr   r   r   r   r   r   r   r   r   r   r0   r'   r%   <module>r      s  &N #  	  (  @ @B 
(BC >?  ( #E  ; 5  $-$	. $   
 
 
, 
 
 
D - 0 #<@%++	+ + :	+
 + +` 

H"--XrzzFV!P - 8 "EI,,%cc c 	c
 c c Cc c c c cP 
 
 
6 S+   	DM )-44 4 	4
 &4 &4 4 4r'   