
    j"y                    (   U 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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  ee      j-                         j.                  j.                  Z ee      ej4                  vr"ej4                  j7                  d ee             ddlmZmZmZm Z m!Z!m"Z"m#Z#m$Z$m%Z%m&Z&m'Z'm(Z( ddl)m*Z*m+Z+m,Z,m-Z-m.Z. g d	Z/d
Z0dZ1de2d<   dZ3dZ4de2d<   e$e%e"fZ5de2d<   de3 dZ6d8dZ7d9dZ8d:dZ9 G d d      Z:ed   Z; ed       G d d             Z< ejz                  d      Z> ejz                  d      Z?d8d Z@d8d!ZAd9d"ZBd;d#ZCd<d$ZD	 	 	 	 	 	 	 	 	 	 	 	 d=d%ZE	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d>d&ZFd?d'ZG	 d@d(de0d)	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dAd*ZH	 	 	 	 	 	 	 	 dBd+ZId,ZJdCd-ZKdDd.ZLdd/	 	 	 	 	 	 	 	 	 dEd0ZMddddd1	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dFd2ZNdGd3ZOdHd4ZP	 d@eOd5	 	 	 	 	 dId6ZQeRd7k(  r ej                   eQ              yy)JuV  scripts/safe_cron_dispatch.py — task-2526 §3 wrapper 본체.

회장 §본질 (2026-05-10 PR #74 재위임 오류):
  독립 bot task cron에 ``--session`` 옵션이 잘못 붙어 봇이 아니라 **아누 자기 세션이
  resume된 사고**가 발생함 (cron 5C9995CCB → PID 448820 = 아누 자기 세션 resume → kill).
  동일 사고가 다시 발생하면 안 된다.

  이 wrapper는 cokacdir cron 명령을 직접 조립하는 모든 호출자가 **반드시 거쳐야 하는**
  preflight gate다. 잘못된 조합 (independent_task + ``--session``, merge_task + ``--session``,
  bot_task + bot_key 누락, owner_pat fallback path) 을 **실행 전에** 차단한다.

회장 §명시 5 차단 조건:
  1. ``independent_task`` + ``--session`` 존재 → BLOCK
  2. ``merge_task`` + ``--session`` 존재 → BLOCK
  3. ``bot_task`` 인데 bot_key 없이 merge/PR 작업 → BLOCK
  4. target bot과 session owner 불일치 → BLOCK
  5. owner_pat fallback 가능성 (gh pr merge 명령에 GH_TOKEN=$BOT_GITHUB_TOKEN 미설정) → BLOCK

회장 §명시 1 ALLOW 예외:
  - ``followup_readonly`` + ``--session`` → 같은 chat 아누 session resume 허용 (오직 이 케이스만)

회장 §금지:
  - ❌ dispatch.py 본체 수정
  - ❌ cokacdir CLI 본체 수정
  - ❌ 직접 cokacdir cron 명령 조립 (외부 호출자도 본 wrapper만 사용)
  - ❌ admin override / owner_pat fallback 정당화
  - ❌ Critical 7종 외 회장 보고 (CRON_TARGETING_GUARD_BLOCKED는 Critical enum이 아니라
    audit JSONL용 짧은 운영 신호 — 별도 chairman 메시지는 호출자 책임)
  - ❌ token / raw key / session secret 원문 기록

API:
  result = safe_cron_dispatch(
      prompt="...",
      schedule="0 9 * * *",
      chat="6937032012",
      target_bot_key="0b94683120a691cf",
      task_kind="bot_task",
      session_id=None,
  )

  if result.status == DispatchStatus.BLOCKED:
      # blocked_reason / audit_record 활용
      ...
  else:
      # result.command_argv 를 caller가 subprocess로 실행하거나
      # safe_cron_dispatch_run() 헬퍼로 실행 (선택)
      ...
    )annotationsN)asdict	dataclass)Path)CallableListLiteralOptionalSequenceTuple)ACTOR_ANU_SESSIONACTOR_BOT_SESSIONALLOWED_TASK_KINDSBlockedReasonCronTargetingAuditRecordTASK_KIND_BOTTASK_KIND_FOLLOWUP_ROTASK_KIND_INDEPENDENTTASK_KIND_MERGEappend_audit_jsonlbuild_audit_recordhash_bot_key)DEFAULT_TIMERS_PATHextract_task_id_from_promptextract_team_id_from_task_mdextract_team_id_from_textupsert_cron_dispatch)DEFAULT_COKACDIR_CLIDispatchStatusCronDispatchResultsafe_cron_dispatchformat_chairman_block_noticeAUTO_FINALIZE_FOOTER_MARKERAUTO_FINALIZE_OPT_OUT_TOKENSauto_inject_finalize_footer"should_auto_inject_finalize_footeris_finalize_opt_out&parse_schedule_id_from_cokacdir_stdoutrun_cron_with_timers_upsertz/usr/local/bin/cokacdir)
