virtual-insanity
← 리포트 목록

Critic 리뷰 — 웹앱 실태 감사 전수 검증

2026-04-14 critic [critic, webapp, audit, openclaw]

Critic 리뷰 — 웹앱 실태 감사 전수 검증

최종 판정: REVISE

원본 보고서의 핵심 4대 고장 주장(/api/ops 500, /graph 404, KG embed 내부 graph.json 404, /api/chart/SPY 404)은 직접 curl과 코드 read로 재현됐다. 다만 “전수 감사”로 보기에는 누락된 등록 라우트가 있고, 일부 데이터 missing 주장은 현재 상태가 바뀌었으며, 회귀 테스트/플레이북 증거가 없다. 따라서 승인 불가, 보완 후 재검토가 맞다.

검토 대상: ~/knowledge-agent/100-inbox/260414_webapp_audit.md
체크리스트: /tmp/critic_checklist.md
검토 범위: 읽기 전용. 원본 보고서·웹앱 코드·shared/llm.py 수정 안 함.

1. 수행한 검증

  • 원본 보고서 전체 read.
  • scripts/pipeline/webapp/blueprints/ route annotation grep.
  • Flask app.url_map 덤프: /tmp/critic_routes.tsv 생성, 94개 route entry 확인.
  • Flask test client 샘플 sweep: /tmp/critic_test_client_routes.tsv 생성, 86개 GET 샘플 확인.
  • 보고서가 주장한 깨진 엔드포인트를 curl http://localhost:8080/...로 직접 재현.
  • 원인 코드 read:
  • webapp/blueprints/market.py:195-205, 2395-2402, 2450-2453
  • ~/.openclaw/workspace/graphify-out/graph.html:19
  • 동일 패턴 grep:
  • _L() 사용처 전체 확인.
  • /api/chart/, SPY, /graph, knowledge-graph/embed, market/api/summarize, watchlist/matches 호출자 grep.
  • 데이터 missing 이중 확인: ~/.openclaw/workspace/memory 현재 상태 재조회.
  • knowledge-agent/500-signals/에 웹앱 감사 플레이북 존재 여부 확인.

2. 보고서의 “깨진 엔드포인트” 직접 재검증

주장 직접 재현 결과 판정 근거
/api/ops 500 500, 265 bytes ✅ 재현 curl --max-time 15 ... /api/ops; 서버 로그도 ValueError: too many values to unpack (expected 8)
/graph 404 404, 207 bytes ✅ 재현 등록 route 없음
KG embed 내부 ./graph.json 404 /vault/knowledge-graph/embed200, 내부 fetch 대상 /vault/knowledge-graph/graph.json404 ✅ 재현 graph.htmlfetch('./graph.json'); 현재 URL 기준으로 잘못 해석됨
/api/chart/SPY 404 404, 20 bytes, {"error":"no data"} ✅ 재현 market.py:2400memory/price-history/SPY.json을 찾음. 파일 없음
/api/briefing/default 404, 0 bytes ✅ 재현 mode 제한. 단 의도된 contract일 가능성 있음
/api/briefing/latest 404, 0 bytes ✅ 재현 위와 동일
/api/fed-liquidity/chart 빈 데이터 200, 69 bytes, 모든 배열 빈 값 ✅ 재현 {"dates":[],...}
/api/analysis 빈 JSON 200, 3 bytes, {} ✅ 재현 daily-report/analysis-*.json 없음
/favicon.ico 404, 207 bytes ✅ 재현 정적 파일 없음
/robots.txt 404, 207 bytes ✅ 재현 정적 파일 없음
POST /api/alert-check 404, 207 bytes ✅ 재현 route 없음

3. 원인 코드 확인

/api/ops 500

확인한 코드:

  • _L()는 10개 loader를 반환한다.
  • api_ops()는 8개 변수로 unpack한다.

