# OATH Protocol Specification

| | |
|---|---|
| **Version** | 0.1.0 |
| **Status** | Draft |
| **License** | Apache-2.0 |
| **Canonical home** | https://oath.sh/spec |

## Abstract

OATH (Open Agent Transparency & Honesty) is an open protocol for AI agent
accountability. An agent publicly takes an **oath**: a versioned document of
numbered, falsifiable laws. From then on its actions are **adjudicated**
against those laws, every verdict citing the law by number, and every record
is appended to a tamper-evident, append-only, publicly verifiable ledger.
Violations and uncertainty must be **confessed** by contract. Over time the
record yields a published-formula **honesty index** that no one — including
the ledger operator — can quietly edit.

Every claim this protocol makes about an agent is checkable by a stranger
with no trust in the agent, the operator, or the authors of this document.
Anything that cannot be verified independently is out of scope.

Machines may fail. They may not hide it.

---

## 1. Terminology

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
"OPTIONAL" in this document are to be interpreted as described in BCP 14
[RFC 2119] [RFC 8174] when, and only when, they appear in all capitals.

- **Agent** — a software system that takes an oath. Identified by a
  *handle* and an Ed25519 public key (§2.3).
- **Oath** — a versioned document binding an agent to numbered laws (§2).
- **Law** — one numbered, falsifiable rule inside an oath.
- **Action** — something the agent did, recorded in summary and by content
  hash inside an entry.
- **Entry** — the atomic record: an action, its verdict, the law cited, and
  any confession, canonically serialized and signed by the agent (§3).
- **Verdict** — `compliant`, `violation`, or `uncertain`. There is no
  silent failure state in this protocol (§3.3).
- **Confession** — the agent's own declaration, inside the entry, of a
  violation, an uncertainty, or an invention (§8).
- **Adjudication** — the process that produces a verdict, either
  machine-checkable (Mode A) or model-adjudicated (Mode B) (§7).
- **Ledger** — an append-only Merkle log of entries (§4). May be *local*
  (files on disk, operated by the agent) or *public* (a service accepting
  submissions from many agents).
- **Signed Tree Head (STH)** — a signed statement of the ledger's size and
  root hash at a moment in time (§5).
- **Inclusion proof / consistency proof** — Merkle proofs that an entry is
  in the log, and that the log never rewrote history (§6).
- **Receipt** — a public ledger's signed promise to incorporate a
  submitted entry within its declared merge delay (§11.3).
- **Index** — the honesty index: a published-formula score computed purely
  from ledger contents (§10).
- **Monitor** — anyone verifying a ledger. The reference monitor is the
  `oath verify` command-line tool.
- **Operator** — whoever runs a ledger. An agent is the operator of its own
  local ledger.
- **Oathbreaker** — a terminal status assigned to a record on cryptographic
  proof of tampering (§6.3). Never assigned on judgment or accusation alone.

### 1.2 Conventions and encodings

- **Canonical serialization** is JSON Canonicalization Scheme (JCS)
  [RFC 8785]. Wherever this document says "canonical bytes" it means the
  UTF-8 bytes of the JCS serialization.
- **Hash** is SHA-256 [FIPS 180-4]. Hashes are written as 64 lowercase hex
  characters.
- **Signatures** are Ed25519 [RFC 8032]. Public keys are the raw 32-byte
  key as 64 lowercase hex characters; signatures are the raw 64-byte
  signature as 128 lowercase hex characters.
- **Timestamps** are RFC 3339 UTC with the `Z` suffix, e.g.
  `2026-07-13T00:00:05Z`. Sub-second precision is OPTIONAL.
- **Handles** match `^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$` (lowercase
  alphanumeric and hyphens, 1–64 characters, no leading/trailing hyphen).
- Unknown JSON fields MUST be rejected by validators of the structures
  defined in this specification. Extensibility happens through spec
  versions (§12), not through silent extra fields.

### 1.3 What OATH guarantees — and what it does not

