
     jF                    `   d Z ddlmZ ddl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  e
d      Zedz  Zd	Z e
d
      ZddZddZdddZddZddZddZd dZd!dZddd"dZddd"dZddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d#dZddd"dZdd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d$dZy)%u  utils/state_repair.py — checksum mismatch 복구 코드 경로 + chairman approval evidence.

task-2472 구현 5: state 파일 수리는 반드시 이 모듈을 통해서만.
manual 편집 금지 — evidence + sha256 백업 + audit 없으면 fail-closed.

repair_action 종류:
  - "recompute_checksum": 기존 state 그대로 유지하고 _checksum 재계산
  - "rollback_to_backup": 가장 최신 backup 파일로 복원
  - "manual_fixup": new_state로 덮어쓰기 (위험, evidence 강력 검증)
    )annotationsN)datetimetimezone)Path)Optionalz.tasks/statez.backupsz.verify-pending-z0memory/orchestration-audit/checksum-repair.jsonlc                 f    t        j                  t        j                        j	                  d      S )Nz%Y-%m-%dT%H:%M:%SZr   nowr   utcstrftime     )/home/jay/workspace/utils/state_repair.py_now_isor   &   s!    <<%../CDDr   c                 f    t        j                  t        j                        j	                  d      S )u0   파일명용 타임스탬프 (YYYYMMDDTHHMMSSZ).z%Y%m%dT%H%M%SZr	   r   r   r   _now_ts_compactr   *   s!    <<%../?@@r   c                \    | xs) t        t        j                  j                  dd            S )NWORKSPACE_ROOTz/home/jay/workspace)r   osenvironget	workspaces    r   _workspace_rootr   /   s#    URZZ^^,<>STUUr   c                   t        j                         }| j                  d      5 t        fdd      D ]  }|j	                  |        	 ddd       |j                         S # 1 sw Y   |j                         S xY w)u#   파일의 sha256 hex digest 반환.rbc                 &     j                  d      S )Ni   )read)fs   r   <lambda>z_sha256_file.<locals>.<lambda>7   s    !&&- r   r   N)hashlibsha256openiterupdate	hexdigest)pathhchunkr   s      @r   _sha256_filer*   3   sj    A	4 A/5 	EHHUO	 ;;= ;;=s   &A&&A>c                H    t        j                  |       j                         S N)r!   r"   r&   )datas    r   _sha256_bytesr.   <   s    >>$))++r   c                    t        j                  | dd      }t        j                  |j	                  d            j                         S )NTF	sort_keysensure_asciiutf-8)jsondumpsr!   r"   encoder&   )record
serializeds     r   _evidence_hash_strr9   @   s7    FdGJ>>*++G45??AAr   c                    | j                         D ci c]  \  }}|dk7  s|| }}}t        j                  |dd      }t        j                  |j                  d            j                         S c c}}w )u6   state dict에서 _checksum 제외한 내용의 sha256.	_checksumTFr0   r3   )itemsr4   r5   r!   r"   r6   r&   )
state_dictkvcleanr8   s        r   _compute_checksumrA   E   sh    (..0EdaA4DQTEEEETFJ>>*++G45??AA Fs
   A4A4c                   | j                   j                  dd       t        j                  |dd      }t	        j
                  | j                   d      \  }}	 t        j                  |dd	
      5 }|j                  |       ddd       t        j                  |d       t        j                  ||        y# 1 sw Y   6xY w# t        $ r' 	 t        j                  |        # t        $ r Y  w xY ww xY w)u   tempfile + os.replace 방식 atomic write.

    Gemini 리뷰 medium: tempfile.mkstemp 기본 권한이 0o600이라 os.replace 후
    state 파일 권한이 제한됨. 0o644로 명시 chmod 후 replace.
    Tparentsexist_okF   )r2   indentz.tmp)dirsuffixwr3   encodingN  )parentmkdirr4   r5   tempfilemkstempr   fdopenwritechmodreplace	ExceptionunlinkOSError)r'   r-   contentfdtmpr   s         r   _atomic_write_jsonr\   L   s     	KKdT2jjE!<G4;;v>GB
YYr31 	QGGG	
e


3	 	  	IIcN 	  		sH   C 3B:4C :C?C 	C6C&%C6&	C2/C61C22C6r   c                  t        |      }|t        z  |  dz  }g }dddddd|d}|j                         s|j                  d|        |S d|d<   	 |j	                  d	      }d|d
<   	 t        j                  |      }d|d<   |j                  d      xs |j                  d      |d<   |j                  d      }	|	s|j                  d       |S d|d<   t        |      }
|
|	k7  r |j                  d|	dd  d|
dd  d       |S d|d<   |S # t
        $ r }|j                  d|        |cY d}~S d}~ww xY w# t        j                  $ r }|j                  d|        |cY d}~S d}~ww xY w)uL  .tasks/state/{task_id}.json 검사.

    Returns
    -------
    dict
        {
            "exists": bool,
            "readable": bool,
            "json_valid": bool,
            "checksum_present": bool,
            "checksum_match": bool,
            "current_state": str | None,
            "issues": [str, ...]
        }
    .jsonFN)existsreadable
json_validchecksum_presentchecksum_matchcurrent_stateissues   state 파일 없음: Tr_   r3   rK   r`      state 파일 읽기 실패: ra   u!   state 파일 JSON 파싱 실패: statestatusrd   r;   u   _checksum 필드 없음rb   zchecksum mismatch: stored=   z..., computed=z...rc   )r   STATE_DIR_RELr_   append	read_textrV   r4   loadsJSONDecodeErrorr   rA   )task_idr   	work_root
state_pathre   resultrY   excrh   stored_checksumcomputeds              r   inspect_staterw   g   s      	*I]*y->>JF!F -j\:;F8&&&8!z


