Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,11 @@ jobs:
- name: Every test file actually ran
run: node scripts/check-test-files-ran.mjs vitest-report.json

# Fixture conformance, README example sync, and the README vocabulary
# table vs SPEC section 3. Documentation claims that can be checked by
# machine are checked by machine.
# Fixture conformance, the docs/protocol.md example sync, and that
# document's vocabulary table vs SPEC section 3. Documentation claims that
# can be checked by machine are checked by machine. The owner is one file
# rather than four READMEs: the same fixture repeated per translation is
# four places for it to drift.
- name: Conformance suite
run: bash spec/verify.sh

Expand Down
42 changes: 5 additions & 37 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,50 +406,18 @@ lifecycle — `Supersedes:` と `Expires:`、そして上の表の最終行を

エージェント向けの指針は `skills/commitlore-commits/` にあり、commit-msg hook はエージェントが追加した record を検証するだけで、record を発明したり黙って追加したりしません。`harvest` の経路、`capture` トランザクション、そして人が trailer を手書きする逃げ道は、いずれも [docs/capture.md](docs/capture.md) にあります。

## 完全な record
## record プロトコル

record はこれよりずっと小さくできますし、ほとんどは数個の field で足ります。この例が語彙のすべてを使うのは、conformance fixture でもあるからです — Git trailer parser は、すべての翻訳 README で下の code block を同じように読みます。
record は普通の Git commit trailer の集まりで、たいていは小さいもので足ります:

```text
Prevent silent session drops during long-running operations
Fix expired-token refresh

The auth service returns inconsistent status codes on token
expiry, so the interceptor catches all 4xx responses and
triggers an inline refresh.

Limit: Auth service does not support token introspection
Record-Id: r-4b7e21
Ruled-out: Extend token TTL to 24h | security policy violation
Ruled-out: Background refresh on timer | race condition
Certainty: firm
Blast: module
Undo: easy
Warn: 4xx handling is intentionally broad
-- do not narrow without verifying upstream behavior
Verified: Single expired token refresh (unit)
Unverified: Auth service cold-start > 500ms behavior
CommitLore-Version: 2.0.0
Warn: Do not narrow the 4xx handler without verifying upstream behavior
```

### Protocol vocabulary

| Trailer | Meaning |
|---|---|
| `Limit:` | External condition that constrained the decision |
| `Record-Id:` | Stable identity across rewritten commit hashes |
| `Ruled-out:` | `alternative \| reason` — the first `\|` separates; there is no escape, so an alternative may not contain one |
| `Certainty:` | `firm` \| `tentative` \| `guess` |
| `Blast:` | `local` \| `module` \| `system` |
| `Undo:` | `easy` \| `costly` \| `permanent` |
| `Warn:` | Warning for a future modifier; trust-graded before delivery |
| `Verified:` / `Unverified:` | What was and was not checked |
| `Follows:` / `Supersedes:` | Decision-chain and lifecycle links |
| `Expires:` | Date or condition that ends a limit |
| `Evidence:` | Path, anchor, or URL supporting a claim |
| `Provenance:` | `authored` \| `inherited <sha>` \| `reconstructed` |
| `CommitLore-Version:` / `X-*:` | Protocol identity and extensions |

path の履歴は `commitlore context <path>` で読みます。より小さな例と、Git だけで record を読む方法は [docs/protocol.md](docs/protocol.md) に、規範的な定義は [SPEC §3](spec/SPEC.md) にあります。
語彙をすべて使う完全な例、すべての trailer key の表、そして CommitLore なしで素の Git から読む方法は [docs/protocol.md](docs/protocol.md) にあります。規範的な定義は [SPEC §3](spec/SPEC.md) です。

## リポジトリが証明すること

Expand Down
42 changes: 5 additions & 37 deletions README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -400,50 +400,18 @@ lifecycle — `Supersedes:`와 `Expires:`, 그리고 위 표의 마지막 행을