OATH makes tampering *evident*, not impossible, and makes honesty
*measurable*, not automatic. Verifiable guarantees: a logged entry cannot
be altered, reordered, or deleted without detection; a verdict cannot be
rewritten after the fact; a confession cannot be quietly withdrawn.
Not guaranteed: that an agent logs everything it does. An agent that omits
actions is not caught by cryptography; it is caught by scope declarations,
coverage measurement (§10), and monitors comparing the ledger against the
agent's observable behavior. This specification states this limit plainly
because the protocol's authority depends on never overclaiming.

---

## 2. The Oath Document

### 2.1 Two forms

An oath exists in two forms:

- `oath.md` — the human-readable document. Presentation only.
- `oath.json` — the canonical machine form. **Normative.** All hashes and
  signatures are computed over this form.

If the two disagree, the canonical JSON governs.

### 2.2 Canonical form

```json
{
  "spec_version": "0.1",
  "agent": "thm",
  "display_name": "The Honest Machine",
  "version": 1,
  "pubkey": "<64 hex chars: raw Ed25519 public key>",
  "scope": "Plain-language declaration of which actions this agent logs.",
  "commitments": { "entries_per_30d": 30, "max_latency_seconds": 300 },
  "laws": [
    { "n": 1, "mode": "machine", "text": "..." },
    { "n": 2, "mode": "judgment", "text": "..." }
  ],
  "created_at": "2026-07-13T00:00:00Z",
  "signature": "<128 hex chars>"
}
```

Field rules:

- `spec_version` — the MAJOR.MINOR of this specification (§12). REQUIRED.
- `agent` — the handle. REQUIRED.
- `display_name` — OPTIONAL.
- `version` — integer ≥ 1, starting at 1, incremented only by amendment
  (§9). REQUIRED.
- `pubkey` — the agent's Ed25519 public key. REQUIRED.
- `scope` — a plain-language, non-empty declaration of which of the agent's
  actions produce entries. REQUIRED. Coverage (§10) is measured against it.
- `commitments` — OPTIONAL. `entries_per_30d` (integer ≥ 1): the expected
  entry count per trailing 30 days, used for coverage. `max_latency_seconds`
  (integer ≥ 1): the maximum delay the agent commits to between an action
  and its entry, used for promptness (§8.2). When absent,
  `max_latency_seconds` defaults to 300.
- `laws` — a non-empty array. `n` MUST run 1..N consecutively. `text` MUST
  be plain language and falsifiable: a reader can say what conduct would
  violate it. `mode` is `machine` (Mode A) or `judgment` (Mode B) per §7.
  Laws SHOULD be machine-checkable wherever possible. REQUIRED.
- `created_at` — REQUIRED.
- `signature` — Ed25519 signature over the canonical bytes of the document
  *without* the `signature` field. REQUIRED. For version 1 the signing key
  is the document's own `pubkey`; for an amendment it is the previous
  version's key (§9), which is how keys rotate while the document carries
  the new `pubkey`. It MUST verify against the applicable key.

The **oath hash** is the SHA-256 of the canonical bytes of the complete
signed document (including `signature`). It identifies the oath version
everywhere else in the protocol.

### 2.3 Identity

An agent's identity is the pair (handle, public key). The key is
authoritative; the handle is a name. A public ledger MUST enforce handle
uniqueness within itself and MUST bind a handle to the key that first swore
it. Key rotation is an amendment (§9): a new oath version carrying the new
`pubkey`, signed by the *previous* version's key.

### 2.4 Swearing

An agent is **sworn** when its oath document is logged: the first entry for
an agent MUST have `action.type` = `oath.swear` and `action.content_hash`
equal to the oath hash of version 1 (§3.4). A ledger accepting a swear MUST
verify the oath document's signature before appending. The oath document
itself MUST be retained and served by the ledger (§11.2), so that the tree
commits to the laws from the first leaf onward.

---

## 3. Entries and Canonical Serialization

### 3.1 Canonical form

```json
{
  "spec_version": "0.1",
  "agent": "thm",
  "oath_version": 1,
  "action": {
    "type": "cycle.publish",
    "summary": "One or two sentences describing what was done.",
    "content_hash": "<64 hex chars or null>",
    "occurred_at": "2026-07-13T00:00:00Z"
  },
  "verdict": "violation",
  "law_cited": 3,
  "adjudication": { "mode": "machine", "check": "law3.grade-freeze" },
  "confession": { "note": "Grade for H-041 was edited 12s after publication." },
  "timestamp": "2026-07-13T00:00:05Z",
  "signature": "<128 hex chars>"
}
```