6937032012Tuple[str, ...]_CHAIR_CHATSz[AUTO_FINALIZE_CHAIN_FOOTER_v1])zfinalize_policy: no_przfinalize_policy:no_przread_only: truezread_only:truezanalysis_only: truezanalysis_only:truezreport_only: truezreport_only:truer$   _AUTO_FINALIZE_TASK_KINDSz

u  
## 자동 finalize chain (회장 §명시 — code task default, opt-out 없으면 자동 진입)
본질 작업과 자체 검증이 PASS하면 별도 지시 없이 다음 12단계를 그대로 수행한다.
read_only / analysis_only / report_only / finalize_policy:no_pr 명시가 없으면 이 chain은 default다.

1. commit (관련 파일만 명시 add, 의미 있는 메시지)
2. push (task 전용 브랜치 — main/master push 금지)
3. PR 생성 (gh pr create — 본문에 expected_files / 회귀 / 영향 범위 포함)
4. CI 통과 확인 (gh pr checks — FAILURE 시 재시도/수정 후 재push)
5. Gemini 리뷰 통과 (high/critical 응답 → fix 후 push)
6. auto_gemini_triage 응답 (gemini high/critical은 코드로 수용)
7. bot identity merge (GH_TOKEN=$BOT_GITHUB_TOKEN gh pr merge — owner_pat fallback 금지)
8. post_merge_smoke (utils/post_merge_smoke_runner — FAIL 시 Critical 7종 ESCALATE)
9. reconcile (utils/lifecycle_reconciliation_manager --reconcile --apply)
10. .done.acked + .merge-done 마커 검증 (manual forgery 금지 — evidence 기반만)
11. timer end + audit JSONL 1줄 추가
12. 한 줄 결과 보고 (성공: <task> AUTO_FINALIZE_PASS — mergeCommit <sha>, mergedBy=app/jeon-jonghyuk-taskctl-bot)
c                Z    | sy| j                         t        fdt        D              S )u'  opt-out 토큰이 prompt에 포함되어 있는지 검사.

    회장 §명시 4 토큰 (대소문자 무시):
      - finalize_policy: no_pr
      - read_only: true
      - analysis_only: true
      - report_only: true

    공백 변형 (`field: value` / `field:value`)도 모두 인식.
    Fc              3  B   K   | ]  }|j                         v   y wN)lower).0toklowereds     K/home/jay/workspace/.worktrees/task-2644-dev1/scripts/safe_cron_dispatch.py	<genexpr>z&is_finalize_opt_out.<locals>.<genexpr>   s     N#syy{g%Ns   )r1   anyr$   )promptr4   s    @r5   r'   r'      s(     llnGN1MNNN    c                H    | t         vryt        |xs dv ryt        |      ryy)u   자동 finalize footer 삽입 여부 결정.

    True 조건 (모두 만족):
      - task_kind ∈ {independent_task, merge_task, bot_task}
      - prompt에 opt-out 토큰 부재
      - prompt에 footer marker 부재 (중복 방지)
    F T)r-   r#   r'   	task_kindr8   s     r5   r&   r&      s-     11"v|46"r9   c                N    t        | |      s|S |j                          t         S )u   조건 충족 시 prompt 끝에 12단계 footer 추가, 그 외 그대로 반환.

    멱등적: 동일 prompt를 두 번 inject해도 marker 검사로 중복 삽입 안 됨.
    )r&   rstrip_AUTO_FINALIZE_FOOTER_BODYr<   s     r5   r%   r%      s+    
 .i@mmo9:;;r9   c                      e Zd ZdZdZy)r   ALLOWEDBLOCKEDN)__name__
