
    @j.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	Z	ddl
Z
ddlmZmZmZmZmZ dZdZdZdZd	Zd
ZdZdZdZdZdZdZdZdZdZ e h d      Z! e h d      Z"dZ#dZ$dZ%dZ&dZ'dZ(dZ)dZ*dZ+dZ,d Z-d!Z.g d"Z/d#Z0d$d%gZ1g d&Z2d'Z3d?d(Z4d@d)Z5dAd*Z6dBd+Z7dCd,Z8dDd-Z9dEd.Z:dFd/Z;dGd0Z<dHd1Z=dId2Z>dJd3Z?	 	 	 	 	 	 	 	 dKd4Z@dLd5ZAdMd6ZBdNd7ZCdOd8ZDdPd9ZEdOd:ZFdNd;ZGdQdRd<ZHdQdSd=ZIeJd>k(  r eK eI             y)Tu  scripts/anu_control_status.py — read-only ANU 자동화 조종석 (ACTIVE=false).

task-2777. 회장이 ANU 자동화 상태를 **한눈에 read-only로 관측**하고 다음에 무엇을
승인해도 되는지 판단할 수 있는 조종석. **자동화 실행이 아니다 — 순수 관측 도구.**

설계 단일소스:
  - memory/reports/control_panel_status_command_spec_260628.md   (§2 소스 매핑 · §3 level)
  - memory/reports/capability_matrix_update_plan_260628.md       (WIRED≠ACTIVE 분리)

★ read-only 절대 불변 (코드 차원):
  flag 생성/수정 0 · state 파일 수정 0 · evidence append 0 · driver 실행 0 ·
  fixture 생성 0 · callback 발사 0 · real fire 0 · systemd 조작 0 · cron 등록 0 ·
  archive 이동 0 · key 출력 0 · key rotation 0 · git checkout/switch/reset/clean 0.
  → 이 모듈 어디에도 open(..., 'w'/'a'/'x') · os.remove/rename/replace ·
    subprocess write/mutation 명령이 없다. subprocess 는 read-only 조회만:
    `git rev-parse/branch/status(--porcelain)` · `systemctl --user is-active/is-enabled`.

★ WIRED ≠ ACTIVE: gated 축(callback real launcher 등)은 "WIRED(gated, ACTIVE=false)"
  로만 표기한다. 어떤 축도 active 처럼 보이게 쓰지 않는다.

★ cron count — raw key 금지: cron-list 호출에는 key 가 필요하나, sealed loader 기반
  read-only helper 가 없으면 raw key 를 만들지 않고 cron_count="UNKNOWN_KEY_REQUIRED"
  로 둔다. raw key literal 을 code/test/log/output 어디에도 남기지 않는다.
    )annotationsN)AnyDictListOptionalTuplezmemory/state/p0b_driver_enabledz'memory/state/p0b_event_strategy_enabledz(memory/state/p0b_callback_launch_enabledz!memory/state/p0b_activation_epochzmemory/state/p0b_killz"memory/p0b_state/driver_runs.jsonlz,memory/p0b_state/callback_launch_audit.jsonlz(memory/events/p0b_processed_ledger.jsonlz/memory/events/p0b_live_inbox/task-*.result.jsonz*memory/events/p0b_inbox/task-*.result.jsonzBmemory/reports/p0b_inbox_backlog_archive_260627/task-*.result.jsonzmemory/reportszanu-pickup.pathunavailableSYSTEMD_STATUS_UNAVAILABLE>   activefailedinactive	reloading