Field rules:

- `spec_version`, `agent`, `oath_version` — REQUIRED. `oath_version` is the
  oath version in force when the action occurred.
- `action.type` — a dotted lowercase identifier, agent-defined, REQUIRED.
  Types beginning `oath.` are reserved by this specification (§3.4).
- `action.summary` — plain language, non-empty, REQUIRED. SHOULD be at most
  1000 characters.
- `action.content_hash` — SHA-256 of the full content of the action (the
  published artifact, the tool call and result, the document), or `null`
  when no content exists beyond the summary. SHOULD be present. Content MAY
  be private; publishing its hash is a commitment that permits later
  disclosure and verification.
- `action.occurred_at` — when the action happened. REQUIRED.
- `verdict` — `compliant` | `violation` | `uncertain`. REQUIRED (§3.3).
- `law_cited` — the law number, or `null` (§3.3).
- `adjudication` — how the verdict was produced (§7). REQUIRED.
- `confession` — REQUIRED when `verdict` is `violation` or `uncertain`;
  OPTIONAL when `compliant` (an agent MAY confess an invention or
  assumption on a compliant action). See §8.
- `timestamp` — when the entry was authored. REQUIRED.
- `signature` — Ed25519 signature by the agent's key over the canonical
  bytes of the entry *without* the `signature` field. REQUIRED.

### 3.2 Leaf bytes

The **leaf bytes** of an entry are the canonical bytes of the complete
signed entry (including `signature`). These exact bytes are what the Merkle
tree commits to (§4) and what ledgers store and serve. The same entry has
the same leaf bytes — and therefore the same leaf hash — in every ledger it
appears in; leaf indices are per-ledger.

An entry whose signature does not verify against the agent's sworn key, or
which violates any rule in §3.1 or §3.3, is malformed. A ledger MUST reject
malformed entries and MUST NOT modify entries to repair them.

### 3.3 Verdict rules

- `violation` — `law_cited` MUST identify the violated law, and
  `confession` is REQUIRED.
- `uncertain` — `confession` is REQUIRED. `law_cited` SHOULD identify the
  law in question and MAY be `null`.
- `compliant` — `law_cited` MAY identify the governing law or be `null`.

An agent that cannot determine whether an action complied MUST log
`uncertain` with a confession. Logging nothing is a coverage failure;
logging `compliant` without basis is a lie that adjudication records make
re-checkable (§7). There is no fourth verdict and no silent state.

### 3.4 Reserved action types

- `oath.swear` — logs oath version 1 (§2.4). `content_hash` MUST be the
  oath hash. MUST be the agent's first entry in the ledger.
- `oath.amend` — logs a new oath version (§9). `content_hash` MUST be the
  new version's oath hash.

Reserved entries carry verdicts like any other entry: they are adjudicated
against the oath's amendment and identity rules (Mode A).

---

## 4. Hashing and the Merkle Log

The ledger is an append-only Merkle tree over leaf bytes, in the style of
RFC 6962 (Certificate Transparency).

- Entries are assigned consecutive **leaf indices** 0, 1, 2, … in append
  order. A ledger MUST NOT reorder, mutate, or delete a leaf. Ever.
- **Leaf hash:** `SHA-256(0x00 || leaf_bytes)`.
- **Node hash:** `SHA-256(0x01 || left || right)`.

The Merkle Tree Hash (MTH) of a list of leaves D[n]:

- `MTH({}) = SHA-256("")`
  (= `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`)
- `MTH({d0}) = SHA-256(0x00 || d0)`
- `MTH(D[n]) = SHA-256(0x01 || MTH(D[0:k]) || MTH(D[k:n]))` where `k` is
  the largest power of two strictly less than `n`.

The `0x00`/`0x01` domain separation prevents leaf/node confusion attacks
and MUST be implemented exactly. This structure is identical to RFC 6962
§2.1, so its security analysis and proof algorithms apply directly.

Storage is not specified; the tree is defined by the ordered leaf bytes
alone. Any store that can reproduce the ordered leaves — Postgres, JSONL
files (Appendix A), anything — can serve a conforming ledger.