근거:

  • market.py:195-205
  • return (load_analysis, load_market_indicators, load_hypotheses, load_filtered_ideas, load_agent_worker_status, load_system_digest_summary, load_analyst_agents, load_cowork_tasks, load_conflict_signals, load_etf_insight)
  • market.py:2452
  • _, _, _, _, lw, ld, _, lt = _L()

동일 패턴 grep 결과 _L() 직접 사용처는 다음뿐이며, 같은 unpack 폭 불일치는 api_ops() 1곳만 확인됐다.

  • market.py:211_, lm, *_ = _L()라 가변 unpack, 안전.
  • market.py:2213fns = _L()라 안전.
  • market.py:2441, 2447 — index 접근.
  • market.py:2452 — 실패 지점.

KG embed graph.json 404

~/.openclaw/workspace/graphify-out/graph.html는 다음 상대 경로를 사용한다.

  • <a href="./graph.json">graph.json</a>
  • fetch('./graph.json')

이 파일이 /vault/knowledge-graph/embed에서 그대로 serve되면 브라우저는 /vault/knowledge-graph/graph.json을 요청한다. 해당 route가 없어 404다. 실제 존재하는 큰 원본은 /graphify/graph.json이며 로컬 파일 크기는 약 24MB(25201613 bytes)다.

/api/chart/SPY 404

market.py:2395-2402에서 memory/price-history/{TICKER}.json을 요구한다. 현재 memory/price-history/SPY.json은 없다. 템플릿 호출자도 확인됐다.

  • templates/analyst/log.html:633/api/chart/ fetch
  • templates/market/index.html:1315/api/chart/ fetch

4. 보고서가 놓친 route / 감사 범위 누락

원본 보고서가 주요 화면은 잘 봤지만, “전수” 기준으로는 아래 등록 route가 누락되거나 너무 뭉뚱그려졌다.

누락/부족 route 직접 결과 왜 문제인가
/market/api/summarize 200, 50 bytes, ID 없음 등록된 API인데 보고서 테스트 목록에 없음. 빈 입력 contract/호출자 확인 필요
/api/watchlist/matches 200, 3 bytes, {} watchlist API 계열 중 누락. 빈 데이터 상태 확인 필요
/api/vault/note valid path로 200, 129 bytes 원본은 /vault/note 빈 query 400만 언급. API variant는 별도 contract
/news/partial/<sector> 200, 211 bytes, “아직 채점된 뉴스가 없습니다” sector news partial route 누락
/watchlist/news/<item_id> 200, 70 bytes, “종목을 찾을 수 없습니다” watchlist partial route 누락
/market/bond-study/<date_str> 404, 16 bytes for 2026-04-14 index만 200으로 보고, 날짜 detail/pdf route는 확인 누락
/market/bond-study/<date_str>/pdf 404, 10 bytes for 2026-04-14/pdf 위와 동일
/admin/api/telemetry/* 비로그인 302; -L 사용 시 login HTML 200 원본의 /admin/* 302→200 정상은 큰 틀은 맞지만 API 호출자가 JSON을 기대할 때 -L 테스트가 200 HTML로 오인될 수 있음
/graphify/<path:filename> /graphify/graph.json은 약 24MB 기능상 정상이나 공개/성능/응답 크기 리스크가 보고서에서 충분히 분리되지 않음

추가로 blueprint 파일 중 sector_data.py, sector_valuechain.py, showcase_curate.py는 route annotation이 없어 원본의 “미등록/보조” 판단은 맞다. 단 sector_compass.py.bak에는 route 흔적이 있으나 .py 등록 대상은 아니므로 운영 route에서는 제외하는 것이 맞다.

5. 데이터 missing 목록 이중 확인

현재 기준 memory/**/*.json은 65개다. 원본 보고서의 기준 시각은 2026-04-14 10:12 KST, 원본 count는 62개였다. 이후 자동화가 파일을 추가/갱신한 것으로 보인다.