activatingmaintenancedeactivating>   linked-runtimemasked-runtimeenabled-runtimebadaliaslinkedmaskedstaticenableddisabledindirect	generated	transientUNKNOWN_NAIVE_DATETIMEEPOCH_PARSE_UNAVAILABLEEPOCH_IN_FUTURE
REAL_FIREDLIVE_PROCESSEDLIVE_PENDING_OWNER_PROOF&L2_DONE_L3_BLOCK_EVIDENCE_ACTIVE_FALSEL0_L2_READY_ACTIVE_FALSEINDETERMINATE_REVIEW_REQUIREDUNKNOWN_KEY_REQUIREDUNKNOWN_NO_KEYzscripts.anu_cron_count_readonly)zflag ONz
driver runz	real firezLIVE_PROCESSED executionzcallback launchzsystemd start/enablezcron registerzarchive movezpending fixture movezkey rotationmergezproduction activationzIdesign-only (D: LIVE_PROCESSED surface-only preflight) or read-only audituQ   read-only anu_control_status.py (task-2777 — MERGE_CANDIDATE, merge=ANU/회장)uP   D: LIVE_PROCESSED surface-only preflight (design-only, 별도 승인 후 관측))z'limited activation bound gate(kill/N/T)z/live_inbox evidence wiring(write_evidence=True)z&owner-proof gate(sealed ANU+freshness)z.event_strategy live path(scan_live_inbox_once)znormal callback real launcheru   limited_runtime 신호zWIRED(gated, ACTIVE=false)c                    	 t        | ddd      5 }|j                         j                         cddd       S # 1 sw Y   yxY w# t        $ r Y yw xY w)uP   파일 내용을 strip 하여 반환. 부재/오류 → None (crash 0, write 0).rutf-8replaceencodingerrorsN)openreadstripOSError)pathfs     1/home/jay/workspace/scripts/anu_control_status.py
_read_textr9      sM    $gi@ 	$A668>>#	$ 	$ 	$ s*   A 9	A AA A 	AAc                @    t         j                  j                  |       S N)osr6   exists)r6   s    r8   _existsr>      s    77>>$    c                    	 d}t        | ddd      5 }|D ]  }||v s|dz  } 	 ddd       |S # 1 sw Y   |S xY w# t        $ r Y yw xY w)uQ   파일에서 token 을 포함하는 줄 수(grep -c 대체). 부재/오류 → 0.r   r,   r-   r.   r/      N)r2   r5   )r6   tokennr7   lines        r8   _count_token_linesrE      sj    $gi@ 	A D=FA	 		  s'   = 
00	= := = 	A	A	c                ,   d}	 t        | ddd      5 }|D ]  }|j                         }|s|} 	 ddd       |y	 t        j                  |      }t        |t              r|S dS # 1 sw Y   6xY w# t        $ r Y yw xY w# t        t        f$ r Y yw xY w)uY   jsonl 마지막 비어있지 않은 줄을 dict 로 파싱. 부재/파싱실패 → None.Nr,   r-   r.   r/   )	r2   r4   r5   jsonloads
isinstancedict
ValueError	TypeError)r6   lastr7   rD   sobjs         r8   _last_json_linerP      s    D$gi@ 	A JJLD	 |jj d+s55	 	
   	" sC   A2 A&A&A2 &B $B &A/+A2 2	A>=A>BBc                ^    	 t        t        j                  |             S # t        $ r Y yw xY w)Nr   )lenglobr5   )patterns    r8   _glob_countrU      s-    499W%&& s     	,,c                    d}	 t        j                  |       D ])  \  }}}d|vr|D ]  }|j                  d      s|dz  } + 	 |S # t        $ r |cY S w xY w)uV   find memory/reports -path '*pending_fixture_quarantine*' -name '*.result.json' 대체.r   pending_fixture_quarantinez.result.jsonrA   )r<   walkendswithr5   )reports_rootrC   dirpath_	filenamesfns         r8   _quarantine_countr_      sw    	A%'WW\%: 	!GQ	+7: ;;~.FA	 H  s   7A 	A AAc                    	 t        j                  dd| g|ddddd      }|j                  dk7  ry|j
                  j                         S # t        t         j                  f$ r Y yw xY w)	uI   read-only git 명령만 실행(rev-parse/branch/status). 실패 → None.gitz-CFT   shellcheckcapture_outputtexttimeoutNr   )
subprocessrunr5   SubprocessError
returncodestdoutr4   )rootargsprocs      r8   _gitrq      sv    
~~D$&&
 !;;	 Z//0 s    A A('A(c                    | xs dj                         }|j                         syd|v sd|v sd|v sd|v sd|v ryd	|v sd
|v sd|v ryd|v sd|v sd|v sd|v ryd|v sd|v sd|v ryy)u$  raw stderr 를 짧은 분류 토큰으로 요약(원문 길게 출력 0 · key/env 유출 0).

    DBus/session bus 불가 · command missing · unit 부재 · 권한 거부 등을 고정
    토큰으로만 분류한다. stderr 원문을 status 필드/출력에 절대 넣지 않는다.
     	no_outputzfailed to connect to buszd-busdbuszno medium foundzconnection refuseddbus_unavailablezcommand not foundzno such file or directoryz	not foundcommand_missingzno such unitzcould not be foundz