---

## 5. Signed Tree Heads

An STH is the ledger's signed assertion of its entire history at a point
in time:

```json
{
  "ledger": "oath.sh",
  "tree_size": 1042,
  "root_hash": "<64 hex chars>",
  "timestamp": "2026-07-13T00:00:10Z",
  "signature": "<128 hex chars>"
}
```

- `ledger` — the ledger's identifier: its DNS name for a public ledger, or
  an agent-chosen name for a local ledger. REQUIRED.
- `tree_size` — the number of leaves covered. REQUIRED.
- `root_hash` — `MTH(D[tree_size])`. REQUIRED.
- `signature` — by the **ledger key** over the canonical bytes of the STH
  without the `signature` field. REQUIRED. For a local ledger the agent's
  key is the ledger key.

Rules:

- A public ledger MUST emit a new STH within its declared merge delay
  (§11.3) of incorporating entries, and at least once every 24 hours even
  if the tree has not grown (a fresh timestamp over an unchanged tree is a
  liveness proof, not a violation).
- A local ledger SHOULD emit an STH at the end of every batch of appends,
  and MUST emit one before its contents are presented anywhere.
- Every STH a ledger has ever emitted MUST be retained and served. The STH
  history is itself evidence: any two STHs from the same ledger MUST be
  consistent (§6.2), and a signed STH can never be disowned.

---

## 6. Proofs

### 6.1 Inclusion proofs

An inclusion proof demonstrates that a given leaf is in the tree described
by an STH. The audit path is computed exactly as `PATH(m, D[n])` in
RFC 6962 §2.1.1, and verified by recomputing the root from the leaf hash
and the path. A ledger MUST serve an inclusion proof for any leaf it holds,
against any STH it has emitted whose `tree_size` covers that leaf.

Proof object: `{ "leaf_index": m, "tree_size": n, "audit_path": [ ... ] }`
where `audit_path` is the ordered list of sibling hashes, lowest first.

### 6.2 Consistency proofs

A consistency proof demonstrates that the tree at size `n` is an
append-only extension of the tree at size `m` — that history was extended,
never rewritten. It is computed exactly as `PROOF(m, D[n])` in RFC 6962
§2.1.2. A ledger MUST serve a consistency proof between any two tree sizes
at which it has emitted STHs.

Proof object: `{ "old_size": m, "new_size": n, "path": [ ... ] }`.

### 6.3 Proof failure: oathbreaker

A verifier holding cryptographic proof of tampering MUST treat the record
as **oathbreaker** status. Exactly three proofs qualify:

1. Two signature-valid STHs from the same ledger key that fail a
   consistency proof (history was rewritten, or two views were shown to
   two audiences).
2. A signature-valid receipt (§11.3) whose entry is absent from every STH
   emitted after the receipt's merge delay elapsed.
3. A signature-valid entry whose leaf bytes differ from what an inclusion
   proof against a signed STH commits to.

Oathbreaker status is terminal and applies to the key that signed the
conflicting statements: the ledger key in cases 1–2, which for a local
ledger is the agent itself. The index of an oathbreaker record is void
(§10.4). Accusation, suspicion, or judgment never assign this status —
only proofs a stranger can re-check.

---

## 7. Adjudication

Every entry records how its verdict was produced, in the `adjudication`
field. Two modes exist. A verdict that cannot be re-derived by a third
party is not a verdict; it is an opinion, and it is non-conforming.

### 7.1 Mode A — machine-checkable

The verdict is the output of a deterministic check over the entry's action
record and declared context.

```json
{ "mode": "machine", "check": "law3.grade-freeze", "check_version": "1" }
```

- `check` — an identifier for the check, REQUIRED. `check_version`
  OPTIONAL, RECOMMENDED when checks evolve.
- The definition of the check (code or precise prose) SHOULD be published
  alongside the oath. Given the same inputs, anyone re-running the check
  MUST obtain the same verdict.

Mode A SHOULD be preferred. Laws SHOULD be written to be Mode-A checkable.

### 7.2 Mode B — model-adjudicated

The verdict is produced by a model applying a judgment law. Permitted,
under one hard rule: **re-computability**.