7##| $ii0GEIIh4GF? ii,O/0!%F 'H?"(")=(>nXVYWY]O[^_	
 M $( M=  4SE:;  9#?@s<   D +D: 	D7D2,D72D7:E-E("E-(E-c                  t        |      }|t        z  |  dz  }t               }|j                         sddd|d| dS 	 |j	                         }t        |      }|t        z  }|j                  dd	       ||  d
| dz  }		 |	j                  |       dt        |	      ||dS # t
        $ r}ddd|d| dcY d}~S d}~ww xY w# t
        $ r}dt        |	      ||d| dcY d}~S d}~ww xY w)u   .tasks/state/{task_id}.json의 sha256 + 원본 백업 보존.

    백업 위치: .tasks/state/.backups/{task_id}-{timestamp}.json

    Returns
    -------
    dict
        {"ok": bool, "backup_path": str, "sha256": str, "ts": str}
    r^   F rf   )okbackup_pathr"   tsreasonrg   NTrC   -u   백업 파일 쓰기 실패: )rz   r{   r"   r|   )r   rk   r   r_   
read_bytesrV   r.   BACKUP_DIR_RELrO   write_bytesstr)
rp   r   rq   rr   r|   rY   rt   r"   
backup_dirr{   s
             r   backup_state_filer      sC     	*I]*y->>J		B-j\:
 	
	