not loadedz	not-foundunit_not_foundzaccess denied
permissionznot authorizedpermission_deniedsystemctl_error)lowerr4   )stderrlows     r8   _classify_systemctl_stderrr      s     <R


 C99;!S(GsNfm C'+?3+F!c!%@C%Gc!  4 ;s"kS&8#!48HC8O"r?   c                V     g d fd} |dt               } |dt              }||fS )u  systemctl --user is-active/is-enabled (read-only · 안전 처리).

    nonzero+미인식 stdout · stderr 출력 · DBus/session bus unavailable ·
    command missing · timeout · stdout empty 는 모두 'unavailable' 로 **안전 표기**
    하고 원인을 분류 요약해 warnings(SYSTEMD_STATUS_UNAVAILABLE marker)로 돌려준다.

    - stdout 첫 토큰이 정상 status allowlist 면 신뢰한다(is-active inactive=exit3,
      is-enabled disabled=exit1 처럼 nonzero 가 정상인 케이스 포함).
    - 미인식/empty/예외 → 'unavailable' (실패를 inactive/disabled 로 확정 표기 0).
    - raw stderr 원문/길게 출력 0 · traceback 0 · key/env 유출 0 · crash 0.

    반환: (is_active, is_enabled, warnings)
    c           	        	 t        j                  dd| gddddd      }|j                  xs dj                         D cg c]#  }|j                         s|j                         % }}|r|d   j                         nd}||v r|S t        |j                  xs d      }j                  t         d|  d| d|j                          t
        S # t        $ r$ j                  t         d|  d       t
        cY S t         j                  $ r$ j                  t         d|  d	       t
        cY S t        t         j                  f$ r$ j                  t         d|  d
       t
        cY S w xY wc c}w )N	systemctlz--userFTrb   rc   : z reason=command_missingz reason=timeoutz reason=subprocess_errorrs   r   z reason=z rc=)ri   rj   FileNotFoundErrorappendSYSTEMD_WARN_MARKERr
   TimeoutExpiredr5   rk   rm   
splitlinesr4   r|   r   r}   rl   )	verbknownrp   lnstdout_linesrB   reasonunitwarningss	          r8   _probez_systemd.<locals>._probe   so   	.>>hd3#D$ /3kk.?R-K-K-M\rQSQYQYQ[
\\+7Q%%'RE>L+DKK,=2>"#2dV8F84?PQ	
 *)) ! 	.OO232dV;RST--(( 	.OO232dV?KL--334 	.OO232dV;STU--	. ]s)   C E2E2*E/>3E/39E/.E/z	is-activez
is-enabled)r   strr   z'frozenset[str]'returnr   )_KNOWN_ACTIVE_TOKENS_KNOWN_ENABLED_TOKENS)r   r   r   r   r   s   `   @r8   _systemdr      s9     H*@ K!56F\#89G7H$$r?   c                x   | sy| j                         }|sy	 t        |      }|Nt        j                  |      sdt
        fS t        j                         |z
  }|dk  rdt        fS t        |      dfS 	 ddl	m	} |j                  |j                  dd            }	 |j                  dt        fS |j                         }t        j                         |z
  }|dk  rdt        fS t        |      dfS # t        $ r d}Y w xY w# t        t        f$ r dt
        fcY S w xY w# t         t"        f$ r dt
        fcY S w xY w)u!  activation epoch(unix sec or ISO8601) → (age_seconds, warning_token).

    tz 안전 규칙(KST 환경에서도 안정):
      - 순수 숫자 → unix epoch 초로 보고 `time.time() - epoch` (tz 독립).
      - ISO8601 + tz offset(aware) → tz-aware 비교(`dt.timestamp()` 가 정확).
      - ISO8601 naive(offset 없음) → KST/local 임의 해석 **금지** → age=None +
        UNKNOWN_NAIVE_DATETIME warning (datetime.timestamp() 의 local 가정 회피).
      - 파싱 실패/비유한값 → (None, EPOCH_PARSE_UNAVAILABLE).
      - 극단 날짜(예: 0001-01-01, platform time_t 범위 밖)에서 timestamp() 가
        OverflowError/OSError → (None, EPOCH_PARSE_UNAVAILABLE), crash 0.
      - 음수 age(미래 epoch) → 0 으로 clamp + EPOCH_IN_FUTURE warning(crash 0).
    )NNNr   datetimeZz+00:00)r4   floatrK   mathisfiniteEPOCH_WARN_PARSEtimeEPOCH_WARN_FUTUREintr   fromisoformatr.   rL   tzinfoEPOCH_WARN_NAIVE	timestampOverflowErrorr5   )	epoch_rawrawepochdeltar   dttss          r8   _epoch_age_secondsr   "  sS    
//
Cc
 }}U#)))		e#19'''5z4&%##CKKX$>?&99)))\\^
 IIK"Eqy###u:t;   	" &%%%& 7# & %%%&s;   C1 4'D D 0D 1C?>C?DDD98D9c                `    | y| dk  r|  dS | dk  r| dz   dS | dk  r| dz   dS | dz   dS )	Nnone<   rN   i  miQ hd )secondss    r8   