```json
{
  "mode": "model",
  "model": "<exact model identifier>",
  "prompt_hash": "<64 hex chars: SHA-256 of the adjudicator prompt>",
  "inputs_hash": "<64 hex chars: SHA-256 of the complete input record>"
}
```

- `model` — the exact model identifier used. REQUIRED.
- `prompt_hash` — SHA-256 of the full adjudicator prompt. REQUIRED. The
  prompt MUST be published.
- `inputs_hash` — SHA-256 of the complete, canonically serialized inputs
  given to the adjudicator (laws, action record, context). REQUIRED. The
  inputs MUST be available to verifiers, either published or disclosed on
  request under the agent's scope declaration.

Models are not deterministic; a re-run MAY disagree. The requirement is
not identical output — it is that anyone can re-run the same judgment on
the same inputs and publish disagreement. Hidden judgment is what this
mode forbids.

### 7.3 The adjudicator contract

A Mode B adjudicator prompt MUST instruct the model to: receive the
numbered laws, the canonical action record, and context; return exactly one
verdict from `compliant | violation | uncertain`; cite the governing law by
number or return null; judge the action, not the agent; say `uncertain`
when genuinely uncertain rather than guess; and output JSON only:
`{"verdict": "...", "law_cited": n | null, "reason": "one line"}`.

The reference adjudicator prompt is published at oath.sh and identified by
its hash.

### 7.4 Who adjudicates

In this version, adjudication ordinarily runs inside the agent's own
process (the SDK). This is self-adjudication, and the protocol does not
pretend otherwise: the trust does not come from the judge's independence
but from the record's permanence and re-computability. Any monitor can
re-judge any entry and publish a disagreement. Independent adjudication
services are an anticipated evolution, not a v0.1 requirement.

---

## 8. Confessions

### 8.1 Form

```json
{ "note": "Plain-language statement of what went wrong or is unknown." }
```

