Ledger Record Format
Record kinds, fields, the SHA-256 seal, identifiers, and every rule a ledger line must satisfy (format version 0)
A ledger is a sequence of lines. Each line is one record, serialized as canonical JSON: object keys
sorted by UTF-16 code unit, no insignificant whitespace, and integers only. Every field is present;
null marks an unknown time or number and "" an empty string field. The portable implementation is
the @y2/ontology package.
Common fields
| Field | Type | Meaning |
|---|---|---|
v | 0 | Format version |
seq | integer | Position in the log, starting at 1, contiguous |
id | string | Record ID; the prefix names the kind |
kind | subject, designator, observation, assertion | Record kind |
recorded_at | time | Record time; strictly increasing along the log |
prev | hex or null | hash of the previous line; null on the first |
hash | hex | SHA-256 of the canonical line without hash |
Times are ISO 8601 in UTC with milliseconds, exactly as Date.prototype.toISOString prints them.
Records
Subject
| Field | Meaning |
|---|---|
class | person, organization, or system. A subject never changes class. |
A subject has no name. Names and identifiers arrive as designators and claims.
Designator
| Field | Meaning |
|---|---|
designator_kind | See Vocabulary |
value | The canonical string |
namespace | Platform, algorithm, registry, or exchange MIC; "" for kinds without one |
normalized | Case-folded for names and handles; otherwise equal to value |
One designator per (kind, namespace, normalized) in a ledger. A second line with the same key is
rejected; reuse the existing ID.
Observation
| Field | Meaning |
|---|---|
method | One passive method, or analyst-note |
source_grade | primary, secondary, or analytic (only for a note) |
source_url | The page; required for a retrieval, optional for a note |
archive_url | A preserved copy, or null |
retrieved_at | Retrieval time; null on a note |
content_hash | SHA-256 of excerpt, or of an archived blob when archive_url is set |
excerpt | A quote of 1–500 characters, or null when an archive holds the content |
collector | A local label for the recorder, 1–80 characters, never a personal name in Y2 |
cites | Observations a note connects; empty on a retrieval |
Assertion
| Field | Meaning |
|---|---|
op | assert or retract |
predicate, from, to | The claim; see predicates |
qualifier | The public role on member-of; "" otherwise |
supersedes | The current head of the claim key, or null on the key's first line |
confidence | Integer 0–100; null on a retraction |
verification | A ladder state; null on a retraction |
valid_from, valid_to | Valid-time interval, end exclusive; null is unknown, never guessed |
evidence_refs | 1–20 earlier observations |
note | Up to 1,000 characters of caveat |
The claim key is predicate|from|to|qualifier. Each key has exactly one chain of lines.
Identifiers
Y2 derives record IDs with its public-ID strategy: a prefix and the first 24 hex characters of SHA-256 over the record type and the workspace ledger position.
| Kind | Prefix | Example |
|---|---|---|
| Subject | sbj_ | sbj_0123456789abcdef01234567 |
| Designator | dsg_ | dsg_0123456789abcdef01234567 |
| Observation | obv_ | obv_0123456789abcdef01234567 |
| Assertion | clm_ | clm_0123456789abcdef01234567 |
The offline format accepts any lowercase ID with the right prefix, such as sbj_ada in the
fictional fixtures.
Rules
Each rule has a code. The API returns the codes in detail; the offline checker prints them per line.
| Code | Rule |
|---|---|
shape.* | Exact field set and field types for the kind |
format.json, format.canonical | The line is JSON in canonical form |
chain.seq, chain.prev, chain.hash | Contiguous seq, prev links the previous hash, hash matches |
chain.time, time.format | Record time strictly increases; times use the exact format |
id.format, id.duplicate | Prefix matches the kind; IDs are unique |
designator.* | Canonical value for the kind and namespace; computed normalized; one row per key |
observation.method, observation.grade | Passive method; only a note is analytic |
observation.url, observation.time | Exact http(s) URL without userinfo; a retrieval has its URL and time, not after record time |
observation.note, observation.cites | A note cites earlier observations and has no retrieval time; a retrieval cites nothing |
observation.excerpt, observation.hash, observation.preservation | Excerpt 1–500 characters; hash matches the excerpt unless archived; keep an excerpt or an archive |
assertion.endpoint | from and to match the predicate; distinct ends; same class for same-as and distinct-from |
assertion.qualifier | Only member-of carries a lowercase role |
assertion.chain | First line supersedes nothing; later lines supersede the current head; no double retraction |
assertion.evidence | 1–20 distinct earlier observations |
assertion.confidence, assertion.interval | Integer 0–100; valid_from before valid_to |
assertion.verification | The state is one the evidence supports (see the ladder) |
assertion.retract | A retraction clears confidence, verification, and the interval |
assertion.label | preferred-label needs a live designated-by for the same pair |
Example
The first two lines of the fictional fixture, wrapped here for reading:
{"class":"person","hash":"…","id":"sbj_ada","kind":"subject","prev":null,
"recorded_at":"2026-03-01T10:00:00.000Z","seq":1,"v":0}
{"designator_kind":"published-name","hash":"…","id":"dsg_ada_name","kind":"designator",
"namespace":"","normalized":"ada example","prev":"…","recorded_at":"2026-03-01T10:00:01.000Z",
"seq":2,"v":0,"value":"Ada Example"}The complete fixtures, fictional.ndjson and fictional-cyber-market.ndjson, are in
packages/ontology/v0/examples in the Y2 repository.