_human_ager   X  sa    |!}~R-""T/"!$$q!!r?   c                 V   	 ddl } | j                  t              }t        |dd      }t        |      st        dfS 	  |       }t        |t              r"t        |t              s|dk\  rt        |      dfS t        dfS # t        $ r t        dfcY S w xY w# t        $ r t        dfcY S w xY w)	ub  cron 등록 수 (raw key 금지). 반환: (value, reason).

    cron-list 조회에는 key 가 필요하다. raw key literal 을 만들지 않으며, sealed
    loader 기반 read-only helper(`CRON_HELPER_MODULE.cron_count_readonly`)가
    **실제로 존재하고** raw key 출력/기록 없이 cron 수(int)만 돌려줄 때에만 사용한다.

    helper 부재 · 계약 불명확 · 새 key path 신설 필요 → **사용 금지**,
    `UNKNOWN_KEY_REQUIRED` 유지하고 reason 에 왜 UNKNOWN 인지 명시한다.
    cron-list 를 직접 호출하지 않는다(등록/수정 0, 새 key path 0).
    r   NzNno sealed read-only cron helper module present; raw key required and forbiddencron_count_readonlyzMcron helper present but lacks sealed read-only cron_count_readonly() contractz0sealed cron helper raised during read-only queryz)sealed read-only cron helper (no raw key)z,sealed cron helper returned non-count result)	importlibimport_moduleCRON_HELPER_MODULE	ExceptionCRON_UNKNOWN_KEY_REQUIREDgetattrcallablerI   r   boolr   )r   helperr^   results       r8   _cron_countr   e  s    	
(();< 
.	5BB<%-
 	


 &#z&$'?FaKFHII!6 -  
%-
 	

  
%>
 	

s"   A= B =BBB('B(c                   |j                  d      dk7  }|j                  d       }|j                  d       }|j                  d       }|j                  d       }|xr |xr
 |xr |xr |}t        |j                  dd            }	t        |j                  d	d            }
| rt        S |r|	d
k\  r|
dk(  rt        S |r|	dk(  r|
dk(  rt        S t        S )uw  현재 flag 스냅샷 + counts 기반 read-only level 판정.

    - 전 flag OFF + LIVE_PENDING≥1 + LIVE_PROCESSED 0 + ACTIVE=false
        → L2_DONE_L3_BLOCK_EVIDENCE_ACTIVE_FALSE.
    - LIVE_PROCESSED 0 이면 L3 PASS 로 올리지 않는다.
    - 전 flag OFF + pending 0 + processed 0 → L0/L2-ready (baseline).
    - 모호 → INDETERMINATE_REVIEW_REQUIRED.
    driverr   event_strategycallbackepoch_presentkillr$   r   r#   rA   )getr   LEVEL_INDETERMINATELEVEL_L2_DONE_L3_BLOCKLEVEL_L2_READY)r   flagscounts
driver_offes_offcb_off	epoch_offkill_offall_offlive_pendinglive_processeds              r8   compute_levelr     s     ))H%2J+,,F:&&FIIo..I99V$$HIVII9IGvzz"<a@AL$4a89N ""<1$1)<%%<1$1)<r?   c                `    d> fd}t         |t                    }|r|nd}|dk(  }t         |t                    }t         |t                    }t         |t
                    }|du}t        |      \  }	}
