형식과 규칙 문서입니다. 에이전트에게 일을 맡겨 쓰는 데는 읽지 않아도 되고, 기록을 다루는 도구를 만들거나 정확한 동작을 확인할 때 봅니다. 처음이라면 Jaekit 이해하기부터 읽으세요.
이 페이지
계약: 판단 이력 (decision-history/v1)
판단 이력은 실행자가 한 목표를 실행하면서 남기는 관측·원인·결정·조치·검증·막힘·재개를 구조화해 보존하는 선택 기능이다. 사건은 실행자의 주장이다. Core는 형식, 참조, 관계와 당시 문맥을 확인하고 조회할 뿐이며 판단 내용의 참 여부를 평가하지 않는다. 모델·네트워크·계정·상시 프로세스·검색 DB를 쓰지 않는다.
판단 이력은 실행 기록과 분리되어 있다. 이력을 쓰거나 읽어도 runs.jsonl은 바뀌지 않으며, 실행 규칙·완료 판정·종료 의미·승인·재작업·예산·검사·완료 뒤 발견의 결과도 달라지지 않는다. 기존 명령은 판단 이력에 접근하지 않는다. 이력의 서술은 막힘이나 필수 미충족 조건을 해소하지 않는다.
1. 지원과 호환
capabilities의 features에 decision-history/v1, formats.decision_history에 decision-history/v1이 있는 실행 파일이 이 기능을 지원한다(지원 정보 §5). 이 기능은 배포판 v0.1.5(ha 0.1.5)부터 제공하며 v0.1.4(ha 0.1.4)에는 없다. 기능을 쓰기 전에 실제 선택한 실행 파일의 두 항목을 확인하며 버전 숫자만으로 추정하지 않는다. 기능이 없으면 이력을 쓰지 않고 기존 Markdown에 설명을 남긴다. 자동 설치·업그레이드는 없다.
판단 이력은 유효한 시작 기록이 있는 목표에만 쓴다. 시작 기록이 없으면 등록을 거부하며(not_started) 실행을 시작하지 않는다. 시작 기록의 규칙(run-rules/1·/2·/3·/4)과 관계없이 쓸 수 있으며 저장된 규칙을 바꾸지 않는다.
현재 소스는 macOS·Linux의 arm64·amd64와 Windows x64 로컬 후보에서 같은 등록·재전송·정정·기본/current·list·두 방식의 show를 제공한다. Windows의 일반 checkout과 linked worktree도 같은 Git 공통 디렉터리의 이력과 잠금을 공유한다. Windows ARM64·x86, 네트워크/UNC 저장소와 같은 checkout의 Windows·WSL 교차 사용은 포함하지 않는다. Windows Server의 두 PowerShell 개발자 CI는 Windows 11 사용자 설치·에이전트 사용·공개 Windows 릴리스와 별개의 관측이다.
2. 저장 위치와 보존
<git-common-dir>/ha/history/<start-sha256>/events.jsonl 사건 (한 줄에 한 사건)
<git-common-dir>/ha/history/<start-sha256>/lock 등록 잠금
<git-common-dir>/ha/history/<start-sha256>/last 마지막으로 저장한 사건의 순번과 줄 sha256
<git-common-dir>는git rev-parse --git-common-dir가 알려 주는, 같은 저장소의 모든 작업 트리가 공유하는 Git 디렉토리다. 일반 checkout에서는.git이다. 기본 checkout과 linked worktree는 같은 이력과 같은 잠금을 쓴다. 작업 트리별 Git 디렉토리의 실행 기록 잠금(ha/lock)은 이력 등록을 보호하지 않는다.<start-sha256>는 해당 실행 시작 기록 줄의 원문 sha256이다. 목표 경로나 branch 이름을 쓰지 않으므로 경로에 허용된 공백·줄바꿈 같은 문자가 저장 위치를 바꾸지 않는다. 다른 시작 기록과 다른 저장소의 이력은 섞이지 않는다. 같은 시작 기록 원문을 가진 실행 묶음 사본은 같은 이력을 공유한다.- Core는 이 경로의 디렉토리·파일이 symlink이거나 일반 디렉토리·파일이 아니면 쓰지도 읽지도 않는다(
history_unreadable). Windows의 junction·reparse point도 같은 경계이며 중간 디렉터리, 사건·마지막 사건 식별·임시·잠금 경로 모두에 적용한다. 다른 이름과 내용을 공유하는 링크 수 둘 이상의 파일도 같은 오류로 거부한다. 조회도 그 너머의 자료를 이 저장소의 이력으로 보이지 않는다. 조회는 파일이나 디렉토리를 만들지 않는다. - 다른 작업 트리의 등록과 겹친 조회는 그 등록 전이나 후의 온전한 이력을 읽는다. 등록 잠금이 있으면 기다리며, 첫 등록의 저장 디렉터리가 아직 없으면 등록 전의 부재를 반환할 수 있다. 잠금 없이 읽은 사이 첫 등록 잠금이 생기면 잠금 아래에서 다시 읽어, 쓰는 도중의 줄을 손상으로 보고하지 않는다.
- 잠금 보유 프로세스가 종료되면 남은 잠금 파일만으로 후속 접근이 영구 차단되지 않는다. 쓰기 중 중단으로 남은 불완전한 사건 줄은 손상이며 자동으로 잘라 복구하지 않는다. 사건 저장 뒤의 실패나 저장 여부를 확인할 수 없는 실패는 기존
write_uncertain의미를 유지한다. Windows 파일 공유·권한·교체 거부를 성공으로 숨기지 않는다. - 로컬 자료다. Git이 추적하지 않으며 새 clone이나 다른 기기로 따라가지 않는다. 자동 백업·동기화·export/import·분기 병합·손상 복구는 없다. linked worktree를 제거해도 이력은 남지만, 남은 이력이 목표 문서나 검사 출력 원문의 보존·복원을 보증하지 않는다. 검사 출력 원문만 지우는 안내(설치 안내)는
ha/runs/만 지우고 이력을 남긴다. Git 디렉토리 전체 삭제처럼 보존 범위를 벗어나는 행위는 복구하지 않는다. - 목표 디렉토리를 옮겨도 같은 시작 기록 원문이면 같은 이력을 찾는다. 이때 조회는 당시와 현재의 목표 경로·문서 digest 차이를 표시하며 바뀐 경로·내용의 유효성을 자동으로 인정하지 않는다.
3. 명령
ha history <goal> add --kind <종류> --summary <요약> [--status <주장 상태>] [--reason <이유>]
[--target <AC-n|EX-n|T001>]... [--ref <참조>]... [--checked-ref <참조>]...
[--builds-on <Dn>]... [--supersedes <Dn>[@<Dm>]]...
[--event-at <RFC 3339 시각>] [--request-id <식별자>]
ha history <goal> add --input <JSON 파일 | -> [--request-id <식별자>]
ha history <goal> [current|list] [--target <id>]... [--keyword <낱말>]...
[--since <커서>] [--page <커서>] [--limit <n>] [--format md|json]
ha history <goal> show <Dn> | --request-id <식별자> [--page <커서>] [--limit <n>] [--format md|json]
- 흔한 사건은 별도 입력 파일 없이 한 번의
add호출로 등록한다. 같은 플래그를 반복한 값은 준 순서를 유지한다. 근거는--ref값들 다음에--checked-ref값들이 오며, 두 플래그를 섞어 준 순서는 보존하지 않는다. 단일 값 플래그를 반복하면 거부한다. - 참조 표기:
run:<seq>[:<sha256>],file:<path>[#<section>],commit:<sha>[@<repository>],external:<reference>.--checked-ref는 실행자가 내용을 읽었다고 주장하는 참조다. --input은 아래 JSON 하나를 받는다(-는 표준입력). 최대 256 KiB이며 잘못된 UTF-8·중복 키·대소문자만 다른 필드·알 수 없는 필드·뒤에 붙은 자료·16단계보다 깊은 중첩을 거부한다. 외부 참조의version처럼 플래그 표기가 없는 값도 이 형식으로 넣는다.
{"request_id": "decide-1", "decision": {"kind": "결정", "summary": "빈 입력이면 0을 반환한다",
"targets": ["T001"], "builds_on": ["D1"],
"evidence": [{"type": "external", "ref": "https://example.invalid/review/1", "version": "rev-2", "checked": true}]}}
--request-id를 생략하면 Core가 새 식별자(auto-…)를 만들어 영수증과 적용 불확실 오류에 표시한다. 재전송에서 중복을 막으려면 같은 식별자를 다시 준다.- 조회 기본은
current, 기본 형식은 Markdown이다. Markdown과 JSON(decision-history-view/v1)은 같은 사건과 의미를 전달한다.
4. 사건
| 정보 | 작성 책임과 의미 |
|---|---|
id, seq, prev |
Core가 붙인다. id는 이력 안 순번의 D<n>이다. prev는 직전 줄 원문의 sha256이며 첫 줄은 null이다. 이 연결로 중간 줄의 수정·삭제와 잘린 줄을 손상으로 드러낸다. 끝에서부터 지운 온전한 줄은 last가 기록한 마지막 사건과 대조해 손상으로 드러내며, 사라진 사건의 ID·요청 식별자를 다른 내용에 다시 쓰지 않는다. last와 이력을 함께 지우거나 이전 상태로 되돌린 경우는 드러나지 않는다. 줄을 쓴 직후 중단되거나 last 갱신이 실패하면 last가 한 사건 뒤처질 수 있다. 조회는 이 상태를 허용하며, 그동안 그 마지막 사건이 끝에서 사라진 것은 드러나지 않는다. 다음 등록이 먼저 last를 따라잡고, 따라잡지 못하면 등록하지 않는다. 같은 사용자 권한의 위조를 막지는 못하므로 보증은 local이다 |
request_id |
요청 식별자. ASCII 영숫자로 시작하는 영숫자·_.:/-, 1~128자. 이력 안에서 고유하다 |
decision.kind, decision.status |
종류와 주장 상태. 아래 표 |
decision.summary |
한 줄 요약. 1~1024바이트 |
decision.reason |
해당하는 사건에만 적는 이유. 줄바꿈을 허용하며 최대 16 KiB |
decision.targets |
관련 조건(AC-n)·추가 확인(EX-n)·Task(T001). 등록 시 현재 목표 문서와 PLAN에 있는 ID만 받는다. 대상의 상태를 바꾸지 않는다 |
decision.evidence |
근거 참조. 아래 5절 |
decision.builds_on, decision.supersedes |
사건 관계. 아래 6절 |
decision.event_at |
선택. 출처가 아는 사건 시각의 RFC 3339 주장. 모르면 생략하며 기록 시각으로 대신하지 않는다 |
at |
등록 명령이 읽은 기록 시각(UTC). Core가 붙인다. 다른 등록의 잠금을 기다린 경우 실제 저장보다 이르고 앞 사건의 at보다 이를 수 있다. 사건의 순서는 seq가 정한다 |
context |
Core가 등록 시 관측한 당시 문맥: 시작 기록(start), 실행 기록 head(run_head의 seq·sha256), 목표 경로, HEAD commit, 작업 트리 깨끗함(목표의 실행 묶음 제외 경로 기준), 목표 문서 digest, 근거별 관측(evidence) |
decision은 제출한 그대로 저장한다. 같은 request_id의 같은 decision은 재전송으로 판단하며, 등록 시각이나 당시 문맥이 달라도 새 사건을 만들지 않는다.
| 종류 | 주장 상태 |
|---|---|
관측 |
없음 |
원인 |
가설, 확정, 기각 |
결정 |
없음 |
조치 |
예정, 반영 |
검증 |
대기, 통과, 실패 |
막힘 |
없음 |
재개 |
없음 |
종류는 PROGRESS 타임라인과 같은 토큰이다. 주장 상태는 해당 종류에만 붙일 수 있고 생략할 수 있다. 생략은 미표시이며 다른 상태로 추정하지 않는다. 계획 변경은 결정의 내용으로 적고, 정정은 종류가 아니라 관계로 표현한다. 종류와 상태는 작업 단계나 상태 전이 절차가 아니다. 사건을 어떤 순서로 쓰는지, 작은 사건을 하나로 묶는지는 실행자가 정한다. 묶은 사건은 대표 종류와 그 주장 상태만 기계적으로 구분되며 나머지 경과는 요약과 이유에 적는다.
5. 근거 참조
type |
입력 | 등록 시 관측(context.evidence[].state) |
조회 시 현재 비교 |
|---|---|---|---|
run |
seq, 선택 sha256 |
같은 목표 실행 기록의 그 줄이 있고 해시가 맞으면 matched이며 version에 그 줄의 sha256을 남긴다. 없거나 해시가 다르면 거부 |
현재 실행 기록의 같은 줄과 same·different·unverifiable |
file |
저장소 기준 path, 선택 section |
저장소 안의 일반 파일이면 read와 내용 sha256(version). missing(경로가 없음), not_regular(symlink 포함), outside, unreadable(권한·symlink 순환 등으로 그 경로나 상위 경로를 확인할 수 없음), too_large(64 MiB 초과)이면 버전 미식별 |
unchanged·changed·unavailable·unidentified |
commit |
commit(7~64자리 16진수), 선택 repository |
이 저장소에서 찾으면 found와 전체 id. 없으면 not_found(얕은 clone 포함), 다른 저장소면 other_repository. fetch하지 않는다 |
등록 시 관측 그대로 |
external |
ref(URL·issue·리뷰 댓글 등), 선택 version |
not_checked. version이 있으면 실행자가 준 버전 식별 주장이다 |
not_checked |
파일의 버전은 신규 등록과 상세 조회 모두에서 실제로 안전하게 읽은 저장소 내부 일반 파일의 내용으로 식별한다. 검사 뒤 파일이나 상위 경로가 교체돼도 저장소 밖 파일·마지막 경로의 symlink·비일반 파일을 정상 근거로 읽지 않는다. FIFO 등의 내용이나 작성자를 기다리지 않는다. 64 MiB는 실제 읽기에도 적용한다. 정확히 64 MiB인 파일은 식별할 수 있지만 처음부터 또는 읽는 중 상한을 넘으면 초과 판별을 위한 제한된 추가 읽기만 하고 too_large로 남긴다. 경합으로 안전하게 식별할 수 없어도 버전을 만들어 내지 않는다. 이런 관측 실패는 유효한 사건 전체를 거부하지 않으며, too_large는 입력을 거부하는 limit_exceeded와 다르다. 동시 수정에 대한 원자적 파일 복사본은 보증하지 않는다.
파일 근거의 기존 상위 symlink 또는 Windows junction이 저장소 내부 일반 파일로 이어지고 실제 대상의 동일성과 내부 위치를 안전하게 확인할 수 있으면 read로 관측한다. 외부로 이어지면 outside, 마지막 경로 자체가 링크이면 not_regular이다. 이 허용은 이력 저장 영역의 symlink·junction/reparse point 거부를 완화하지 않는다. OS가 경로 교체를 막아 원래 대상이 안전하게 유지되면 정상 관측할 수 있지만, 실제 교체 뒤에는 이전 대상의 검사 결과를 다른 파일에 적용하지 않는다.
Windows에서도 입력의 상대 경로 문법은 같다. 그 문법을 통과한 참조가 Windows 장치·named pipe·대체 데이터 스트림(ADS)·드라이브 상대 경로로 해석되면 열거나 기다리지 않고 unreadable·버전 미식별로 남긴다. 정상 파일의 부재·비일반 파일·외부 경로는 기존 상태로 구별한다. 이는 파일 관측의 한계이며 유효한 사건 전체의 거부가 아니다. 원래 입력 문법상 잘못된 참조는 그대로 거부하고, macOS/Linux의 유효한 파일 이름이나 이미 저장된 참조·관측을 소급 변경하지 않는다.
show는 등록 당시 관측을 그대로 보이고 현재 파일 비교를 별도로 계산한다. 당시 버전이 있었으나 현재 안전하게 식별할 수 없으면 unavailable, 당시부터 버전이 없었으면 unidentified다. current·list는 근거 파일 내용을 다시 읽지 않는다. 같은 요청의 재전송도 파일을 다시 읽기 전에 원래 영수증을 반환하며 당시 관측을 갱신하지 않는다. 이미 저장된 사건에도 같은 조회 의미를 적용하고 과거 관측을 자동 복구하지 않는다.
모든 참조의 checked는 실행자가 내용을 읽었다는 주장이며, 생략하면 내용 미확인이다. 참조는 내용의 진실성, 검사 통과, 독립 검토, 사용자 승인, 완료를 인증하지 않는다. 실행 기록의 사용자 확인 줄을 참조해도 그 줄이 원래 가진 의미 이상을 갖지 않는다. Core는 외부 자료를 실행·다운로드·수집하지 않으며 문서·로그 내용을 사건에 복사하지 않는다. 현재 파일 링크만으로 당시 내용을 확인했다고 표시하지 않는다. 같은 commit이라도 미커밋 변경이 있으면 같은 코드 입력으로 보지 않는다.
실행 시작 전의 판단은 PLAN·REVIEW·PROGRESS에 남긴다. 시작 뒤의 사건은 첫 사건을 포함해 PLAN·REVIEW의 절(file:<path>#<section>)과 그 문서의 등록 시 버전을 근거로 연결해 시작 전 판단과 흐름을 잇는다. 시작 전 사건이나 과거 시각을 이력에 소급 생성하지 않는다.
6. 사건 관계
| 관계 | 의미 |
|---|---|
supersedes |
이 사건이 이전 판단을 정정·기각·대체한다. 원래 사건의 내용과 근거는 그대로 남는다 |
builds_on |
이전 사건이 이 판단의 근거가 되었다. 선택 관계이며 참조한 사건을 바꾸지 않는다 |
- 두 관계 모두 같은 이력 안에 이미 저장된 앞선 사건만 가리킨다. 한 사건은 각 관계로 사건 64개까지 가리킬 수 있다. 자기 참조, 아직 저장되지 않았거나 뒤에 저장된 사건, 다른 이력의 사건, 존재하지 않는 사건은
invalid_relation으로 거부한다. 앞선 사건만 가리키므로 순환이 생기지 않는다. - 관계는 종류와 무관하다. 모든 사건에 관계를 요구하지 않으며 종류 사이의 순서나 전이를 강제하지 않는다.
- 정정은 그 판단에 대해 마지막으로 확인한 정정을 함께 적어 등록한다: 정정이 없었다면
--supersedes D1,D4가 마지막 정정이었다면--supersedes D1@D4. 등록 잠금 안에서 다시 읽은 이력의 마지막 정정과 다르면correction_conflict로 거부하고 그 정정을 알려 준다. 사건 시각이나 마지막 작성자라는 이유로 덮어쓰지 않는다. 충돌은 모든 실행 계보의 정정으로 판단한다. 여러 사건이 같은 사건을 근거로 삼거나, 근거로 삼은 사건이 이후 정정되는 것은 충돌이 아니다.
7. 실행 계보와 현재 판단
실행 계보. 조회는 그 목표의 현재 실행 기록 체인을 기준으로 삼는다. 유효한 체인은 실행 기록 §2·§4.2의 순번과 원문 해시 연결을 확인할 수 있는 체인이다. 사건의 context.run_head(등록 당시 실행 기록 head의 seq·sha256)가 조회 기준 체인에 포함되면 같은 계보(same)다. 같은 seq에 다른 줄이 있으면 다른 계보(different)다. 기준 head보다 뒤이거나 체인 무결성을 확인할 수 없으면(record_integrity에 해당하는 끊긴 연결·잘못된 순번 포함) 확인 불가(unverifiable)다. 선택적인 run 근거 참조는 계보 판정에 쓰지 않는다. 당시 head가 현재 head와 같을 필요는 없으며, 정상적인 실행 기록 추가로 head가 전진해도 같은 계보로 남는다. 같은 branch 이름이나 같은 seq만으로 같은 계보로 보지 않는다. 등록한 당시 문맥은 다시 쓰지 않는다. 실행 기록과 이력 전체를 하나의 원자적 시점에 관측했다고 주장하지 않는다.
같은 시작 기록과 실행 head를 공유해 실행 기록이 아직 갈라지지 않은 작업 트리는 같은 계보로 취급된다. 이때 명시적인 supersedes는 작업 트리 경계를 넘어 지정한 판단을 대체하며, 코드·문서 차이는 문맥 표시(context.code, context.spec)로만 구분한다.
현재 판단. 전체 이력의 마지막 사건 하나가 아니다. 조회 기준의 이력 범위 안에서 같은 계보의 supersedes로 대체되지 않은 같은 계보 사건들의 집합을 먼저 계산한 뒤, 대상·키워드·변경분·페이지 조건을 적용한 결과다. 필터 밖의 정정도 대체 효력에 반영되므로 검색 조건이나 페이지 제한 때문에 대체된 판단이 다시 나타나지 않는다. 필터에 맞는 현재 판단이 없으면 정상적인 0건 결과일 수 있다.
- 다른 계보의 사건은 불일치로, 확인 불가 사건은 확인 불가로 따로 표시하며(
other_lineage) 현재 판단 집합에 합치지 않는다. 그런 사건의 정정도 선택한 계보의 판단을 대체한 것으로 계산하지 않고 정정의 존재만 드러낸다(other_corrections). - 같은 계보 안에서도 당시와 현재의 코드(
same·changed·uncommitted), 목표 문서(same·changed), 목표 경로(same·moved) 차이를 표시한다. 목표 문서 비교는ha status의 목표 digest를 쓰며 이 digest는 문서 경로를 포함하므로, 목표를 옮기면 내용이 같아도moved와 함께changed로 표시되어 옮긴 문서의 유효성을 자동으로 인정하지 않는다. - 대체되지 않았다는 사실이나 문맥 일치는 판단이 지금도 옳거나 적용 가능하다는 보증이 아니다. 적용 여부는 모델이 판단한다.
- 판단 C가
builds_on으로 A를 가리키고 A에 같은 계보의 정정이 있으면 C에 그 정정이 표시된다(grounds_corrected, Markdownlinked grounds A corrected by …). 조회 이력 범위 안의 정정은 C보다 먼저 저장됐거나 C 자체이거나 C 뒤에 저장됐어도 모두 포함하며, 기록 시각·사건 시각으로 선후를 가려 제외하지 않는다. 정정 ID는 저장 순서대로 표시한다. C는 자동으로 기각되거나 새 근거로 바뀌지 않는다. 표시는 직접 연결한 근거까지만 하며 연쇄적으로 전파하지 않는다. - 근거 정정 표시는 기본/current·list·사건 ID 또는 요청 식별자로 하는 show에서 같은 의미다. 대상·키워드·변경분 필터 밖의 정정도 포함하지만 다른 계보나 고정된 조회 경계 뒤의 정정은 포함하지 않는다. 이어읽기는 같은 경계의 정정 ID 집합·순서를 유지한다. 시간 중립 표시는 기존 JSON 구조·현재 판단 구성·저장 사건을 바꾸지 않으며 새 알림이나 전체 현재 상태의 증분 동기화를 뜻하지 않는다.
- 후속 관계가 없다는 사실만으로 미실행·미해결을 판정하지 않는다. Core는 "후속 조치 기록 없음" 같은 부재 조회를 제공하지 않는다.
8. 조회
| 조회 | 내용 |
|---|---|
current |
위 현재 판단. 다른 계보·확인 불가 사건의 수와 ID(최대 50개)를 따로 보인다 |
list |
범위 안 모든 사건의 요약. 사건마다 현재 판단 여부와 대체·정정 표시를 붙인다 |
show <Dn> |
한 사건의 상세: 이유, 근거와 등록 시·현재 관측, 당시 문맥, 직접 선행 사건(근거·정정 대상)과 직접 후속 사건(그 사건을 근거로 삼거나 정정한 사건)의 짧은 요약. --request-id로 저장된 요청을 찾는다 |
목록 항목은 식별자·종류·주장 상태·대상·짧은 요약과 해석에 필요한 계보·대체·근거 정정·문맥 차이 표시를 준다. 긴 이유·관계 전체·원문 로그는 자동으로 넣지 않는다. 상세는 연결 전체를 펼치지 않으며 더 먼 관계는 연결된 사건을 다시 선택해 읽는다. 별도 그래프 저장소나 질의 시스템이 필요 없다.
- 필터.
--target은 나열한 대상 중 하나라도 가진 사건,--keyword는 요약과 이유에 모든 낱말을 대소문자 구분 없이 포함한 사건을 고른다. 순서는 사건 순번 오름차순이다. - 변경분과 대체 안내. 모든 조회 결과는 이력 경계 커서(
cursor,c1.…)를 준다.--since <커서>는 그 경계 이후부터 이번 조회 경계까지 추가된 사건을 대상으로 한다. 같은 조회 조건에서 필터에 맞는 새 사건을 보이고, 그 구간의 같은 계보 정정이 필터에 맞는 같은 계보 판단을 대체했다면 정정 사건이 필터 밖이어도, 원래 판단이 이전 경계보다 앞에 있어도 대체 안내(type: replacement, Markdownreplacement: A → B)를 붙인다. 안내는 현재 판단 목록과 구분되며 일반 현재 조회에는 과거 대체를 붙이지 않는다. 변경분은 새 사건과 직접 대체 관계를 전달할 뿐, 계보나 코드·문서 문맥 변화까지 포함한 현재 판단 집합 전체의 동기화를 보장하지 않는다. 전체 현재 상태는current로 확인한다. Agent별 열람 상태나 알림 사건을 저장하지 않는다. - 이어읽기. 한 번에 기본 20건, 최대 200건(
--limit)을 보인다. 목록·직접 후속·대체 안내가 잘리면truncated, 보인 수와 전체 수, 다음 페이지 커서(next,p1.…)를 준다. 페이지 커서는 첫 페이지의 이력 범위, 선택한 실행 head, 조회 조건을 고정하므로 그 사이 새 정정이 저장되어도 누락·중복 없이 이어진다. 그 실행 head를 현재 체인에서 확인할 수 없으면cursor_stale로 재조회가 필요함을 알린다. 다른 이력의 커서는cursor_invalid, 다른 조건의 커서는cursor_mismatch다. 다음 변경분 조회는 새 경계 커서로 이력 경계를 전진시키며 같은 계보의 정상적인 head 전진을 반영한다. 계보가 갈라졌거나 기준을 확인할 수 없으면 이전 결과에 이어지는 변경분으로 처리하지 않는다. - 부재. 이력 파일이 없으면
history: absent로 답한다. 이것은 이 저장소에 이력이 저장되어 있지 않다는 뜻일 뿐 과거에 한 번도 기록하지 않았다는 뜻이 아니다.
Markdown은 Core가 계산한 해시를 앞 12자로 줄여 보일 수 있다. 외부 근거의 version처럼 실행자가 준 식별 값은 줄이지 않아 JSON과 같은 값으로 구분된다. 정정 관계에 등록 때 확인한 마지막 정정(after)이 있으면 관계 줄에 after D<n>으로 보인다. 목표 경로처럼 저장소에서 온 이름은 Markdown에 그대로 넣으므로, 줄바꿈 같은 제어 문자가 든 경로는 JSON으로 읽는다.
조회는 파일·실행 기록을 만들거나 바꾸지 않고 모델이나 외부 서비스를 호출하지 않는다. 같은 저장 자료와 같은 Git 상태의 조회는 같은 결과를 낸다.
9. 영수증과 오류
성공한 등록은 decision-receipt/v1 JSON 한 줄을 출력한다: id, request_id, seq, sha256, replayed. 재전송이면 원래 사건의 영수증을 replayed: true로 돌려주고 아무것도 덧붙이지 않는다.
| 종료 | 코드 | 의미 |
|---|---|---|
| 0 | — | 성공, 재전송, 이력 부재를 포함한 조회 |
| 64 | invalid_input |
인수·종류·주장 상태·형식·식별자·필드 오류 |
| 64 | limit_exceeded |
문서화된 입력 상한 초과(파일 근거의 too_large 관측과 구분) |
| 65 | invalid_reference |
없는 대상, 없는 실행 기록 줄, 맞지 않는 해시 |
| 65 | invalid_relation |
관계가 앞선 저장 사건을 가리키지 않음 |
| 65 | correction_conflict |
같은 판단의 다른 정정이 먼저 저장됨 |
| 65 | request_conflict |
같은 요청 식별자가 다른 내용을 가리킴 |
| 65 | not_started, run_record_broken |
시작 기록 없음, 등록 전 실행 기록 체인 손상 |
| 65 | history_corrupt |
저장된 이력의 손상(잘린 줄, 수정·삭제, 끝에서 사라진 사건, 해시 연결·순번·관계 위반, last와의 불일치, 상한 초과 줄) |
| 65 | unknown_event, cursor_invalid, cursor_stale, cursor_mismatch |
조회 대상·커서 오류 |
| 66 | history_unsupported |
이 실행 파일이 읽지 못하는 schema, 또는 지원하지 않는 시작 규칙 |
| 70 | history_unreadable |
저장소 읽기·권한·경로 오류, 등록과 조회 모두에서 symlink 등 비정상 저장 경로 |
| 70 | write_uncertain |
쓰기·동기화·닫기 실패로 적용 여부를 알 수 없음, 또는 사건은 저장했지만 last를 갱신하지 못함(오류가 저장한 사건 ID를 알려 준다) |
거부는 이력을 바꾸지 않는다. 손상·미지원 이력은 정상 전체 이력으로 보이거나 빈 성공 결과로 바뀌지 않으며, 조회와 추가 등록이 같은 오류로 멈춘다. 자동으로 잘라내거나 복구하지 않는다. write_uncertain은 성공이나 미적용으로 단정하지 않는다. 오류가 알려 주는 요청 식별자로 show --request-id를 조회하거나 같은 요청을 같은 식별자로 다시 보내 적용 여부를 확인한다. 손상 때문에 확인할 수 없으면 그 한계가 오류로 드러난다.
schema 필드의 누락·null·빈 값·잘못된 타입, 최상위 객체의 중복 키와 schema 키의 대소문자 변형은 미지원 버전이 아니라 history_corrupt로 구분한다. 올바른 schema 식별자가 미지원 버전을 가리키면 그 버전의 본문에 현재 버전의 필드·중첩 규칙을 적용하지 않고 history_unsupported로 알린다.
미지원 시작 규칙과 미지원 이력 schema는 입력 자체가 유효한 add·같은 요청의 재전송·기본/current·list·사건 ID 또는 요청 식별자로 하는 show 모두에서 종료 66과 history_unsupported: 설명문으로 알린다. 기존 사건·실행 기록·자료를 변경하거나 새 이력 저장소를 만들지 않는다. 이력 부재를 미지원의 빈 성공 결과로 대신하지 않는다.
10. 실행 묶음과의 관계
판단 이력은 상세 이유·근거·관계를 한 번 담는 보조 기록이다. 목표 문서, PLAN·REVIEW·Task의 현재 계획과 검토, PROGRESS의 현재 상태와 Task 상태의 소유권과 판정 의미는 그대로다. Seal 0.1.13 이상으로 시작한 목표의 PROGRESS 역할은 실행 묶음 §5.3에 있다. 기능을 쓰지 않거나 쓸 수 없는 환경에서도 인계는 기존 Markdown만으로 가능해야 한다. 이력은 실행자가 기록할 사건과 시점을 선택하는 선택 기능이며, 이 기능만의 새 사람 승인 절차나 고정 작업 절차를 만들지 않는다.
11. 합성 예시
아래는 형식 설명용 합성 예시다. 실제 목표나 대화를 나타내지 않는다. docs/specs/empty-input은 AC-1과 T001이 있고 ha start와 ha check(seq 2)를 마친 가상 목표다.
ha history docs/specs/empty-input add --kind 원인 --status 가설 --summary "첫 항목이 항상 있다고 가정했다" --target AC-1 --ref run:2 --request-id cause-1
ha history docs/specs/empty-input add --kind 관측 --summary "빈 파일에서 오류가 재현된다" --target AC-1 --checked-ref file:src/count.go
ha history docs/specs/empty-input add --kind 결정 --summary "빈 입력이면 0을 반환한다" --target T001 --builds-on D1 --builds-on D2
ha history docs/specs/empty-input add --kind 원인 --status 기각 --summary "첫 항목 가정이 아니라 입력 버퍼 경계가 원인이다" --target AC-1 --supersedes D1
ha history docs/specs/empty-input current
ha history docs/specs/empty-input show D3
네 번의 등록은 D1~D4의 영수증을 낸다. current 결과에서 대체된 D1은 빠지고 아래 항목이 남는다. D3은 근거가 정정되었다는 표시를 달고 현재 판단으로 남는다.
- D2 · 관측 · AC-1 · 빈 파일에서 오류가 재현된다
- D3 · 결정 · T001 · 빈 입력이면 0을 반환한다 (linked grounds D1 corrected by D4)
- D4 · 원인 [기각] · AC-1 · 첫 항목 가정이 아니라 입력 버퍼 경계가 원인이다
show D3은 직접 선행 사건 두 개와 근거 정정 표시를 보인다.
- builds_on D1 · 원인 [가설] · 첫 항목이 항상 있다고 가정했다 (lineage same)
- builds_on D2 · 관측 · 빈 파일에서 오류가 재현된다 (lineage same, current)
- markers: linked grounds D1 corrected by D4
이미 정정된 근거를 사용하는 경우에도 정보를 유지한다. 별도의 가상 이력에서 D2가 D1을 정정한 다음 D3이 --builds-on D1로 등록되면 D3에는 linked grounds D1 corrected by D2가 표시된다. 한 사건이 --builds-on D1 --supersedes D1을 함께 가져도 그 사건의 근거 정정 정보에 자기 ID가 포함된다. 두 경우 모두 뒤에 정정됐다고 단정하지 않으며 JSON의 grounds_corrected와 같은 사건을 가리킨다.
12. 보증 한계
판단 이력의 보증 수준은 local이다. 같은 사용자 권한의 실행자는 파일을 고칠 수 있으며, 해시 연결과 last는 사고를 드러낼 뿐 위조를 막지 않는다. 이력 디렉토리 전체를 지우거나 이전 사본으로 되돌린 경우는 드러나지 않는다. 사건은 실행자의 주장이며 Agent Wiki, 자동 전송, 미래 모델의 성능 향상을 구현하거나 보장하지 않는다. 토큰 절감·재개 시간·오판 감소 같은 효과는 이 기능의 완료로 주장하지 않는다.
contracts/decision-history.md만든 커밋 bcaddb7603e8