에이전트 지침은 `skills/commitlore-commits/`에 있고, commit-msg hook은 에이전트가 추가한 record를 검증할 뿐 record를 발명하거나 조용히 추가하지 않는다. `harvest` 경로, `capture` 트랜잭션, 그리고 사람이 trailer를 직접 쓰는 탈출구는 모두 [docs/capture.md](docs/capture.md)에 있다.

## 완전한 record
## record 프로토콜

record는 이보다 훨씬 작을 수 있고, 대부분은 몇 개 field면 충분하다. 이 예시가 어휘 전체를 쓰는 이유는 conformance fixture이기도 하기 때문이다 — Git trailer parser는 모든 번역 README에서 아래 code block을 동일하게 읽는다.
record는 평범한 Git commit trailer 묶음이고, 대개는 작은 것으로 충분합니다:

```text
Prevent silent session drops during long-running operations
Fix expired-token refresh

The auth service returns inconsistent status codes on token
expiry, so the interceptor catches all 4xx responses and
triggers an inline refresh.

Limit: Auth service does not support token introspection
Record-Id: r-4b7e21
Ruled-out: Extend token TTL to 24h | security policy violation
Ruled-out: Background refresh on timer | race condition
Certainty: firm
Blast: module
Undo: easy
Warn: 4xx handling is intentionally broad
-- do not narrow without verifying upstream behavior
Verified: Single expired token refresh (unit)
Unverified: Auth service cold-start > 500ms behavior
CommitLore-Version: 2.0.0
Warn: Do not narrow the 4xx handler without verifying upstream behavior
```

### 프로토콜 어휘

| Trailer | Meaning |
|---|---|
| `Limit:` | External condition that constrained the decision |
| `Record-Id:` | Stable identity across rewritten commit hashes |
| `Ruled-out:` | `alternative \| reason` — the first `\|` separates; there is no escape, so an alternative may not contain one |
| `Certainty:` | `firm` \| `tentative` \| `guess` |
| `Blast:` | `local` \| `module` \| `system` |
| `Undo:` | `easy` \| `costly` \| `permanent` |
| `Warn:` | Warning for a future modifier; trust-graded before delivery |
| `Verified:` / `Unverified:` | What was and was not checked |
| `Follows:` / `Supersedes:` | Decision-chain and lifecycle links |
| `Expires:` | Date or condition that ends a limit |
| `Evidence:` | Path, anchor, or URL supporting a claim |
| `Provenance:` | `authored` \| `inherited <sha>` \| `reconstructed` |
| `CommitLore-Version:` / `X-*:` | Protocol identity and extensions |

경로의 이력은 `commitlore context <path>`로 읽는다. 더 작은 예시와 Git만으로 record를 읽는 방법은 [docs/protocol.md](docs/protocol.md)에, 규범적 정의는 [SPEC §3](spec/SPEC.md)에 있다.
어휘 전체를 쓰는 완전한 예제, 모든 trailer key 표, 그리고 CommitLore 없이 순수 Git으로 읽는 방법은 [docs/protocol.md](docs/protocol.md)에 있습니다. 규범적 정의는 [SPEC §3](spec/SPEC.md)입니다.

## 저장소가 증명하는 것

Expand Down
42 changes: 5 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -501,50 +501,18 @@ Ask the agent to commit normally and preserve only the decision context the diff

The agent instructions live in `skills/commitlore-commits/`, and the commit-msg hook validates any record the agent adds — it never invents or silently adds one. The `harvest` route, the `capture` transaction, and the escape hatch of writing trailers by hand are all in [docs/capture.md](docs/capture.md).

## A complete record
## The record protocol

A record can be much smaller than this; most need only a few fields. This one uses the whole vocabulary because it is also a conformance fixture — Git's trailer parser reads the code block identically in every translated README.
A record is an ordinary set of Git commit trailers, and a small one is usually enough:

```text
Prevent silent session drops during long-running operations
Fix expired-token refresh

The auth service returns inconsistent status codes on token
expiry, so the interceptor catches all 4xx responses and
triggers an inline refresh.

Limit: Auth service does not support token introspection
Record-Id: r-4b7e21
Ruled-out: Extend token TTL to 24h | security policy violation
Ruled-out: Background refresh on timer | race condition
Certainty: firm
Blast: module
Undo: easy
Warn: 4xx handling is intentionally broad
-- do not narrow without verifying upstream behavior
Verified: Single expired token refresh (unit)
Unverified: Auth service cold-start > 500ms behavior
CommitLore-Version: 2.0.0
Warn: Do not narrow the 4xx handler without verifying upstream behavior
```