t         |t                    }|t        |      t        |      ||	t        |      d}t         |t                    }|r|xs i j                  d      nd}d}|rS|j                  d      |j                  d      |j                  d      |j                  d	      |j                  d
      d}t        t         |t              t              t        t         |t               t              t"        t         |t              t"              dt%         |t&                    dt%         |t(                    dt%         |t*                    dt-         |t.                    i}|d   |d   d}|d   |d   d}t1        t2              \  }}}t2        ||d}t5               \  }}t7         ddg      }t7         ddg      }t7         ddg      }t7         ddg      }t9        |      }|r|nd|r|nd|r|ndt        |xr	 |xr ||k(        |d}t;        |||      }g } |r| j=                  d       |d   r| j=                  d       |d    r3t?        |d    t@              r |d    d!kD  r| j=                  d"|d     d#       |d$   s| j=                  d%       |tB        k(  r| j=                  d&       | jE                  |       |
r| j=                  d'|
 d#       tG        |      jI                  d(      r| j=                  d)| d#       |t           d!k(  r|t"           d*k\  r| j=                  d+       i d,d-d.tK               d/ d0|d1|d2|d3|d4|d5|d6|d|d7|d8|d9|d:|d;tL        D !ci c]	  }!|!tN         c}!d<tQ        tR              tT        tQ        tV              | d=S c c}!w )?uI   root(repo) 기준으로 모든 항목을 read-only 수집해 dict 반환.c                D    t         j                  j                  |       S r;   )r<   r6   join)relrn   s    r8   pzcollect.<locals>.p  s    ww||D#&&r?   r   r   N)r   r   r   r   epoch_age_secondsr   verdictr   r   
activationowner_key_class)r   r   r   r   r   
live_inbox	p0b_inboxarchiverW   )r   r   )r   rW   )r   	is_active
is_enabledbranchz--show-currentz	rev-parseHEADzorigin/mainstatusz--porcelain=v1unknown)r   headorigin_mainsyncedtracked_dirty)r   r   r   u:   ACTIVE=true observed — expected false (review required).r   u)   p0b_kill present — kill switch engaged.r   r   ztracked dirty files: .r   z)HEAD != origin/main (or git unavailable).u:   automation level indeterminate — manual review required.zactivation_epoch: UNKNOWNu   cron count unavailable — rA   uO   L3 owner-proof PASS (LIVE_PROCESSED) not reached — evidence/block layer only.schemazanu_control_status.v1generated_atrn   automation_levelr   r   r   last_verdictlast_evidenceinboxsystemdcroncron_reasonra   wired_gated_axespending_approvals)next_allowed_actionforbidden_actionsr   )r   r   r   r   ),r9   REL_DRIVER_ENABLEDr>   REL_EVENT_STRATEGYREL_CALLBACK_LAUNCHREL_ACTIVATION_EPOCHr   REL_KILLr   rP   REL_DRIVER_RUNSr   TOK_REAL_FIREDrE   REL_CALLBACK_AUDITTOK_LIVE_PROCESSEDREL_PROCESSED_LEDGERTOK_LIVE_PENDINGrU   REL_LIVE_INBOX_GLOBREL_P0B_INBOX_GLOBREL_ARCHIVE_GLOBr_   REL_REPORTS_DIRr   SYSTEMD_UNITr   rq   _tracked_dirty_countr   r   rI   r   r   extendr   
startswith_now_isoWIRED_GATED_AXESWIRED_GATED_LABELlistPENDING_APPROVALSNEXT_ALLOWED_ACTIONFORBIDDEN_ACTIONS)"rn   r   driver_textdriver_stater   r   r   r   r   	epoch_age
epoch_warnr   r   last_objr   r   r   r   r   sysd_activesysd_enabledsysd_warningsr   r   r   r   r   r   	porcelainr   ra   r   r   axiss"   `                                 r8   collectr!    s   ' Q123K"-;:LY&FQ123Nq,-.H1123IT)M.y9Iz1X;D ~.N&&T
E q12H6>HN''	2DL.2M,,t$||I.ll8,",,|4'||,=>
 	*1-?+@.Q.q1E/FHZ[,Q-?AQRk!$7"89[#5!67;q!123$&7/8J&KF ",/f[>QRE)$&,-I&JG 08/E,K} "G $D+ $#345F{F+,Dtk=9:KTH&678I(3M"&	)&1{ytCC0CD&C %F%O HTUV}CD