__module____qualname__rB   rC    r9   r5   r   r      s    GGr9   r   )independent_task
merge_taskbot_taskfollowup_readonlyhuman_responseT)frozenc                  D    e Zd ZU dZded<   ded<   ded<   ded	<   ded
<   y)r    u?  preflight 결과. caller는 status 검사 후 분기.

    BLOCKED 상태에서는 command_argv가 비어있고 blocked_reason이 채워진다.
    ALLOWED 상태에서는 command_argv (cokacdir cron CLI 인자)가 채워진다.
    audit_record는 항상 채워진다 (성공/차단 모두 audit JSONL에 기록).
    strstatusOptional[str]blocked_reasonr   audit_recordr+   command_argvchairman_noticeN)rD   rE   rF   __doc____annotations__rG   r9   r5   r    r       s&     K!!**!!""r9   r    z\bgh\s+pr\s+merge\bz,\bGH_TOKEN\s*=\s*\$?\{?\s*BOT_GITHUB_TOKEN\bc                >    t        t        j                  |             S r0   )bool_GH_PR_MERGE_REsearchr8   s    r5   _prompt_uses_gh_pr_merger]     s    &&v.//r9   c                >    t        t        j                  |             S r0   )rY   _BOT_GH_TOKEN_INJECTION_REr[   r\   s    r5   _prompt_injects_bot_gh_tokenr`     s    *11&9::r9   c                P    | t         t        fvryt        |      syt        |       S )u   ``gh pr merge`` 등장 + ``GH_TOKEN=$BOT_GITHUB_TOKEN`` 미설정 → True.

    merge_task / bot_task에만 적용. independent / followup은 무관.
    F)r   r   r]   r`   r<   s     r5   _looks_like_owner_pat_fallbackrb     s-    
 -88#F++F333r9   c                .    | t         dfv rt        S t        S )u   task_kind로 expected actor 추론.

    - ``followup_readonly`` → ``anu_session`` (예외 케이스, 같은 chat resume 허용)
    - ``human_response`` → ``anu_session``
    - 그 외 (independent / merge / bot) → ``bot_session``
    rL   )r   r   r   )r=   s    r5   _expected_actorrd   #  s     *,<==  r9   c                    | sy|t         v S )uc   현재는 chat이 회장 chat이고 session_id가 채워졌으면 아누 self-session으로 추정.F)r,   )
session_idchats     r5   _session_owner_is_anurh   /  s    <r9   c                   |t         vrt        dt          d|      t        |      }d}|t        k(  r|rt        j
                  dfS |t        k(  r|rt        j                  dfS |t        k(  r#|r!t        ||      rd}nt        j                  dfS |t        t        fv r|st        j                  dfS t        ||       rt        j                  dfS |t        t        t        fv r |rt        ||      rt        j                  dfS d|fS )uo   preflight 검사 — (blocked_reason, session_id_allowed) 반환.

    blocked_reason가 None이면 ALLOW.
    task_kind must be one of , got FTN)r   
ValueErrorrY   r   r   INDEPENDENT_TASK_WITH_SESSIONr   MERGE_TASK_WITH_SESSIONr   rh   TARGET_SESSION_OWNER_MISMATCHr   BOT_KEY_MISSING_FOR_BOT_TASKrb   OWNER_PAT_FALLBACK_DETECTED)r8   rg   target_bot_keyr=   rf   has_sessionsession_alloweds          r5   _run_preflightru   :  s     **'(:';6)O
 	
 z"KO ))k::EAA O#44e;; ))k T2"O >>EE ]O44 ==uDD &i888%?? *M?KK0TB >>EE  r9   c                    |d| d|d|g}|r|j                  d|g       |r|r|j                  d|g       |r|j                  d       t        |      S )u~   ALLOWED 시 cokacdir cron 인자 리스트 조립.

    BLOCKED인 경우 호출자가 이 함수를 부르지 않는다.
    z--cron--at--chat--key	--session--once)extendappendtuple)	r8   schedulerg   rr   rf   rt   clionceargvs	            r5   _build_command_argvr   t  s[     Hffh$ODWn-.o[*-.H;r9   c                2    dj                  d | D              S )uK   argv를 shell-quote된 단일 문자열로 결합 (sanitize 직전 형태). c              3  F   K   | ]  }t        j                  |        y wr0   )shlexquote)r2   as     r5   r6   z3_build_command_preview_for_audit.<locals>.<genexpr>  s     1qEKKN1s   !)join)r   s    r5    _build_command_preview_for_auditr     s    881D111r9   F)r   
audit_pathcli_pathc                  t        | t              r| st        d      t        |t              r|st        d      t        |t              r|st        d      |t        vrt        dt         d|      t	        | ||||      \  }	}
|	t        ||       } |	t        | |||||
||      }nt        | ||||d	||      }t        |      }t        |      }t        |      }|rd
| d|r|dd nd dnd
| d}t        d||||
||d||	
      }t        ||       |	2t        t        j                  |	|t               t!        ||	|            S t        t        j"                  d||d      S )u[  preflight → BLOCK 또는 ALLOW.

    이 함수는 외부 부수효과 없음:
      - subprocess 실행 X (caller가 결정)
      - chairman 메시지 송신 X (caller 책임)
      - audit JSONL append O (성공/차단 모두 1건 기록)

    raw bot_key / raw session_id는 audit에 절대 기록되지 않는다 (해시/플래그만).
    zprompt must be a non-empty strz schedule must be a non-empty strzchat must be a non-empty strrj   rk   )r8   rg   rr   r=   rf   N)r8   r   rg   rr   rf   rt   r   r   Tzchat=z/key=<hash:   r;   u   …>z/no-key)
cron_id
target_botbot_keyrf   session_id_allowedr=   actor_expectedactor_actual_if_knowncommand_previewrR   )r   r=   rR   r   )rP   rR   rS   rT   rU   )
isinstancerO   rl   r   ru   r%   r   r   rd   r   r   r   r    r   rC   r~   r"   rB   )r8   r   rg   rr   r=   rf   r   r   r   rR   rt   r   r   r   	_key_hashr   records                    r5   r!   r!     s   * fc"&9::h$H;<<dC 788**'(:';6)O
 	
 '5%'#NO ,Y?")!+	
 #)! 	
 7t<O$Y/N^,I  v[)2A DDITF'"   *%"'%F v*5!!!)))8#-%

 
	
 %% r9   c                    d|  d| d| S )uR   회장 chat 짧은 보고용 텍스트 (Critical 7종 X — 별도 운영 신호).u+   CRON_TARGETING_GUARD_BLOCKED — task_kind=z reason=z target=rG   r   s      r5   r"   r"     s$    Kx'7x
|	Mr9   okc                   	 t        j                  |       }t	        |t
              sy|j                  d      t        k7  ry|j                  d      }t	        |t              r|sy|S # t         j                  t        f$ r Y yw xY w)uU   JSON 후보 문자열에서 ``status=='ok'`` 인 schedule id 추출. 실패 시 None.NrP   id)	jsonloadsJSONDecodeErrorrl   r   dictget_COKACDIR_OK_STATUSrO   )	candidatepayloadschedule_ids      r5   _try_parse_cokacdir_payloadr   1  s{    **Y' gt${{8 33++d#Kk3'{   *- s   A' 'BBc                   | sy| j                         }|sy|j                         D ]O  }|j                         }|j                  d      r|j                  d      s6d|vrd|vr?t	        |      }|sM|c S  |j                  d      }|j                  d      }|dk7  r!|dk7  r||kD  r|||dz    }t	        |      }|r|S y)ut  cokacdir cron 등록 stdout 에서 ``id`` 필드를 파싱한다 (회귀 8 견고화).

    파싱 실패 / status != ``ok`` / id 누락 → None 반환 (caller 가 hook skip 결정).

    파싱 전략 (Gemini PR #82 review L577 medium 수용 — 견고화):
      1. 줄 단위 스캔 — 각 줄을 trim 후 ``{`` 시작 / ``}`` 종료 면 ``json.loads`` 시도
      2. 줄 합계 시도 (single-line) — ``"status": "ok"`` substring 포함 줄들만 후보
      3. 마지막 fallback — 첫 ``{`` ~ 마지막 ``}`` 한 덩어리 시도
    어떤 단계에서든 ``status='ok' + id`` 가 추출되면 즉시 반환.
    N{}z"status"z'status'   )strip
splitlines
startswithendswithr   findrfind)stdouttextlineline_sresultstartendr   s           r5   r(   r(   A  s     <<>D !   %V__S-Av%*F*B,V4M IIcNE
**S/C{sbyS5[q)	,Y7Mr9   )	tasks_dirc                    | r| S ||nt         dz  dz  }|r|| dz  }t        |      }|dk7  r|S |rt        |      }|dk7  r|S y)u  team_id 결정 우선순위.

    1. ``--team`` 인자 (caller 명시) — 비어있지 않으면 즉시 채택
    2. task md 파일 (memory/tasks/<task_id>.md) 에서 ``devN-team`` 추출
    3. cron prompt 본문에서 ``devN-team`` 추출
    4. ``"unknown-team"``
    memorytasksz.mdzunknown-team)_WORKTREE_ROOTr   r   )explicit_teamtask_idr8   r   base_dircandidate_mdteam_from_mdteam_from_prompts           r5   _resolve_team_idr   i  sn     %1y8QT[8[HWIS/13LA>)4V<~-##r9   )r   timers_pathr   runnerc           	        ||nt         } ||       \  }}	|dk7  r||	dfS t        |	      }
|
s$t        j                  j	                  d       ||	dfS t        |      }|s$t        j                  j	                  d       ||	dfS t        ||||      }	 t        |||
|||xs t              }||	|fS # t        t        t        j                  f$ r2}t        j                  j	                  d| d       ||	dfcY d}~S d}~ww xY w)	uz  ALLOWED command argv 를 실행하고, 성공 시 timers entry 를 upsert.

    Returns:
        ``(returncode, captured_stdout, upserted_entry_or_None)``

    동작:
      - rc != 0 → hook skip (atomic)
      - schedule_id 파싱 실패 → hook skip + stderr 1줄 운영 신호
      - upsert 자체 실패 → rc 보존 + stderr 1줄 (caller dispatch 결과 영향 X)
    Nr   uT   WARN: timers upsert skipped — schedule_id parse failed (cron rc=0 but no JSON id)
u<   WARN: timers upsert skipped — task_id not found in prompt
)r   r   r8   r   )r   team_idr   cron_promptchat_idr   zWARN: timers upsert failed: 
)_capture_runnerr(   sysstderrwriter   r   r   r   OSErrorrl   r   r   )r   r8   rg   r   r   r   r   actual_runnerrcr   r   r   r   entryexcs                  r5   r)   r)     s(   ( %0FoMt$JB	Qw648@K

c	
 64)&1G

K	
 64#	G $##:':
 vu	 Z!5!56  

7uB?@64 s   B0 0D 'C;5D ;D c                   t        j                  t        |       t         j                  ddd      }g }|j                  y|j                  D ]P  }t
        j                  j                  |       t
        j                  j                          |j                  |       R |j                  j                          |j                         }|dj                  |      fS )u8  기본 subprocess runner — stdout 을 **실시간 스트리밍** 하면서 동시에 캡처.

    Gemini PR #82 review L694 medium 수용:
      ``capture_output=True`` 는 모든 stdout 을 끝까지 버퍼링하므로 사용자가 cron 발사
      진행 상황을 실시간으로 못 본다 (UX 퇴보). 본 함수는 ``Popen`` + 줄 단위 read 로
      tee 패턴을 구현해 사용자에게는 즉시 보여주면서 schedule_id 파싱용 buffer 도 채운다.

    stderr 는 그대로 부모 stderr 로 inherit (별도 캡처 X — 디버깅 정합).
    NTr   )r   r   r   bufsizer;   )
subprocessPopenlistPIPEr   r   r   flushr}   closewaitr   )r   proccapturedr   r   s        r5   r   r     s     T
D H{{KK 	"DJJT"JJOOD!	" 		Brwwx   r9   c                 V   t        j                  dd      } | j                  dd       | j                  ddd	       | j                  d
d       | j                  ddd        | j                  ddt        t                     | j                  ddd        | j                  dd       | j                  ddd       | j                  dt
               | j                  dd        | j                  ddd d       | j                  dd d !       | j                  d"dd#       | S )$Nr!   z-task-2526 cron --session safety guard wrapper)progdescriptionz--promptT)requiredrw   r   )destr   rx   ry   rr   )r   defaultz--task-kind)r   choicesrz   rf   r{   
store_true)actionz--applyu<   ALLOWED 시 cokacdir 실행 (기본 dry-run: argv만 출력))r   helpz--cli)r   z--audit-pathz--teamr   uK   (task-2533) timers upsert team_id 명시 (생략 시 task md 자동 추출))r   r   r   z--timers-pathu@   (task-2533) timers JSON 경로 (기본: memory/task-timers.json))r   r   z--no-timers-upsertu;   (task-2533) cron 발사 후 timers upsert hook 비활성화)argparseArgumentParseradd_argumentr   r   r   )parsers    r5   _build_arg_parserr     s^   $$!CF 
T2
Z$?
40
&6E
'(  
 ,E
6
K  
 )=>
5
Z	   O  
 J  
 Mr9   )r   c                  t               }|j                  |       }t        |j                  |j                  |j
                  |j                  |j                  |j                  |j                  |j                  rt        |j                        nd |j                  	      }t        t        j                  |j                   |j"                  t%        |j&                        |j(                  i t+        |j,                        dd             |j                   t.        j0                  k(  ry|j2                  r|j4                  r$ ||j&                        \  }}t7        |xs d      S t9        |j&                  |j                  |j
                  |j:                  |j<                  rt        |j<                        nd |      \  }}}t7        |xs d      S y)N)	r8   r   rg   rr   r=   rf   r   r   r   )rP   rR   rT   rU   auditF)ensure_asciir   r   )r   r8   rg   r   r   r   )r   
parse_argsr!   r8   r   rg   rr   r=   rf   r   r   r   r   printr   dumpsrP   rR   r   rT   rU   r   rS   r   rC   applyno_timers_upsertintr)   r   r   )r   r   r   argsr   r   _s          r5   	_cli_mainr     sw   
  FT"D{{YY**..??YY,0OO4(
F 
$**-- //V001!11
V(()
   }}...zz  6../EBrwQ<.$$;;,,262B2BT--.
Aq 27|r9   __main__)r8   rO   returnrY   )r=   rO   r8   rO   r   rY   )r=   rO   r8   rO   r   rO   )r=   rO   r   rO   )rf   rQ   rg   rO   r   rY   )r8   rO   rg   rO   rr   rQ   r=   rO   rf   rQ   r   zTuple[Optional[str], bool])r8   rO   r   rO   rg   rO   rr   rQ   rf   rQ   rt   rY   r   rO   r   rY   r   r+   )r   r+   r   rO   r0   )r8   rO   r   rO   rg   rO   rr   rQ   r=   TaskKindLiteralrf   rQ   r   rY   r   Optional[Path]r   rO   r   r    )r=   rO   rR   rO   r   rO   r   rO   )r   rO   r   rQ   )r   rO   r   rQ   )
r   rQ   r   rQ   r8   rO   r   r   r   rO   )r   Sequence[str]r8   rO   rg   rO   r   rQ   r   r   r   r   r   z4Optional[Callable[[Sequence[str]], Tuple[int, str]]]r   zTuple[int, str, Optional[dict]])r   r  r   zTuple[int, str])r   zargparse.ArgumentParser)r   zOptional[Sequence[str]]r   z*Callable[[Sequence[str]], Tuple[int, str]]r   r   )TrV   
__future__r   r   r   rer   r   r   dataclassesr   r   pathlibr   typingr   r   r	   r
   r   r   __file__resolveparentr   rO   pathinsertutils.cron_targeting_auditr   r   r   r   r   r   r   r   r   r   r   r   utils.cron_timers_upsertr   r   r   r   r   __all__r   r,   rW   r#   r$   r-   r@   r'   r&   r%   r   r   r    compilerZ   r_   r]   r`   rb   rd   rh   ru   r   r   r!   r"   r   r   r(   r   r)   r   r   r   rD   exitrG   r9   r5   <module>r     s  /` #   	   
 )  E E h'')0077~chh&HHOOAs>*+    , 1  !0o /, @ 	1 o 	 . ?  "# $DD *O""< 
  $# # #& "**34'RZZ3 
0;	4 	 3!3! 3! "	3!
 3! 3!  3!t  	
 "   
  22 !%w !%(www w "	w
 w w w w w wt

 
 	

 	
<   %Z !%   	
  	H $("& $CG<
< < 	<
 !<  < < A< %<~!@(X %)/ :I/
!/ 7/ 		/d zCHHY[ r9   