### Protocol vocabulary

| Trailer | Meaning |
|---|---|
| `Limit:` | External condition that constrained the decision |
| `Record-Id:` | Stable identity across rewritten commit hashes |
| `Ruled-out:` | `alternative \| reason` — the first `\|` separates; there is no escape, so an alternative may not contain one |
| `Certainty:` | `firm` \| `tentative` \| `guess` |
| `Blast:` | `local` \| `module` \| `system` |
| `Undo:` | `easy` \| `costly` \| `permanent` |
| `Warn:` | Warning for a future modifier; trust-graded before delivery |
| `Verified:` / `Unverified:` | What was and was not checked |
| `Follows:` / `Supersedes:` | Decision-chain and lifecycle links |
| `Expires:` | Date or condition that ends a limit |
| `Evidence:` | Path, anchor, or URL supporting a claim |
| `Provenance:` | `authored` \| `drafted` \| `inherited <sha>` \| `reconstructed` \| `unknown` |
| `CommitLore-Version:` / `X-*:` | Protocol identity and extensions |

Read a path's history with `commitlore context <path>`. Smaller examples, and how to read records with plain Git instead, are in [docs/protocol.md](docs/protocol.md); the normative definitions are in [SPEC §3](spec/SPEC.md).
The worked example that uses the whole vocabulary, the table of every trailer key, and how to read records with plain Git are in [docs/protocol.md](docs/protocol.md). The normative definitions are in [SPEC §3](spec/SPEC.md).

## What the repository proves

Expand Down
42 changes: 5 additions & 37 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,50 +386,18 @@ warnings

代理说明位于 `skills/commitlore-commits/`,commit-msg hook 只验证代理添加的 record,不会凭空创建或静默添加。`harvest` 路径、`capture` 事务,以及手写 trailer 这条逃生出口,都在 [docs/capture.md](docs/capture.md)。

## 完整 record
## record 协议

record 可以比这小得多,绝大多数只需要几个 field。这个示例用上全部词汇,是因为它同时也是 conformance fixture —— Git trailer parser 会在所有翻译版 README 中以相同方式读取下面的 code block。
record 就是一组普通的 Git commit trailer,通常小的就够用:

```text
Prevent silent session drops during long-running operations
Fix expired-token refresh

The auth service returns inconsistent status codes on token
expiry, so the interceptor catches all 4xx responses and
triggers an inline refresh.

Limit: Auth service does not support token introspection
Record-Id: r-4b7e21
Ruled-out: Extend token TTL to 24h | security policy violation
Ruled-out: Background refresh on timer | race condition
Certainty: firm
Blast: module
Undo: easy
Warn: 4xx handling is intentionally broad
-- do not narrow without verifying upstream behavior
Verified: Single expired token refresh (unit)
Unverified: Auth service cold-start > 500ms behavior
CommitLore-Version: 2.0.0
Warn: Do not narrow the 4xx handler without verifying upstream behavior
```

### Protocol vocabulary

| Trailer | Meaning |
|---|---|
| `Limit:` | External condition that constrained the decision |
| `Record-Id:` | Stable identity across rewritten commit hashes |
| `Ruled-out:` | `alternative \| reason` — the first `\|` separates; there is no escape, so an alternative may not contain one |
| `Certainty:` | `firm` \| `tentative` \| `guess` |
| `Blast:` | `local` \| `module` \| `system` |
| `Undo:` | `easy` \| `costly` \| `permanent` |
| `Warn:` | Warning for a future modifier; trust-graded before delivery |
| `Verified:` / `Unverified:` | What was and was not checked |
| `Follows:` / `Supersedes:` | Decision-chain and lifecycle links |
| `Expires:` | Date or condition that ends a limit |
| `Evidence:` | Path, anchor, or URL supporting a claim |
| `Provenance:` | `authored` \| `inherited <sha>` \| `reconstructed` |
| `CommitLore-Version:` / `X-*:` | Protocol identity and extensions |