?
3+? E#oJ^abJb/O0D/EQGHx=CD..TUOOM",ZL:;
4yI&5k]!DE !Q&62B+Cq+H]	
)
 	 	,	
 	& 	 	& 	 	 	 	7 	7 	 	{ 	s  	AQRT#44R!" 	T"34#$  3!"34)   Ss   2P+c                    | yd}| j                         D ]'  }t        |      dk\  s|d   dk(  s|d   dv s#|dz  }) |S )u   git status --porcelain=v1 출력에서 tracked dirty(미스테이지 M/D) 줄 수.

    spec §2: `git status --porcelain=v1 | grep -cE '^ (M|D)'`.
    git 미사용/불가(tmp root 등) → 'unknown'.
    r   r       rA   )MD)r   rR   )r  rC   rD   s      r8   r  r  :  sZ     	A$$& t9>d1gnaJ1FFA Hr?   c                     	 ddl m }  | j                         j                         j                  d      S # t        $ r Y yw xY w)Nr   r   r   )timespecrs   )r   now
astimezone	isoformatr   r   s    r8   r  r  I  s?    %||~((*44i4HH s   36 	AAc                4    t        j                  | ddd      S )NFr#  T)ensure_asciiindent	sort_keys)rG   dumps)datas    r8   format_jsonr2  S  s    ::dqDIIr?   c                    | rdS dS )Ntruefalser   )bs    r8   _ynr7  W  s    6#G#r?   c                $   | d   }| d   }| d   }| d   }g }|j                   } |d        |d| j                  dd               |d	| d
            |dt        | d          d        |d        |d|d            |dt        |d                  |dt        |d                  |dt        |d          dt        |d                  |dt        |d                  |d        |d| d           | j                  d      xs i } |d|j                  d        d!|j                  d"       d#|j                  d       d$|j                  d%               |d&        |d'|d(            |d)|d*            |d+|d,            |d-|d.            |d/|d0            |d1|d2            |d3|d4            |d5        |d6|d7    d8|d9    d:|d;            |d<| d=    d>| j                  d?d       d@        |dA        |dB|dC    dD|dE            |dF|dG    dHt        |dI          dJ|dK            |dL       | dM   j	                         D ]  \  }}	 |dN| dO|	          |dP       | dQ   D ]  }
 |dR|
          |dS| dT            |dUdVj                  | dW                  |dX       | dY   r| dY   D ]  } |dZ|         n |d[        |d\       d]j                  |      S )^Nr   r   ra   r   u   ┌─ ANU 자동화 조종석 (read-only · 관측 전용) ──────────────────────────u   │ generated_at : r   rs   u   │ automation level : r   u   │ ACTIVE : r   uA      (production activation: false, 별도 승인 전 항상 false)u   ├─ flags ─────────────────────────────────────────────────────────────u    │ p0b_driver_enabled        : r   u    │ p0b_event_strategy_enabled: r   u!   │ p0b_callback_launch_enabled: r   u(   │ p0b_activation_epoch      : present=r   z age=r   u    │ p0b_kill                  : r   u   ├─ verdict / evidence ────────────────────────────────────────────────u   │ last verdict  : r   r   u   │ last evidence : ts=r   z	 verdict=r   z driver=z activation=r   u   ├─ counts ────────────────────────────────────────────────────────────u"   │ REAL_FIRED total            : r"   u"   │ LIVE_PROCESSED total        : r#   u"   │ LIVE_PENDING_OWNER_PROOF tot: r$   u"   │ live_inbox count            : r   u"   │ p0b_inbox count             : r   u"   │ archive count               : r   u"   │ pending fixture quarantine  : rW   u   ├─ systemd / cron ────────────────────────────────────────────────────u   │ systemd r   z : is-active=r   z is-enabled=r   u   │ cron count : r   u     (raw key 미사용 — r   )u   ├─ git ───────────────────────────────────────────────────────────────u   │ branch=r   z HEAD=r   u   │ origin/main=r   z synced=r   z tracked_dirty=r   uz   ├─ WIRED ≠ ACTIVE (gated 축은 WIRED·ACTIVE=false) ─────────────────────r   u   │ - r   u   ├─ approvals / actions ───────────────────────────────────────────────r   u   │ pending approval : u   │ next allowed action : r   u   │ forbidden next actions : z, r   u   ├─ warnings ──────────────────────────────────────────────────────────r   u   │ ⚠ u
   │ (none)u   └──────────────────────────────────────────────────────────────────────
)r   r   r7  r   itemsr   )r1  r7   cgrN   linesaddevr   labelpaws               r8   format_humanrD  [  s:   WAXAUAYAE
,,C  	U  V
dhh~r:;<=
!$'9":!;<=-DN+,,mno  	O  P
*1X;-89
*3q1A/B+C*DEF
+C*,>+?@A
23q7I3J2K5Q[\]^q\rQsPtuv
*3qy>*:;<  	u  v
tN3456	/	"	(bB
!"&&,y	9J8K L&&"#<|0D/E	G H  	M  N
,Q|_,=>?
,Q/?-@,ABC
,Q/I-J,KLM
,Q|_,=>?
,Q{^,<=>
,Qy\N;<
,Q/K-L,MNO  	}  ~,qykq~.>l1\?J[\]
DL>)B488M[]C^B__`ab  	S  T+ak]&645
1]+,HS85E4FoVWXgVhUijk  	E  F./557 &efTF"UG$%&  	s  t&' ,%bT*+,
$T*?%@$ABC
'		$7J2K(L'MNO  	I  JJj! 	 A(1#	  	L  	`  a99Ur?   c                     t         j                  j                  t         j                  j                  t         j                  j                  t                          S )uA   이 스크립트(scripts/anu_control_status.py) 기준 repo root.)r<   r6   dirnameabspath__file__r   r?   r8   _default_rootrI    s/    77??277??277??8+DEFFr?   c                0    t        | xs
 t                     S )u3   공개 API: read-only 상태 dict 반환 (write 0).)r!  rI  )rn   s    r8   build_statusrK    s    4*=?++r?   c                   t        j                  dd      }|j                  ddd       |j                  dd d	
       |j                  |       }t	        |j
                        }|j                  r,t        j                  j                  t        |      dz          yt        j                  j                  t        |      dz          y)Nanu_control_statusu=   read-only ANU 자동화 조종석 (관측 전용 · write 0).)progdescriptionz--json