'') 7#F^+JTD1'!B4u55K	
( ;'	 3  
4SE:
 	

   
{+5cU;
 	

s<   B% C %	C.
B>8C>C	C-C("C-(C-)	new_stater   c               &   t        |      }|t        z  |  dz  }t        |      }	|	j                         s||	z  }	|	j	                         sdd|	 ddddS t        | |      }
|
d   sdd	|
j                  d
d       ddddS |
d   }|
d   }|t        z  t         |  z  }	 |j                  j                  dd       |j                  t        j                  | ||t               ddd      d       d}d}d}	 |dk(  rc|j	                         sd}nt        j                  |j!                  d            }t#        |      |d<   t%        ||       t'        |      }d}d}n|dk(  r|t(        z  }t+        |j-                  |  d      d      }|D cg c]  }t/        |      |k7  s| }}|sd}n}|d   }|j1                         }|j3                  |       t'        |      }d}d |j4                   }n:|d!k(  r/|d"}n0t#        |      |d<   t%        ||       t'        |      }d}d#}nd$| d%}|rdnd'}|r7	 |j                  t        j                  | ||t               d(dd      d       	 t7        | |||t/        |	      ||||||)      }||t/        |      |dS # t        $ r}dd| dd|dcY d}~S d}~ww xY wc c}w # t        $ r}d&| }d}Y d}~d}~ww xY w# t        $ r Y ~w xY w# t        $ r}dd*| d+d|dcY d}~S d}~ww xY w),u  state 파일 수리 (chairman approval evidence 필수).

    fail-closed:
    - evidence_path 파일 부재 → reject
    - sha256 백업 실패 → reject
    - audit 기록 실패 → reject
    - repair 후 .verify-pending 마커 생성 (verify_consistency 호출 강제)

    Parameters
    ----------
    approved_by_chairman:
        chairman 승인자 식별자.
    evidence_path:
        approval evidence 파일 경로 (반드시 실제 파일 존재).
    actor:
        요청자.
    repair_action:
        "recompute_checksum" | "rollback_to_backup" | "manual_fixup"
    new_state:
        manual_fixup 시 새 state dict.

    Returns
    -------
    dict
        {"ok": bool, "reason": str, "audit_path": str, "backup_path": str}
    r^   Fu   evidence_path 파일 없음: u     — repair 거부 (fail-closed)ry   )rz   r}   
audit_pathr{   r   rz   u   sha256 백업 실패: r}   r"   r{   TrC   z
pre-repair)rp   repair_actionactorr|   stager2   r3   rK   u,   verify-pending 마커 사전 생성 실패: u     — repair 중단 (fail-closed)Nrecompute_checksumu/   state 파일 없음 (recompute_checksum 불가)r;   u   checksum 재계산 완료rollback_to_backupz-*.json)reverseu%   복원할 이전 backup 파일 없음r   u   backup 복원 완료: manual_fixupu    manual_fixup에 new_state 필수u.   manual_fixup 완료 (chairman approval 적용)u   알 수 없는 repair_action: ''u   repair 실행 중 예외: rejectedzpost-repair)rp   r   r   approved_by_chairmanevidence_pathinput_state_sha256output_state_sha256r{   rs   r}   r   u   audit 기록 실패: u    — fail-closed)r   rk   r   is_absoluter_   r   r   VERIFY_PENDING_PREFIXrN   rO   
write_textr4   r5   r   rV   rn   rm   rA   r\   r*   r   sortedglobr   r   r   namerecord_repair_audit)rp   r   r   r   r   r   r   rq   rr   ev_pathbackup_resultinput_sha256r{   marker_pathrt   output_sha256	repair_okrepair_reasonrh   r   backupsbprev_backupslatest_backuprY   
result_strr   s                              r   repair_stater      s   H  	*I]*y->>J =!G g%>>5gY>^_	
 	
 &gCM.}/@/@2/N.OOop	
 	
 !*L.K m+1F0Gy.QQK
   =JJ&%2""*) #	  	 	
, MIM-00$$& Q

:#7#7#7#IJ%6u%=k"":u5 ,Z 8 	 ;22"^3JZ__y-@A4PG'.H!#a&K2GAHLH G ,Q'224&&w/ ,Z 8 	"89K9K8L Mn,  B *;9)E	+&":y9 ,Z 8 	 P=m_ANM #
J 	""

