Matrix
Matrix 는 플랫폼이 지금 어떤 상태인가와 무엇을 하기로 했는가를 한 곳에 모아 두는 서비스다. 두 가지를 다룬다.
- 운영 상태 — 서비스 health 를 주기적으로 수집해 시계열로 쌓고, 이상이 생기면 alert 로 남긴다. 가동률 보고서와 회선 장애 구간 보고서를 만들어 준다.
- 작업 원장 — 백로그(할 일), 고객 티켓, 로드맵 마일스톤, 배포 이력의 정본. 문서 파일이 아니라 데이터베이스가 정본이고, 사람과 에이전트가 같은 API 로 읽고 쓴다.
두 축 모두 MCP 도구로 노출된다. 그래서 AI 에이전트가 “지금 무엇이 내려가 있나”, “이번 분기 남은 항목이 뭔가”, “이 문제를 티켓으로 올려 줘” 를 사람의 중개 없이 처리할 수 있다.
접속
| 표면 | 주소 | 형식 |
|---|---|---|
| MCP | https://<workspace>.axelabs.ai/matrix/mcp | JSON-RPC 2.0 over HTTP |
| REST | https://<workspace>.axelabs.ai/matrix/api/... | JSON |
| 엔드포인트 목록 | https://<workspace>.axelabs.ai/matrix | JSON — 서비스가 자기 경로를 스스로 알려준다 |
<workspace> 는 조직에 배정된 워크스페이스 이름이다. MCP 커넥터로 등록하거나 AXE CLI 로 axe matrix ... 를 호출하면 같은 백엔드에 닿는다.
전용 호스트 matrix.axelabs.ai 에서는 /matrix/<문서 경로> 로 들어온 문서 요청이 /<문서 경로> 로 308 된다(api·mcp·health·static 은 제외 — 기계 경로는 그대로). 그래서 어느 마운트로 들어오든 Blueprint 콜백은 호스트마다 등록된 값 하나로 떨어진다(B-matrix-canonical-host-redirect). HTML 응답에는 보안 헤더 기준선 4종(Content-Security-Policy: frame-ancestors 'none' · X-Frame-Options: DENY · X-Content-Type-Options: nosniff · Referrer-Policy: strict-origin-when-cross-origin)이 붙는다 — 화면을 iframe 에 넣을 수 없고, MCP·REST 응답은 대상이 아니다.
인증과 범위
엔드포인트 목록(/matrix)과 상태 조회(/matrix/api/status)를 제외한 모든 호출은 Bearer 토큰이 필요하다. 토큰은 플랫폼 SSO 로 발급되며, 안에 담긴 조직 정보가 곧 데이터 범위다.
| 범위 | 무엇을 볼 수 있나 |
|---|---|
| 테넌트 | 자기 조직의 행만. 조회는 자동으로 자기 조직으로 고정되고 쓰기도 자기 조직으로 태깅된다. 다른 조직을 지정하는 인자를 넣어도 무시된다. |
| 플랫폼 운영자 | 전 조직 횡단. 인프라 모니터링·로드맵·배포 로그 조회는 이 범위에서만 열린다. |
서명이 유효해도 범위를 넘는 요청은 거부된다 — 인증과 인가가 별개로 걸린다.
서비스 토큰은 한 장씩 폐기할 수 있다. 토큰 안에 든 식별자를 기준으로 서버가 거부하며, 검증 경로가 하나뿐이라 폐기는 MCP·REST 전 표면에 동시에 적용된다. 토큰 하나를 내리려고 서명 비밀을 갈아 끼워 모든 조직의 토큰을 한꺼번에 무효화할 필요가 없다.
식별자가 없던 구 토큰은 자기 만료일까지 한시 수용된다 — 새 검증이 들어오는 순간 쓰던 연결이 끊기지 않게. 그동안 어떤 토큰이 재발급 대상인지가 로그에 남고, 구 토큰 수용을 언제 완전히 닫을지는 운영 측이 정한다.
MCP 도구
인프라 상태
| Tool | 입력 | 하는 일 |
|---|---|---|
get_status | — | 가장 최근 수집 주기의 전체 health 보고 |
get_service_history | service (필수), hours (기본 24) | 특정 서비스의 상태 시계열 |
list_alerts | include_acked (기본 false) | 미확인 alert 목록 (서비스·심각도·메시지·발생시각) |
acknowledge_alert | alert_id (필수), acked_by | alert 확인 처리. 이미 확인된 항목은 no-op |
get_uptime_report | days (기본 7) | 기간 내 서비스별 성공/전체 카운트 + 가동률 |
get_wan_report | hours (기본 168), since, until, target (internet·gateway·dns) | 회선 장애 구간(시작·종료·지속)과 가동률을 재구성한 보고서. 구조화 JSON + 그대로 붙여 쓸 수 있는 타임라인 |
백로그 · 티켓
| Tool | 입력 | 하는 일 |
|---|---|---|
backlog_list | status, service, milestone | 백로그 항목 조회. status 를 비우면 미해결 항목만 나온다 — done 과 dropped 를 함께 제외한다. 저장된 상태값 대신 파생값 stale 로도 거를 수 있다: 30일 넘게 손대지 않은 in_progress 항목이다 — 마지막으로 손댄 시점부터 센다(착수·등재·등록 중 가장 이른 것과 마지막 갱신 시각 중 더 나중). 노트를 붙이거나 다시 착수하면 그 시계가 다시 돈다. 조회 시점에 계산될 뿐 아무것도 기록되지 않는다. 모든 행이 blocked_on 과 파생 boolean stale 을 달고 나온다 |
backlog_create | id (필수), title (필수), body, status, service, milestone, owner, estimate, deps, blocked_on | 백로그 항목 생성. id 는 사람이 읽는 slug. 제목이 비었거나 slug 와 같거나 본문이 비면 거부된다(앞뒤 공백을 떼고 비교하므로 공백만 채운 제목·본문도 같은 거부다) — 맥락을 모르는 다음 사람이 그대로 착수할 수 있어야 한다. 이 규칙은 앱이 아니라 데이터베이스 제약(backlog_item_authoring_check)이라, 작성 경로가 몇 개든 같은 한 벌이 걸린다. 거부 문구는 세 조건을 모두 짚어 준다. deps 를 준 new 항목은 선언한 의존이 같은 조직 안에서 이미 전부 done·dropped 이면 처음부터 ready 로 등록된다. 처음부터 done·dropped 로 만드는 것은 해소라서, 이 id 를 기다리던 같은 조직의 항목이 같은 트랜잭션에서 풀린다(backlog_transition 과 같은 해제). 이미 있는 id 는 아무 검사 없이 no-op 이다 — 같은 id 를 동시에 만들면 뒤의 요청은 앞의 것이 커밋될 때까지 기다린다. blocked_on 의 규칙은 backlog_transition 과 같다 |
backlog_update | id (필수), title, body, service, milestone, owner, estimate, note | 항목의 수정 가능한 필드 갱신. note 는 본문 끝에 [YYYY-MM-DD <호출자>] <줄> 한 줄을 덧붙이고 갱신된 본문 길이를 돌려준다. 비지 않은 한 줄이어야 한다 — 줄바꿈이 든 노트는 거부된다(둘째 줄이 다른 사람이 서명한 기록처럼 보이기 때문). body 와는 상호배타다 — body 는 본문을 통째로 바꾸므로 남의 이력이 지워지는 경로가 바로 그것이다. 처분·결정·배포 참조는 note 로 남긴다. 바꿀 필드를 하나도 주지 않으면 거부한다(nothing to update: give at least one of …) — 아무것도 바꾸지 않은 호출이 갱신 시각을 올려 파생 stale 을 지우지 않게 |
backlog_transition | id (필수), status (필수: new·ready·in_progress·done·blocked·dropped), blocked_on | 상태 전이. 첫 착수·완료 시각이 자동 기록된다. status 가 blocked 면 blocked_on 이 필수다(공백만 채운 사유는 사유가 아니라 거부된다) — 280자 이내 한 줄로, B-<id> · D-<slug> · operator: <한 줄 질문 · (a)/(b) · 권고> 중 한 형식. done 은 착지·해결(완료일이 그날로 찍힌다)이고 dropped 는 안 하기로 결정(해결 시각만 찍히고 완료일은 비워 둔다). blocked 가 아닌 상태로 옮기면 사유는 지워진다 — 원장에 사유를 진 행은 지금 막혀 있는 항목뿐이다. done·dropped 로 옮기면 그 id 에 막혀 있던 항목이 같은 트랜잭션에서 ready 로 풀리고 사유가 지워지며, 풀린 id 가 응답에 함께 나온다. 그 id 를 deps 로 선언한 new 항목은 선언한 의존이 전부 끝났을 때만 함께 풀린다. blocked_on 이 같은 조직의 이미 done·dropped 인 항목을 가리키면 거부된다 — 그 대기를 풀어 줄 해소가 다시 오지 않기 때문이다: “blocked_on names an item that is already done or dropped, so there is nothing left to wait on: move this item to ready instead, or name the blocker that is actually still open.” 대기는 기다리는 행의 조직 안에서만 묶인다. 해제 후보는 같은 조직의 행뿐이고, 다른 조직의 id 는 자유 서술이라 호출자의 범위와 무관하게 거부되지도 풀리지도 않는다. 해제 중에는 규칙의 거절(작성 규칙에 걸린 레거시 행)만 그 행을 남긴 채 id 별로 보고되고, 그 밖의 DB 오류는 전이 전체를 실패시킨다. 같은 항목을 두고 해소와 대기가 엇갈려 데이터베이스가 한쪽을 교착(40P01)·직렬화 실패(40001)로 되돌리면 서버가 전체를 다시 시도하고(첫 시도 포함 최대 3회), 그래도 지면 “a concurrent write resolving or waiting on the same item crossed this one, so the ledger rolled this write back and changed nothing: run it again.” 를 돌려준다 — 아무것도 바뀌지 않았으니 같은 호출을 다시 하면 된다 |
ticket_file | id (필수), title 또는 summary, body, reporter, reporter_contact, severity, service, status, blocked_on | 고객 티켓 등록. 백로그와 같은 원장에 ticket 종류로 들어가되 누가 왜 요청했는가(요청자·연락처·심각도)를 함께 붙든다. blocked_on 은 backlog_transition 과 같은 규칙이다 — 이미 끝난 항목을 기다릴 수 없고, 대기는 이 티켓의 조직 안에서만 묶인다 |
backlog_summary | — | 조직별 롤업 (전체·미해결·완료·진행중·티켓 + 서비스별). dropped 는 미해결에도 완료에도 세지 않는다. 운영자 범위 |
백로그 원장 위생
등재일(discovered)은 비어 있을 수 없다 — 한 번의 백필로 등록 시각을 채운 뒤 필수 컬럼이 됐고, 새 항목은 등록한 날짜가 기본값으로 들어간다. 비어 있던 동안 그 행들은 나이 통계에서 통째로 빠져 있었다(불러온 옛 항목들이라, 오래 묵은 항목 수가 실제보다 적게 세어졌다). 소유자(owner)의 빈 문자열은 NULL 로 정규화된다 — 빈 문자열은 소유자가 아니라 “소유자 없음” 조회를 무력화하는 값일 뿐이다. 반면 service 는 일부러 제약하지 않는다: 값 목록이 AXE CLI 쪽에 살아 있어서, 여기서 잠그면 서비스가 하나 늘어난 날 정당한 쓰기가 거부된다.
로드맵 · 배포 로그
| Tool | 입력 | 하는 일 |
|---|---|---|
roadmap_list | — | 로드맵 마일스톤 목록 (정렬 순) |
roadmap_upsert | id (필수), title (필수), horizon, body, status, sort_order | 마일스톤 생성/갱신 |
shiplog_append | service (필수), summary (필수), commit_ref, highlight | 배포 이벤트 기록. 호출한 조직으로 태깅된다 |
shiplog_list | service, limit (기본 30) | 최근 배포 이벤트 목록 |
committee_record | repo (필수, owner/repo), pr (필수), head_sha (필수), run_id (필수), tier (필수), decision (필수: ship-candidate·needs-human·needs-human-quick·forbidden), split, claude_verdict, codex_verdict, high_count, medium_count, findings, cost_usd, claude_cost_usd, codex_cost_usd, wall_s, runner_s, committee_notes, telemetry (verdict.json 의 구간 시간·석별 모델·토큰·누적 비용 객체, 직렬화 16 KB 이하), append_note (원자적·멱등 노트 한 줄 추가), customer (저장소를 소유한 테넌트 — 커미티 서비스 토큰은 반드시 지정, 운영자는 지정 가능(기본 axe), 테넌트 토큰은 항상 자기 테넌트로 기록) | AI 커미티 판정 원장(committee_run)에 run 1건을 upsert. 사람 열(decided_by·human_final·shipped_sha·operator_task_ref·notes)은 이 도구가 받지 않는다(운영자 전용 committee_update_human). 키 = repo+pr+head_sha+run_id. 재기록 시 커미티 열은 교체되고(빠진 열은 null) 사람 열은 보존된다. committee_notes·telemetry 는 줄 때만 교체되고 빠지면 저장값을 유지한다. 호출한 조직으로 태깅 |
committee_update_human | repo, pr, head_sha, run_id (모두 필수), human_final (필수: ship·revise·reject), shipped_sha, operator_task_ref, notes | 사람의 최종 판정을 기록(decided_by=human). 자기 조직 행만 |
committee_list | repo, pr, since (RFC 3339 또는 YYYY-MM-DD), limit (기본 50, 최대 200), customer (선택 — 그 테넌트 행만) | 판정 원장 최신순 목록, telemetry 포함(운영자 전용 · 커미티 서비스 토큰도 사용). 응답 count = 돌려준 행 수(limit 이하), total_runs·cost_total_usd = 같은 필터(repo·pr·customer·since)에 맞는 전체 행의 수와 cost_usd 합계(limit 무관, 비용 없는 행은 0, 벤치 행 포함 — 목록과 같은 기준). since 의 YYYY-MM-DD 는 UTC 자정 |
committee_stats | since·until (RFC 3339 또는 YYYY-MM-DD = KST 자정, 기본 = 8주 전 월요일부터), repo, customer, include_bench (기본 false) | 판정 원장 주간 지표(운영자 전용, 주 = 월요일 00:00 KST): run 수·판정 분포·PR→판정 대기 p50/p90·구간 중앙값·러너 분·비용·리뷰어 불일치율·사람 번복률·재푸시 지적 소멸률(근사)·첫 주 대비 지수. 지표 정의는 응답의 definitions 에 함께 온다. 골든셋 벤치 run(committee_notes 에 bench: 로 시작하는 줄)은 기본적으로 모든 지표와 지수 기준에서 빠지고 주마다 bench_runs 로만 센다 |
수집 루프 — 점검은 전부, 기록은 하나
여러 인스턴스가 동시에 떠 있어도(예: 파랑/초록 두 색이 함께 사는 배포) 점검은 전부가 수행하고 기록은 한 인스턴스만 한다 — 데이터베이스 잠금을 쥔 쪽이 점검 이력과 알림을 남기고, 나머지는 자기 상태 보드만 최신으로 유지하다가 기록자가 사라지면 다음 주기에 자동으로 이어받는다. 원격 사이트가 밀어 넣는 보고는 이 잠금과 무관하게 받은 인스턴스가 그대로 기록한다.
REST
MCP 를 쓸 수 없는 호출자(모니터링 대시보드, 스크립트)를 위한 읽기 표면이다.
| 메서드 | 경로 | 인증 | 반환 |
|---|---|---|---|
| GET | /matrix/health | 불필요 | liveness |
| GET | /matrix/health/ready | 불필요 | readiness |
| GET | https://matrix.axelabs.ai/health/sso (정본 호스트 전용) | 불필요 | 로그인 경로 점검 — 플랫폼 로그인의 토큰 교환·서명키 조회를 실제 로그인과 같은 코드로 시험, 막히면 503 (D-ops-120) |
| GET | /matrix/api/status | 선택 | 무토큰은 요약 카운트, 운영자 토큰은 전체 리포트 |
| GET | /matrix/api/backlog | 필요 | 자기 조직의 백로그. JSON 에 blocked_on 과 stale 이 함께 실린다 |
| GET | /matrix/api/roadmap | 필요 | 로드맵 |
| GET | /matrix/api/ship-log | 필요 | 배포 이력 |
| GET | /matrix/api/summary | 운영자 | 조직별 롤업 |
| GET | /matrix/api/committee | 운영자 | AI 커미티 원장: {stats, runs, total_runs, cost_total_usd} — stats 는 committee_stats 와 같은 주간 지표, runs 는 최근 run(텔레메트리 포함), total_runs·cost_total_usd 는 runs 와 같은 기간·repo·customer·include_bench 조건의 전체 행 수와 비용 합계(limit 무관). 쿼리 since·until(RFC 3339 또는 YYYY-MM-DD = KST 자정, until 제외), repo, customer, include_bench(true|false, 기본 false), limit(기본 50, 최대 200). 익명 401, 조직 토큰 403 |
| GET | /matrix/api/alerts | 운영자 | alert 큐 |
토큰이 없으면 401, 토큰은 유효하나 범위를 넘으면 403 이다. 조직 범위 토큰으로 조회하면 다른 조직을 지정하려 해도 자기 조직으로 고정된다.
티켓을 올리는 가장 짧은 길
문제를 발견했을 때 메일이나 채팅으로 옮겨 적지 말고 원장에 바로 남기는 편이 낫다 — 그래야 다음 사람이 검색으로 찾는다.
{
"id": "T-report-slow-export",
"title": "월간 리포트 내보내기가 3분 넘게 걸린다",
"body": "재현: 리포트 > 월간 > 내보내기. 지난주까지는 즉시 끝났다. 브라우저는 최신 Chrome.",
"reporter": "담당자 이름",
"reporter_contact": "연락 가능한 주소",
"severity": "medium"
}title 은 slug 를 되풀이하지 말고 한 줄 요약으로, body 에는 재현 경로와 기대 동작을 넣는다. 이 둘이 비면 등록 자체가 거부된다.