store_trueu   JSON 출력)actionhelpz--rootuX   repo root override (테스트용 격리 tmp root). 기본=스크립트 기준 repo root.)defaultrR  r:  r   )argparseArgumentParseradd_argument
parse_argsrK  rn   rG   sysrm   writer2  rD  )argvapro   r1  s       r8   mainr\    s    		 	 !S
B OOH\OFOOg  
 ==D		"Dyy

T*T12  	

d+d23r?   __main__)r6   r   r   Optional[str])r6   r   r   r   )r6   r   rB   r   r   r   )r6   r   r   zOptional[Dict[str, Any]])rT   r   r   r   )rZ   r   r   r   )rn   r   ro   z	List[str]r   r^  )r}   r   r   r   )r   r   r   zTuple[str, str, List[str]])r   r^  r   z#Tuple[Optional[int], Optional[str]])r   zOptional[int]r   r   )r   zTuple[str, str])r   r   r   Dict[str, Any]r   zDict[str, int]r   r   )rn   r   r   r_  )r  r^  r   r   )r   r   )r1  r_  r   r   )r6  r   r   r   r;   )rn   r^  r   r_  )rZ  zOptional[List[str]]r   r   )L__doc__
__future__r   rT  rS   rG   r   r<   ri   rX  r   typingr   r   r   r   r   r   r   r   r   r  r  r  r  r  r	  r
  r  r  r
   r   	frozensetr   r   r   r   r   r  r  r  r   r   r   r   CRON_UNKNOWN_NO_KEYr   r  r  r  r  r  r9   r>   rE   rP   rU   r_   rq   r   r   r   r   r   r   r!  r  r  r2  r7  rD  rI  rK  r\  __name__
SystemExitr   r?   r8   <module>rg     s  0 #     	  
  3 3 7 > @ : "6C A G A W "  + 2  ! "   " #   , , %  % -  B +5  3 & 6   P  XV   1  
($.2%l3l	"(X     	 
 	 HBJJ$4pG
,
* z
TV
 r?   