원본 missing 목록은 대부분 현재도 맞다.

경로 현재 확인
daily-report/analysis-*.json 0
fed-liquidity/latest.json 0
analyst-synthesis/latest.json 0
analyst-technical/latest.json 0
price-history/SPY.json 0
sector-news/*.json 0
watchlist/watchlist.json 0
bond-briefing/[0-9]*.json 0

차이점:

  • memory/commodity-alerts/latest.json은 현재 존재한다. stat 기준 2026-04-14 12:41:09 +0900, 2144 bytes.
  • 따라서 원본의 “commodity-alerts latest 없음”은 보고서 작성 당시에는 맞았을 수 있으나 현재 상태와는 불일치한다. 이 항목은 스냅샷 시각과 파일 manifest를 함께 남겨야 재현성이 생긴다.

6. 체크리스트 A~E 판정

A. 근본 원인 검증

  1. 보고서가 주장하는 원인이 실제 코드와 일치하는가?
    근거: /api/opsmarket.py:2452 unpack 불일치와 정확히 일치. KG embed는 graph.htmlfetch('./graph.json')와 정확히 일치. /api/chart/SPYmemory/price-history/SPY.json 누락과 코드 contract가 일치.

  2. ⚠️ 같은 패턴이 다른 파일/함수에 있어서 놓친 건 없는가?
    근거: _L() grep에서는 같은 unpack bug가 추가로 발견되지 않았다. 다만 route 전수 대조에서 /market/api/summarize, /api/watchlist/matches, /api/vault/note, /news/partial/<sector>, /watchlist/news/<item_id>, bond detail routes 등 감사 누락이 발견됐다. “같은 패턴 버그”는 추가 없음, “같은 감사 범위 누락”은 있음.

  3. ⚠️ 과거 유사 버그가 있었는지 git log / memory / error-ledger 검색
    근거: git log에는 웹앱/브리핑/route 관련 변경 이력이 다수 있고, fix: glob("*.json") → glob("202*.json") 같은 데이터 선택 버그 이력도 보인다. memory/error-ledger500-signals grep에서는 /api/ops, KG embed, price-history/SPY, alert-check 직접 항목을 찾지 못했다. 과거 맥락 조사는 수행했지만 유사 버그 추적은 보고서에 충분히 반영되지 않았다.

B. 수정의 완전성

원본 산출물은 코드 수정이 아니라 audit-only 보고서다. 따라서 아래는 “수정안/감사 산출물의 완전성” 기준으로 판정한다.

  1. 수정/제안이 증상만 가리는가, 근본을 해결하는가?
    근거: /api/ops는 unpack 구조 수정, KG embed는 실제 /graphify/graph.json 경로 정리, /api/chart/SPY는 price-history 생성/연결을 제안했다. 핵심 제안은 증상 masking보다는 근본 원인 방향이다.

  2. ⚠️ 엣지 케이스: 빈 입력, None, 큰 입력, 동시성, 네트워크 단절
    근거: 원본은 /vault/note 빈 query 400, /api/vault/search query 없음 등 일부 빈 입력만 언급했다. 그러나 /market/api/summarize의 빈 id, admin API의 비인증 JSON 기대, 24MB /graphify/graph.json, 네트워크/파일 missing fallback 같은 엣지 케이스가 체계적으로 정리되지 않았다.

  3. ⚠️ 실패 모드: 새 실패 경로가 있는가?
    근거: 코드 수정이 없어서 새 실패 경로는 만들지 않았다. 다만 제안된 /graph redirect나 KG route 추가가 24MB JSON 공개/로드 경로를 바꿀 수 있는데, 실패 모드와 fallback 설계가 보고서에 없다.

  4. 되돌릴 수 있는가? 롤백 전략?
    근거: audit-only라 롤백 대상 변경이 없다. 코드 수정 없음 확인.

C. 부작용

  1. ⚠️ 다른 호출자 영향 확인
    근거: /api/chart/ 호출자는 템플릿 grep으로 확인 가능했으나 원본 보고서에는 호출자 분석이 없다. /graph alias 추가, KG embed 경로 수정, briefing mode 확장 여부는 다른 링크/템플릿 영향 분석이 필요하다.

  2. ⚠️ 성능 영향: 메모리, CPU, I/O
    근거: 원본은 /ops 6초, /verdict 1.3MB, /news/integrated 958KB, /graphify/graph.json 24MB 일부를 언급했다. 하지만 전 route 성능 기준선이나 큰 JSON 공개에 따른 I/O 영향은 별도 표로 관리되지 않았다.

  3. ⚠️ 보안: 새로운 공격 면
    근거: 원본은 외부 봇 스캔과 admin redirect를 언급했으나, /admin/api/telemetry/*가 비로그인에서 302이고 curl -L 시 login HTML 200으로 보이는 점, /graphify/graph.json 24MB 공개 응답의 노출/DoS 리스크는 충분히 다루지 않았다.

D. 검증

  1. 실제로 코드/엔드포인트가 실행됐는가?
    근거: Critic이 직접 curl로 핵심 고장 전부 재현했고, Flask test client sweep에서도 /api/ops 500을 확인했다.

  2. ⚠️ before/after 메트릭 있는가?
    근거: 원본은 상태/응답시간/크기 일부를 남겼지만 audit-only라 after가 없다. 현재 문제의 기준선으로는 쓸 수 있으나 수정 검증 메트릭은 아니다.

  3. 회귀 테스트가 이 버그를 실제로 잡는가?
    구체 결함: 원본 보고서에는 회귀 테스트 추가/실행 증거가 없다. /api/ops 500 같은 문제는 최소 route smoke test에 포함돼야 한다. 의도적 revert 실패 확인도 없다.

E. 문서·재현성

  1. ⚠️ 보고서만 읽고 다음 사람이 재현할 수 있는가?
    근거: 원본은 명령 요약과 표가 있어 핵심 재현은 가능하다. 그러나 route 전체 목록, curl raw transcript, 데이터 missing manifest가 없어 “전수” 재현은 어렵다. 특히 memory 파일 수/commodity-alerts 존재 여부는 시간에 따라 달라졌다.

  2. ⚠️ 접근 선택 근거와 대안
    근거: 우선순위 제안은 합리적이지만 /graph redirect vs 링크 제거, KG embed HTML rewrite vs route alias, /api/chart 404 유지 vs 빈 200 fallback 등 대안 비교가 없다.

  3. knowledge-agent/500-signals/ 플레이북 저장 여부
    구체 결함: find ~/knowledge-agent/500-signals ... webapp|audit|playbook|ops|critic 결과 웹앱 감사 플레이북 없음. 해당 디렉터리에는 다른 signal 문서만 확인됐다.

7. 결함 목록 — Codex A에게 넘길 수정 요구사항

  1. P0 — /api/ops 500 수정 필요
  2. 재현: curl http://localhost:8080/api/ops → 500.
  3. 원인: webapp/blueprints/market.py:2452, _L() 10개 반환을 8개 변수로 unpack.
  4. 요구: index 접근 또는 10개 unpack으로 수정. route smoke test에 /api/ops 포함.

  5. P1 — KG embed 내부 graph JSON 경로 수정 필요

  6. 재현: /vault/knowledge-graph/embed 200, 내부 /vault/knowledge-graph/graph.json 404.
  7. 원인: graph.htmlfetch('./graph.json').
  8. 요구: embed용 HTML을 /graphify/graph.json 절대경로로 rewrite하거나 /vault/knowledge-graph/graph.json alias를 제공. 24MB 응답 성능/캐시 정책도 함께 결정.

  9. P1 — /api/chart/SPY 데이터 contract 정리 필요

  10. 재현: curl http://localhost:8080/api/chart/SPY → 404 {"error":"no data"}.
  11. 원인: memory/price-history/SPY.json 없음. 템플릿에서 /api/chart/ 호출 중.
  12. 요구: price-history collector 실행/연결 또는 UI에서 missing 데이터 graceful fallback.

  13. P1/P2 — /graph 404 처리 결정 필요

  14. 재현: /graph 404.
  15. 요구: 실제 사용 링크가 있다면 /vault/knowledge-graph 또는 /graphify/로 redirect. 없다면 보고서에서 “별칭 기대가 잘못된 것”으로 낮춰 분류.

  16. P2 — /api/briefing/default, /api/briefing/latest의 오류성 재분류 필요

  17. 재현: 둘 다 404.
  18. 보고서 설명처럼 morning/evening만 contract라면 실제 caller 존재 여부를 grep한 뒤 “버그”가 아니라 “잘못된 테스트 입력”으로 낮춰야 한다.

  19. P2 — POST /api/alert-check 호출자 추적 필요

  20. 재현: 404.
  21. 요구: 남아 있는 cron/browser/external caller를 찾아 제거하거나 route 구현.

  22. P2 — 누락 route 감사 보완 필요

  23. 최소 추가 목록: /market/api/summarize, /api/watchlist/matches, /api/vault/note, /news/partial/<sector>, /watchlist/news/<item_id>, /market/bond-study/<date_str>, /market/bond-study/<date_str>/pdf, /admin/api/telemetry/*.
  24. 요구: 원본 보고서에 route map 기반 전수 표 또는 별첨 TSV 추가.

  25. P2 — 데이터 missing manifest를 시각 고정으로 저장 필요

  26. 현재 commodity-alerts/latest.json은 존재한다. 원본 보고서 작성 이후 상태가 바뀐 것으로 보인다.
  27. 요구: 감사 시각의 파일 목록/mtime/size를 knowledge-agent 쪽에 저장해 재현성 확보.

  28. P3 — 회귀 테스트와 플레이북 부재

  29. 요구: 최소 smoke test: status code, JSON content-type, auth redirect, known missing-data graceful behavior.
  30. 요구: knowledge-agent/500-signals/에 웹앱 감사 재현 플레이북 저장.

8. 추가 발견

  • /api/vault/note는 valid path를 넣으면 200이다. 따라서 원본의 /vault/note 400은 “빈 query contract”에 가깝고, 실제 고장으로 보긴 약하다.
  • /admin/api/telemetry/summary는 비로그인에서 302이며, curl -L은 최종 login HTML 200을 반환한다. API 정상 여부를 판단할 때는 redirect follow 여부를 분리해야 한다.
  • /market/bond-study/2026-04-14/market/bond-study/2026-04-14/pdf는 현재 404다. 원본이 bond index만 확인해 detail route 공백을 놓쳤다.
  • /market/api/summarize는 빈 입력에서 ID 없음 HTML을 반환한다. 정상일 수 있으나 route coverage에는 포함해야 한다.
  • /news/partial/semiconductor/watchlist/news/sample은 200이지만 실제 내용은 빈 상태 메시지다. 데이터 missing 영향으로 분류해야 한다.

9. 최종 승인 여부

REVISE

이유:

  1. 원본 보고서의 핵심 고장 원인 자체는 대부분 정확하다.
  2. 그러나 route 전수 감사 요구를 만족하지 못했다. 등록 route 대비 누락된 API/partial/detail route가 있다.
  3. 회귀 테스트, 재현 manifest, 500-signals 플레이북이 없다.
  4. 일부 항목은 시간이 지나며 상태가 변했으므로 스냅샷 증거 없이 단정하면 재현성이 떨어진다.

원 담당자는 위 결함 목록을 반영해 보고서를 개정하거나, 별도 보완 보고서와 route smoke test 결과를 추가해야 한다.