`note` MUST be non-empty plain language, specific enough that a reader
understands what happened without other context. Boilerplate ("an error
occurred") does not conform.

### 8.2 Timing

A confession exists to precede discovery. Therefore:

- The entry carrying a confession MUST be appended to the agent's ledger —
  and submitted to any public ledger the agent is connected to — **before
  the action's results are presented as complete** to any downstream
  consumer.
- An entry is **prompt** when `timestamp − action.occurred_at` is at most
  the oath's `max_latency_seconds` (§2.2; default 300). Promptness is
  measurable by anyone and feeds the index (§10).

### 8.3 Permanence

Confessions are entries; entries are permanent. A confession MUST NOT be
softened, amended, or contextualized by editing — only by *new* entries.
A correction is a new action with its own entry and its own verdict.

---

## 9. Amendments

An oath changes only **forward and in public**.

- An amendment is a complete new oath document with `version` incremented
  by exactly 1, signed by the key of the previous version (§2.3 — this is
  also how keys rotate).
- The amendment MUST be logged as an entry with `action.type` =
  `oath.amend`, `action.content_hash` = the new oath hash, and a `summary`
  stating what changed and why.
- Entries logged after the amendment cite the new version in
  `oath_version`; entries before it keep citing the old one. All prior
  versions remain in the ledger and remain served, forever.
- Laws MAY be added, reworded, or repealed by amendment. No amendment
  affects the adjudication of any entry logged before it.

Retroactive change is not forbidden so much as structurally impossible:
the tree commits to every oath version at the moment it was sworn, and a
rewrite is an oathbreaker proof (§6.3). That is the point of the tree.

---

## 10. The Index

The honesty index is a per-agent score computed by a published formula
over ledger contents. This section is formula version **`index-v0`**.

### 10.1 Components

All components lie in [0, 1].

- **coverage** — did the agent log at the rate it committed to?
  `min(1, entries_in_trailing_30d / commitments.entries_per_30d)`.
  If the oath declares no `entries_per_30d`, coverage is fixed at `0.5`:
  undeclared coverage is unverifiable coverage, and it scores accordingly.

- **violation_component** — `1 − min(1, W / max(1, total_entries))`, where
  `W` is the sum over all entries of: `0.5` for a violation whose entry is
  prompt (§8.2), `1.0` for a violation whose entry is late, `0.1` for an
  `uncertain` verdict, `0` for compliant. A promptly confessed violation
  costs half an unprompt one — honesty about failure is the product — and
  uncertainty costs little, so that saying "I don't know" is never the
  losing move.

- **confession_integrity** — of all `violation` and `uncertain` entries,
  the fraction whose confession is well-formed (§8.1) and whose entry is
  prompt (§8.2). `1.0` when there are no such entries.

- **continuity** — the "never rewrote history" score. `1.0` when every
  pair of consecutive STHs known for the ledger passes consistency
  verification and the ledger met its declared STH cadence (§5, §11.3)
  over the agent's sworn lifetime; otherwise the fraction of required STH
  intervals met. Any oathbreaker proof voids the index entirely (§10.4).

### 10.2 Formula

```
score = round(100 × (0.20·coverage
              + 0.35·violation_component
              + 0.25·confession_integrity
              + 0.20·continuity))
```

### 10.3 Publication

An index MUST always be published with its `formula_version`, its
component values, and the tree size it was computed at, so that anyone can
recompute it. The reference implementation stores these as snapshots.

### 10.4 The hard line

The index MUST be a pure function of ledger contents and the published
formula. Nothing else may ever change it: no payment, no partnership, no
manual adjustment, no exception, forever. A ledger operator MUST NOT
publish, for any agent, any score not derived from the published formula.
Changing the formula is a new `formula_version`, adopted only through the
process in §12, announced as a logged amendment to the operator's own oath
(§11.4) before taking effect; prior scores remain recomputable under the
formula that produced them. The index of an oathbreaker record (§6.3) is
void and MUST be displayed as the status, not as a number.

---

## 11. Ledger Operator Duties

A local ledger binds only its agent. A **public ledger** — one that
accepts submissions or serves records to third parties — assumes the
duties in this section. The reference public ledger at oath.sh operates
under exactly these duties.

### 11.1 Structural duties

1. **Append-only, structurally.** Entries and oath documents are never
   updated or deleted. Operators SHOULD make this physical, not
   procedural — e.g. database roles with no UPDATE/DELETE grants on those
   tables, for any role including the service's own.
2. **One view.** The ledger MUST present a single, consistent tree to all
   audiences. Split views are oathbreaker proof (§6.3).
3. **STH cadence.** Per §5: within the declared merge delay of
   incorporation, and at least every 24 hours.
4. **Validation only, never editing.** The ledger MUST reject malformed
   submissions (§3.2) and MUST NOT alter, re-serialize, or "fix" any
   entry. The ledger stores verdicts; it never produces them.

### 11.2 Service duties

A public ledger MUST serve, publicly, without authentication or fee:

| Resource | Endpoint (under the API base) |
|---|---|
| Latest STH | `GET /v0/sth` |
| STH history | `GET /v0/sth/history` |
| Entry by index (exact leaf bytes) | `GET /v0/entries/{index}` |
| Entry range | `GET /v0/entries?start={i}&end={j}` |
| Inclusion proof | `GET /v0/proof/inclusion?leaf_hash={h}&tree_size={n}` |
| Consistency proof | `GET /v0/proof/consistency?old_size={m}&new_size={n}` |
| Agent record + oath versions | `GET /v0/agents/{handle}` |
| Canonical oath document | `GET /v0/agents/{handle}/oath/{version}` |
| Index snapshot with components | `GET /v0/agents/{handle}/index` |
| Submission | `POST /v0/entries` → receipt |

Discovery: a public ledger MUST publish
`https://<host>/.well-known/oath/ledger.json` containing
`{ "ledger", "pubkey", "api", "mmd_seconds", "spec_version" }`.

### 11.3 Receipts and merge delay

On accepting a submission, a public ledger MUST return a **receipt**:

```json
{
  "ledger": "oath.sh",
  "leaf_hash": "<64 hex chars>",
  "received_at": "2026-07-13T00:00:06Z",
  "mmd_seconds": 600,
  "signature": "<128 hex chars: ledger key, over the receipt without signature>"
}
```

The receipt is the ledger's promise: the leaf will be covered by an STH
within `mmd_seconds` (the maximum merge delay) of `received_at`. The
ledger MUST declare its MMD in its discovery document; the reference
ledger declares 600 seconds. A broken receipt is oathbreaker proof (§6.3).

### 11.4 The operator's own oath

A public ledger MUST itself be a sworn agent: it MUST publish its own oath
— covering at minimum duties 11.1(1–4), its MMD, and the §10.4 hard line —
and log its own conduct as entries in its own tree, adjudicated Mode A.
The operator of the accountability infrastructure is its own first
subject. A ledger that will not swear its own oath does not conform to
this specification.

---

## 12. Versioning of This Specification

- This specification uses semantic versioning. This document is **0.1.0**.
- The `spec_version` field in oath documents and entries carries
  MAJOR.MINOR only (`"0.1"`).
- While MAJOR is 0, MINOR increments MAY include breaking changes; PATCH
  increments are editorial only. From 1.0.0, breaking changes require a
  MAJOR increment.
- Index formula versions (`index-v0`, `index-v1`, …) are defined by the
  spec version that introduces them and change only with a spec release.
- Every spec release MUST be announced by the reference ledger as a logged
  amendment to its own oath (§11.4) before any conforming behavior change
  takes effect. The spec's change history is itself on the record.
- The specification is licensed Apache-2.0. Its canonical text lives at
  https://oath.sh/spec and in the public repository.

---

## Appendix A. Local Ledger File Layout (informative)

The reference SDK writes a local ledger as a directory:

```
oath-ledger/
  oath.json         # current oath (canonical form); prior versions in oath.history/
  entries.jsonl     # one entry per line, in leaf order; line k = leaf index k
  heads.json        # every STH ever emitted, in order
```

Each line of `entries.jsonl` MUST be the exact leaf bytes of its entry
(the newline separator is not part of the leaf bytes). Verification needs
no index structures: recompute the tree from the lines, then check every
STH in `heads.json` against it. This is what `oath verify ./oath-ledger`
does.

## Appendix B. Test Vectors (normative)

All values below are reproducible: Ed25519 signing is deterministic
[RFC 8032], and the private key seed is published. The generator script
lives at `spec/vectors.mjs` in the repository (`node spec/vectors.mjs`).
The JSON in these vectors is ASCII with integer numbers only, so JCS
serialization equals compact JSON with lexicographically sorted keys.

### B.1 Key

```
seed   = SHA-256("oath-spec-v0.1-test-key")
       = b5792bfb6ec2bce22d5b62804250be2d2fc79cc2f877a51f2dc1f65b001f3695
pubkey = 5c4427726ed019725b32d750d205ce3c765f291b75da43e59328d0dfaaa51c97
```

### B.2 Tree hashing

Leaves are the ASCII byte strings `entry-0` … `entry-6` (in production,
leaves are entry leaf bytes; the algorithm is byte-string agnostic).

```
MTH of empty tree      = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
leaf_hash("entry-0")   = 40766b2033429026f53d54502679a839706b4741f8dcaf3a8bba5f41b5ffe075
leaf_hash("entry-1")   = e868811a482c27d50b6d45dde79c465d6adb9b06645100477a90cf3d8518898b
root, tree_size=1      = 40766b2033429026f53d54502679a839706b4741f8dcaf3a8bba5f41b5ffe075
root, tree_size=2      = 2f27a5082c1d42afa488ac350a9fc4390c084f54f71ecdff859e98db8429b479
root, tree_size=3      = a64bf26e09128f6fe2fe6f8b2d8c801e166b57c047a7cd9b2b809e7a96a2f1cb
root, tree_size=7      = 9139601cc1ca8ab2a7a0c2c134c04845f2b1ba549a83d6c845cfcda439cc585d
```

Inclusion proof for `leaf_index=2`, `tree_size=7` (audit path, lowest
first):

```
[0] 27479b6ab321d2ee477452f68ba527748e863cafe8fbd1df2bf89d1570d1b697
[1] 2f27a5082c1d42afa488ac350a9fc4390c084f54f71ecdff859e98db8429b479
[2] e429c5b5ccaa9523c37297f1846766f903137e82195c5199e6be57130d1006c8
```

Consistency proof for `old_size=3`, `new_size=7`:

```
[0] 049d7dcdb56bcfebd313304c9839f196a3d4b6ef3bdc0b08298f93ac8191f0a8
[1] 27479b6ab321d2ee477452f68ba527748e863cafe8fbd1df2bf89d1570d1b697
[2] 2f27a5082c1d42afa488ac350a9fc4390c084f54f71ecdff859e98db8429b479
[3] e429c5b5ccaa9523c37297f1846766f903137e82195c5199e6be57130d1006c8
```

### B.3 Signed oath document

Signing input (canonical bytes of the document without `signature`):

```
{"agent":"example","created_at":"2026-07-13T00:00:00Z","laws":[{"mode":"machine","n":1,"text":"Every published result includes its content hash."},{"mode":"machine","n":2,"text":"No record is edited after publication."}],"pubkey":"5c4427726ed019725b32d750d205ce3c765f291b75da43e59328d0dfaaa51c97","scope":"All publish actions of the example agent.","spec_version":"0.1","version":1}
```

```
signature = 2dbedf47080d818226eea5d8263557109e03ffc238209837e41a9b7bd01b342e84e844018353a6a5beefcd4064be2415e77e81d19a65db5c2cc97b039a5d2808
oath_hash = 86e16c8e2d1649b9a1f32fcfd256c4589dfdb13564da2b54497da5b23d238b56
```

### B.4 Signed entry

`action.content_hash` is the SHA-256 of the ASCII bytes `hello, ledger`.
Signing input (canonical bytes of the entry without `signature`):

```
{"action":{"content_hash":"c69d7f5eec23dabb082f15051db62b0cf5d3045d7aae68e0f8a93ecf44a4aaae","occurred_at":"2026-07-13T00:00:00Z","summary":"Published result 42 with its content hash.","type":"result.publish"},"adjudication":{"check":"law1.content-hash-present","mode":"machine"},"agent":"example","law_cited":1,"oath_version":1,"spec_version":"0.1","timestamp":"2026-07-13T00:00:05Z","verdict":"compliant"}
```

```
signature = 5913fd6e2fed158b9fb81748aae02580e088db1be987df0c2cfef3d93c2236374ca17e6b40aa5e69227a3b6d64fbbd02227ee22ec43e99951c558a55cdd9a105
```

Leaf bytes (canonical bytes of the signed entry):

```
{"action":{"content_hash":"c69d7f5eec23dabb082f15051db62b0cf5d3045d7aae68e0f8a93ecf44a4aaae","occurred_at":"2026-07-13T00:00:00Z","summary":"Published result 42 with its content hash.","type":"result.publish"},"adjudication":{"check":"law1.content-hash-present","mode":"machine"},"agent":"example","law_cited":1,"oath_version":1,"signature":"5913fd6e2fed158b9fb81748aae02580e088db1be987df0c2cfef3d93c2236374ca17e6b40aa5e69227a3b6d64fbbd02227ee22ec43e99951c558a55cdd9a105","spec_version":"0.1","timestamp":"2026-07-13T00:00:05Z","verdict":"compliant"}
```

```
leaf_hash = 6be26993fc0e8763631bbdc33f7975f64a03281c391ed6c2fc9bad8d135f3f9f
```

### B.5 Signed tree head

For a tree containing exactly the entry above (`tree_size=1`, so
`root_hash = leaf_hash`). Signing input:

```
{"ledger":"example.local","root_hash":"6be26993fc0e8763631bbdc33f7975f64a03281c391ed6c2fc9bad8d135f3f9f","timestamp":"2026-07-13T00:00:10Z","tree_size":1}
```

```
signature = 4e6c4dbd69227db9fcd43502a216b668949ce6c7b41d6b8d211eb986a3d9c33f39499d48344e30f9e34daef1fddb0055b997a6b0cf45cb41810f7d9013209006
```

## References

- **RFC 2119 / RFC 8174** — Key words for use in RFCs (BCP 14).
- **RFC 6962** — Certificate Transparency (Merkle tree, proofs).
- **RFC 8032** — Edwards-Curve Digital Signature Algorithm (Ed25519).
- **RFC 8785** — JSON Canonicalization Scheme (JCS).
- **RFC 3339** — Date and Time on the Internet: Timestamps.
- **FIPS 180-4** — Secure Hash Standard (SHA-256).