用 `commitlore context <path>` 读取 path 的历史。更小的示例,以及只用 Git 读取 record 的方法,在 [docs/protocol.md](docs/protocol.md);规范定义在 [SPEC §3](spec/SPEC.md)。
使用全部语汇的完整示例、所有 trailer key 的表格,以及不用 CommitLore 直接用 Git 读取的方法,都在 [docs/protocol.md](docs/protocol.md)。规范性定义见 [SPEC §3](spec/SPEC.md)。

## 仓库能够证明什么

Expand Down
63 changes: 54 additions & 9 deletions docs/protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,64 @@ verification gap. How a record gets written is in [capture.md](capture.md).

## A complete record

The worked example that uses the full vocabulary stays in the README, in every
language, because it is also a conformance fixture: `spec/verify.sh` compares it
byte for byte with
A record can be much smaller than this; most need only a few fields. This one
uses the whole vocabulary because it is also a conformance fixture: `spec/verify.sh`
compares the block below byte for byte with
[`spec/fixtures/valid/11-readme-example.txt`](../spec/fixtures/valid/11-readme-example.txt)
and fails if the two drift apart. See [A complete record](../README.md).
and fails if the two drift apart. A document that violated the spec it teaches
would be this project's most expensive defect, and it has happened —
`Certainty: high`, `Blast: narrow` and `Undo: clean` shipped here once, and all
three are values our own rejection fixtures carry.

The marker below is what the checker finds. It is deliberately not "the last
`text` block in the file": a positional rule cannot say which block it owns, so
editing anything nearby silently moves the contract.

<!-- SPEC-FIXTURE:11-readme-example -->

```text
Prevent silent session drops during long-running operations

The auth service returns inconsistent status codes on token
expiry, so the interceptor catches all 4xx responses and
triggers an inline refresh.

Limit: Auth service does not support token introspection
Record-Id: r-4b7e21
Ruled-out: Extend token TTL to 24h | security policy violation
Ruled-out: Background refresh on timer | race condition
Certainty: firm
Blast: module
Undo: easy
Warn: 4xx handling is intentionally broad
-- do not narrow without verifying upstream behavior
Verified: Single expired token refresh (unit)
Unverified: Auth service cold-start > 500ms behavior
CommitLore-Version: 2.0.0
```

## The vocabulary

The summary table of every trailer key also stays in the README, in every
language, and is checked against SPEC §3 by
`spec/schema/readme-vocab-check.mjs`: a key in the table that the spec does not
define is a key a user would write and the validator would reject, and a key the
spec defines but the table omits is a field that effectively does not exist.
Checked against SPEC §3 by `spec/schema/protocol-doc-vocab-check.mjs`: a key in
this table that the spec does not define is a key a user would write and the
validator would reject (`Decision-Id:` actually did that), and a key the spec
defines but this table omits is a field that effectively does not exist.

| Trailer | Meaning |
|---|---|
| `Limit:` | External condition that constrained the decision |
| `Record-Id:` | Stable identity across rewritten commit hashes |
| `Ruled-out:` | `alternative \| reason` — the first `\|` separates; there is no escape, so an alternative may not contain one |
| `Certainty:` | `firm` \| `tentative` \| `guess` |
| `Blast:` | `local` \| `module` \| `system` |
| `Undo:` | `easy` \| `costly` \| `permanent` |
| `Warn:` | Warning for a future modifier; trust-graded before delivery |
| `Verified:` / `Unverified:` | What was and was not checked |
| `Follows:` / `Supersedes:` | Decision-chain and lifecycle links |
| `Expires:` | Date or condition that ends a limit |
| `Evidence:` | Path, anchor, or URL supporting a claim |
| `Provenance:` | `authored` \| `drafted` \| `inherited <sha>` \| `reconstructed` \| `unknown` |
| `CommitLore-Version:` / `X-*:` | Protocol identity and extensions |