#*)6!&&j!. "'	 ! # $
('!5g,+ -# 

. *o"	 Y  
DSEIij&	
 	

> I2  4SE:	0  		&  
 -cU2BC&	
 	

s   "AJ  <BK K)K-BK :6K# 1K2  	J>)
J93J>9J>K 	K KK #	K/.K/2	L;
LLLc                  t        |      }|t        z  t         |  z  }i }t        | |      }||d<   |d   sdd|dS |d   sdd|dS |d	   sdd
|dS |d   sdd|d    |dS d|d<   |j	                         r	 |j                          d|d<   n
d|d<   d|d<   dd|dS # t        $ r}d|d<   dd| |dcY d}~S d}~ww xY w)u  repair 후 호출되어야 하는 후속 검증.

    체크:
    - state file checksum 일치
    - .verify-pending 마커 제거 (consistency 통과 시)
    - FAIL 시 마커 유지

    Returns
    -------
    dict
        {"ok": bool, "reason": str, "checks": {...}}
    r   state_inspectr_   Fu/   state 파일 없음 — verify-consistency FAIL)rz   r}   checksra   u;   state 파일 JSON 파싱 실패 — verify-consistency FAILrb   u3   _checksum 필드 없음 — verify-consistency FAILrc   u7   checksum mismatch — verify-consistency FAIL. issues: re   Tchecksum_okverify_pending_removedu%   verify-pending 마커 제거 실패: Nu*   마커가 없어 제거 불필요 (정상)verify_pending_noteuF   verify-consistency PASS: checksum 일치, verify-pending 마커 제거)r   rk   r   rw   r_   rW   rV   )rp   r   rq   r   r   
inspectionrt   s          r   verify_consistencyr     s`     	*Im+1F0Gy.QQKF w)<J(F?hG
 	
 l#S
 	
 ()K
 	
 &'OPZ[cPdOef
 	
 !F=		 /3F+, ,1'((T$% Z   	/4F+,A#G  	s   <B" "	C+B>8C>Cc                <   t               }| |||||||||	|d}t        |      }i |d|i}t        |
      }|t        z  }|j                  j                  dd       t        j                  |d      dz   }t        j                  t        |      t        j                  t        j                  z  t        j                  z  d      }	 t        j                  ||j                  d	             t        j                   |       |S # t        j                   |       w xY w)
u#  checksum repair audit 기록.

    memory/orchestration-audit/checksum-repair.jsonl 에 line append.
    필수 필드: task_id, actor, repair_action, approved_by_chairman, evidence_path,
    input_state_sha256, output_state_sha256, backup_path, result, reason, timestamp, evidence_hash
    )rp   r   r   r   r   r   r   r{   rs   r}   	timestampevidence_hashTrC   Fr   
rM   r3   )r   r9   r   AUDIT_JSONL_RELrN   rO   r4   r5   r   r#   r   O_WRONLYO_APPENDO_CREATrS   r6   close)rp   r   r   r   r   r   r   r{   rs   r}   r   r   base_recordev_hashr7   rq   targetlinerZ   s                      r   r   r     s    ( 
I& 4&02"K !-G66_g6F	*I(F
MMt4::f51D8D	VbkkBKK7"**De	LB
T[[)*
M 	s   %D D)returnr   r,   )r   Optional[Path]r   r   )r'   r   r   r   )r-   bytesr   r   )r7   dictr   r   )r=   r   r   r   )r'   r   r-   r   r   None)rp   r   r   r   r   r   )rp   r   r   r   r   r   r   r   r   r   r   zOptional[dict]r   r   r   r   )rp   r   r   r   r   r   r   r   r   r   r   r   r   r   r{   r   rs   r   r}   r   r   r   r   r   )__doc__
__future__r   r!   r4   r   rP   r   r   pathlibr   typingr   rk   r   r   r   r   r   r   r*   r.   r9   rA   r\   rw   r   r   r   r   r   r   r   <module>r      s  	 #   	  '   ^$+*  IJEA
V,B
B6 @D DN DH 7B !% $EE E 	E
 E E E E 
EP EI Fj !%00 0 	0
 0 0 0 0 0 0 0 0 
0r   