앞선 감사 네 번은 "이 코드가 제대로 도는가"를 물었다. 이번엔 작업자·노조·안전관리자·
공장장·설치기사 5인 관점으로 3단논법을 세우고 전제를 코드로 확인한 뒤 반문으로 깼다.
5인 중 2인의 논증이 완전히, 2인이 부분적으로 무너졌다.
읽기 감사 — 이 시스템은 쓰기를 전부 감사했다. 임계값 한 번 바꾼 것까지 해시 체인에
들어간다. 그런데 작업자 사진을 열어보는 것은 하나도 남지 않았다. 감시 시스템과 안전
시스템을 가르는 선이 정확히 거기라서, 나머지 프라이버시 설계(RAM 링버퍼 7.5초·전송 전
얼굴 블러·라이브뷰 부재)가 전부 이 한 줄이 없어서 검증 불가능한 주장에 머물러 있었다.
거부된 열람은 기록하지 않는다 — 그러면 감사 로그가 "본 사람"이 아니라 "요청한 사람"
목록이 되어 정작 누가 봤는지를 묻는 데 못 쓴다.
크론 생존 — 죽은 노드는 스스로 신고할 수 없어서 크론이 하트비트의 부재로 판정한다.
그런데 그 크론이 멈추면 아무도 판정하지 않는다. 통보·5분 확대·장치 이상 발행이 전부 그
한 프로세스에 걸려 있어서, 멈춘 순간부터 화면은 조용해지고 그 침묵은 "사고 없음"과
구별되지 않는다. 감시자의 생존은 감시자가 아닌 쪽이 읽어야 한다.
증거 보존 — 스냅샷 삭제가 나이 하나만 봤다. 기본 7일인데 중대재해 조사는 몇 주에서 몇
달이라, 우리가 내건 목적("사고 후 원인 분석")이 8일째에 스스로 사라졌다. 확인 처리로
자동 연장하지 않는 이유는 자동이면 사실상 전량 보존이 되어 7일 약속이 무너지기 때문이다.
증거 정책 — 프레임이 ergo_report 하나만 빼고 모든 이벤트에 붙었다. 체류 상한 초과도
포함이라, 밀폐공간 프리셋 기본값(30분) 기준으로 정상 작업자가 규정대로 일하는 동안
주기적으로 본인 사진이 서버로 갔다. 위험해서가 아니라 오래 있어서. 기준을 명문화했다:
시간 경과 자체가 조건인 이벤트에는 사진을 붙이지 않는다.
마이그레이션 2026-09-25-evidence-hold-and-view-audit.sql (README-UPLOAD 10번).
검증: phpunit 302 passed(신규 18, 스킵 0) · pytest(신규 18)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBjuR7DW6PNNMows4KnbYU
55 lines
2.8 KiB
PHP
55 lines
2.8 KiB
PHP
<?php
|
||
require __DIR__ . '/layout.php';
|
||
render_header('이벤트 상세');
|
||
?>
|
||
<div class="detail">
|
||
<nav class="detail-crumb"><a href="<?= view_url('/') ?>">감지 스트림</a><span aria-hidden="true">›</span><span>이벤트 상세</span></nav>
|
||
<header class="detail-head">
|
||
<div class="detail-headline">
|
||
<h1 id="detail-title">이벤트 상세</h1>
|
||
<p id="detail-when" class="detail-when">불러오는 중…</p>
|
||
</div>
|
||
<!-- 판정은 증거를 본 화면에서 끝난다. 예전에는 여기서 판단하고 목록으로 되돌아가
|
||
그 행을 다시 찾아 눌러야 했다 — 판단하는 자리에 판단 도구가 없었다. -->
|
||
<div class="detail-actions" id="detail-actions" hidden></div>
|
||
<div class="detail-actions" id="detail-hold" hidden></div>
|
||
</header>
|
||
|
||
<div class="detail-links">
|
||
<a id="detail-package" class="detail-link-btn" href="#">사고조사 패키지 →</a>
|
||
<a id="detail-zones" class="detail-link-btn" href="#">이 카메라 구역 설정 →</a>
|
||
</div>
|
||
|
||
<div class="detail-grid">
|
||
<section class="detail-card detail-evidence">
|
||
<h2 class="detail-card-title">증거 프레임</h2>
|
||
<div id="event-detail-snapshots"></div>
|
||
<p class="detail-foot">프레임은 발생 시점을 향해 왼쪽에서 오른쪽으로 이어집니다. 프레임 간격은 엣지의 스냅샷 주기 설정을 따릅니다.</p>
|
||
</section>
|
||
<aside class="detail-side">
|
||
<div class="detail-card detail-meta">
|
||
<h2 class="detail-card-title">이벤트 정보</h2>
|
||
<dl id="event-detail-meta" role="status" aria-live="polite" aria-busy="true">
|
||
<div class="detail-row"><dt>상태</dt><dd>불러오는 중…</dd></div>
|
||
</dl>
|
||
</div>
|
||
<!-- 사건에서 조치로 바로 이어지게 한다. 조치를 리포트 화면에서만 기록할 수 있으면
|
||
"무엇이 계기였나"가 끊기고, 나중에 그 대책이 어느 사고에서 나왔는지 알 수 없다. -->
|
||
<div class="detail-card detail-action-card" id="detail-action-card">
|
||
<h2 class="detail-card-title">개선 조치 기록</h2>
|
||
<p class="detail-card-hint">이 사건을 계기로 취한 조치를 남기면 전후 감지 밀도를 자동 비교합니다.</p>
|
||
<form id="detail-action-form">
|
||
<label class="field"><span class="field-label">조치 내용</span>
|
||
<input type="text" id="detail-action-note" maxlength="200" placeholder="예: 진입로 미끄럼 방지 매트 설치">
|
||
</label>
|
||
<button type="submit" class="btn-primary">조치 기록</button>
|
||
</form>
|
||
<p id="detail-action-status" role="status" class="admin-status"></p>
|
||
</div>
|
||
</aside>
|
||
</div>
|
||
</div>
|
||
<script src="<?= view_url('/assets/device-scope.js') ?>"></script>
|
||
<script src="<?= view_url('/assets/detail.js') ?>"></script>
|
||
<?php
|
||
render_footer();
|