For the normative meaning, cardinality, and value grammar of each key, read
[SPEC §3](../spec/SPEC.md).
Expand Down
2 changes: 1 addition & 1 deletion scripts/check-exact-head-ci.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const CI_WORKFLOW_FILE_PATH = fileURLToPath(new URL(`../${CI_WORKFLOW_PATH}`, im
// shell command; without this lock replacing every job body with `true` would
// still look like a real successful run. Update deliberately with the CI
// workflow when its reviewed job contract changes.
export const EXPECTED_CI_WORKFLOW_SHA256 = 'f84cd550b55320c433cf53656b27eeb38f906f040795a39862c09f32b47bb4e5';
export const EXPECTED_CI_WORKFLOW_SHA256 = 'b3cf70f2017579462071980215dcd5ee0a65702a05dde409e25b3a18113751bc';

// Fixed rather than inferred from returned jobs: absence must fail rather
// than define itself away. `lint` only runs for pull requests and is therefore
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
#!/usr/bin/env node
// README의 어휘표가 SPEC §3과 일치하는지 검사한다.
// docs/protocol.md 의 어휘표가 SPEC §3과 일치하는지 검사한다.
//
// 이 프로젝트에서 README는 마케팅 문서가 아니라 스펙의 요약본이다. 표에 스펙에 없는
// 표는 사용자가 실제로 보고 쓰는 요약본이다. 표에 스펙에 없는
// 키가 실리면 사용자가 그걸 쓰고 검증기에 거부당한다(`Decision-Id:` 가 실제로 그랬다).
// 반대로 키가 빠지면 그 필드는 존재하지 않는 것과 같다.
//
// 사용: node spec/schema/readme-vocab-check.mjs <SPEC.md> <README.md> [README.*.md ...]
// 소유자는 네 개의 README가 아니라 docs/protocol.md 하나다 — 같은 표를 네 번
// 반복하면 번역본마다 드리프트 지점이 하나씩 생긴다.
//
// 사용: node spec/schema/protocol-doc-vocab-check.mjs <SPEC.md> <docs/protocol.md>

import fs from 'node:fs';

const [specPath, ...readmes] = process.argv.slice(2);
if (!specPath || readmes.length === 0) {
console.error('usage: readme-vocab-check.mjs <SPEC.md> <README.md> [more...]');
const [specPath, ...docs] = process.argv.slice(2);
if (!specPath || docs.length === 0) {
console.error('usage: protocol-doc-vocab-check.mjs <SPEC.md> <docs/protocol.md>');
process.exit(2);
}

Expand All @@ -36,8 +39,8 @@ if (specKeys.size === 0) {

let failed = false;

for (const readmePath of readmes) {
const md = fs.readFileSync(readmePath, 'utf8');
for (const docPath of docs) {
const md = fs.readFileSync(docPath, 'utf8');
// 어휘표 행에서만 키를 뽑는다. 산문 속 백틱 언급(`feat:` 등)은 세지 않는다.
const found = new Set();
for (const m of md.matchAll(/^\|\s*`([A-Za-z][A-Za-z0-9-]*):`/gm)) found.add(m[1]);
Expand All @@ -52,11 +55,11 @@ for (const readmePath of readmes) {

if (missing.length || extra.length) {
failed = true;
console.error(`${readmePath}:`);
if (missing.length) console.error(` SPEC에 있으나 README 표에 없음: ${missing.join(', ')}`);
if (extra.length) console.error(` README 표에 있으나 SPEC에 없음: ${extra.join(', ')}`);
console.error(`${docPath}:`);
if (missing.length) console.error(` SPEC에 있으나 표에 없음: ${missing.join(', ')}`);
if (extra.length) console.error(` 표에 있으나 SPEC에 없음: ${extra.join(', ')}`);
}
}

if (failed) process.exit(1);
console.log(`vocab table matches SPEC (${specKeys.size} keys × ${readmes.length} READMEs)`);
console.log(`vocab table matches SPEC (${specKeys.size} keys, owner: ${docs.join(', ')})`);
Loading
Loading