The feature inventory for chainblocks v0.1, derived from REQUIREMENTS.md and validated against docs/USE-CASES.md.
Status: Left-bookend draft, 2026-05-18.
Companion docs: STORY.md, REQUIREMENTS.md, MODEL.md, USE-CASES.md.
Every feature has a stable ID of the form FT-CB-NN. Features map to one or more requirements (REQ-CB-) and to one or more use cases (UC-).
| Feature | Description | Requirements | Use Cases |
|---|---|---|---|
| FT-CB-01 Open/close lifecycle | Create, open, close, reopen ledgers | REQ-CB-001-005 | UC-1, UC-3 |
| FT-CB-02 Block append | Atomic append of typed payloads | REQ-CB-010-020 | UC-1, UC-2, UC-3, UC-4 |
| FT-CB-03 Sequential read | Async-iterate the chain in seq order | REQ-CB-030-031, 034 | UC-1, UC-2, UC-9 |
| FT-CB-04 Filtered read | Read by seq range and/or kind pattern | REQ-CB-032-033 | UC-1, UC-2, UC-4 |
| FT-CB-05 Head query | O(1) access to current head | REQ-CB-035 | UC-1, UC-8 |
| FT-CB-06 Random access by seq | Direct fetch of any block by sequence | REQ-CB-036 | UC-6 |
| FT-CB-07 Full-chain verification | Walk-and-verify with precise error reporting | REQ-CB-040-041, 043-044 | UC-1, UC-5, UC-6, UC-8 |
| FT-CB-08 Incremental verification | Verify from a trust anchor forward | REQ-CB-042 | UC-8 |
| FT-CB-09 Store port | Pluggable storage interface | REQ-CB-050-053 | UC-7, UC-9 |
| FT-CB-10 FileStore (default) | JSONL + lockfile, fsync per append | REQ-CB-054-057 | UC-1, UC-2, UC-3, UC-4, UC-6, UC-8 |
| FT-CB-11 MemoryStore (default) | In-memory store for tests | REQ-CB-054 | (test infrastructure) |
| FT-CB-12 Verifier CLI | chainblocks-verify <path> standalone tool |
REQ-CB-060-064 | UC-5, UC-6, UC-8 |
| FT-CB-13 Append events | EventEmitter 'append' hook |
REQ-CB-070-071 | UC-4 |
| FT-CB-14 Typed error taxonomy | Specific error classes for each failure mode | REQ-CB-005, REQ-CB-071, REQ-CB-NF-011 | UC-6 |
| FT-CB-15 RFC 8785 canonicalization | Cross-implementation-deterministic hashing | REQ-CB-016, NF-020 | UC-5 |
| FT-CB-16 Project-wide quality bar | The cross-cutting requirements that apply to the whole library, not to a single feature: performance budgets, reliability promises, security guarantees, compatibility commitments, documentation gates, CI quality gates. Owned at the project level; verified by CI and by the GitHub Published Projects playbook. | REQ-CB-NF-001-003, REQ-CB-NF-010, REQ-CB-NF-012, REQ-CB-NF-021-023, REQ-CB-NF-030-033, REQ-CB-NF-040-043, REQ-CB-NF-050-053 | (project-wide) |
16 features at v0.1. Each is small. Together they deliver the doctrine in STORY.md.
The public lifecycle surface:
const ledger = await openLedger({
store: new FileStore({ path: '/data/audit.ledger' }),
name: 'app-audit',
writer: 'app',
});
// ... use the ledger ...
await ledger.close();Behaviors:
- First call creates the genesis block (REQ-CB-017)
- Subsequent calls validate that the on-disk genesis matches the supplied
nameandwriter(REQ-CB-002) closereleases the lock and any file handles (REQ-CB-003)- A closed ledger throws
StoreClosedErroron further operations - Reopening yields the same head as on close (REQ-CB-004)
- Concurrent open of the same path throws
LockHeldError(REQ-CB-005)
Acceptance criteria:
test/integration/lifecycle.test.ts— open, append, close, reopen, verify head matchestest/integration/concurrent-open.test.ts— second opener getsLockHeldError
The hot path. One method:
const block = await ledger.append({
kind: 'app.event.recorded',
payload: { eventId: 'e-a8c4', from: 'pending', to: 'confirmed' },
});Behaviors:
seqassigned by ledger, monotonic (REQ-CB-011)tsassigned by ledger at append time, ISO 8601 UTC ms (REQ-CB-012)prevset to current head's hash (REQ-CB-013)hashcomputed via JCS + SHA-256 (REQ-CB-016)- Atomic: success ⇒ durable; failure ⇒ chain unchanged (REQ-CB-019)
- Concurrent calls serialized internally (REQ-CB-020)
- Returns the sealed block (REQ-CB-018)
Acceptance criteria:
test/unit/append.test.ts— seq monotonicity, hash format, ts format, returned block structuretest/integration/concurrent-append.test.ts— 1,000 parallelappendcalls produce a valid chaintest/integration/append-throughput.test.ts— p50 ≤5ms per REQ-CB-NF-001
for await (const block of ledger.read()) {
console.log(block.seq, block.kind);
}Behaviors:
- Async iterable from genesis to head (REQ-CB-030)
- Snapshot-consistent: every yielded block has a valid prev-hash chain back to genesis as of the moment of yield (REQ-CB-034)
- No back-pressure semantics in v0.1; consumers should handle large chains with care
Acceptance criteria:
test/unit/read.test.ts— yields all blocks in seq ordertest/integration/read-during-append.test.ts— interleaved reads and appends produce no torn or partial views
for await (const block of ledger.read({ kind: 'app.event.*', fromSeq: 1000 })) {
/* ... */
}Behaviors:
fromSeqandtoSeqclamp to valid range (REQ-CB-031)kindsupports exact match and trailing wildcardfoo.*(REQ-CB-032)- Filters compose (REQ-CB-033)
- No regex, no payload filtering — project to a database if you need that
Acceptance criteria:
test/unit/read-filters.test.ts— every filter combination, edge cases (empty range, exclusive bounds, etc.)test/requirements/REQ-CB-032.req.test.ts— kind-pattern matching including wildcard
const head: LedgerHead = await ledger.head();
console.log(head.seq, head.hash, head.ts);Behaviors:
- Returns current head's
{seq, hash, ts}(REQ-CB-035) - Always returns a head (genesis is a valid head)
- O(1) — the ledger caches head in memory after open
Acceptance criteria:
test/unit/head.test.ts— head reflects last append immediately
const block = await ledger.getBySeq(1342);
if (block) console.log(block.kind);Behaviors:
- Returns block at given seq, or null if out of range (REQ-CB-036)
- Performance characteristics depend on store; FileStore is O(N) (must scan), but a future SQLiteStore would be O(log N)
Acceptance criteria:
test/unit/get-by-seq.test.ts— returns correct block; null for out-of-range- Documented in
docs/API.mdthat this is not a constant-time op onFileStore
const result = await ledger.verify();
if (result.ok) {
console.log(`Valid: ${result.head.seq} blocks, head hash ${result.head.hash}`);
} else {
console.log(`Tampered at seq ${result.tamperedAt}: ${result.reason}`);
}Behaviors:
- Walks genesis-to-head, recomputing every hash (REQ-CB-040)
- On first inconsistency, returns precise
{tamperedAt, reason}(REQ-CB-041) - Reasons:
hash_mismatch,prev_mismatch,missing_block,out_of_order,torn_write,malformed_block - O(N) time, linear-scan reads only (REQ-CB-043)
- Read-only operation; works on read-only handles (REQ-CB-044)
Acceptance criteria:
test/unit/verify.test.ts— valid chain returns ok=truetest/unit/verify-tampered.test.ts— every failure reason has a dedicated casetest/requirements/REQ-CB-041.req.test.ts— exhaustive failure-mode coverage
// Trust everything up to seq=5000, verify from there forward
const result = await ledger.verify({ fromSeq: 5001 });Behaviors:
- Reads block at
fromSeq - 1as trust anchor (REQ-CB-042) - Verifies forward from
fromSeqto head - Useful for periodic incremental verification of large ledgers
Acceptance criteria:
test/unit/verify-incremental.test.ts— verifies from arbitrary seq; rejects if trust anchor is missing
The pluggability contract. See MODEL.md §5 for the full interface.
Behaviors:
- Any object satisfying
Storeis a valid backend (REQ-CB-050) Store.appendmust be atomic (REQ-CB-051)Store.readAllreturns blocks in seq order (REQ-CB-052)- Chainblocks ships
FileStoreandMemoryStore(REQ-CB-054)
Acceptance criteria:
test/integration/store-conformance.test.ts— a parameterized suite that runs against every shipped Store implementation, plus an example custom Store
JSONL on disk with a .lock sibling.
Behaviors:
- One block per line, JCS-canonical (REQ-CB-055)
fsyncafter each append (REQ-CB-057)O_EXCLlockfile for single-writer enforcement (REQ-CB-056)- Stale lock recovery: lockfile older than 60s with dead PID is reclaimed (REQ-CB-NF-012)
Acceptance criteria:
test/integration/file-store.test.ts— real fs, crash mid-write recovery (kill -9 in a child process, reopen, verify)test/integration/file-store-lock.test.ts— concurrent open, stale lock recovery
import { openLedger, MemoryStore } from '@chainblocks/core';
const ledger = await openLedger({
store: new MemoryStore(),
name: 'test-ledger',
});Behaviors:
- Map<number, Block> keyed by seq
- Zero persistence (lost on close)
- Used by chainblocks' own tests AND offered to consumers for testing their integrations
- No fsync, no locking — appends are synchronous in JS sense
Acceptance criteria:
test/unit/memory-store.test.ts— conforms to the Store contract- Used as the default store in
examples/basic/
$ npx chainblocks-verify ./audit.ledgerBehaviors:
- Installable via
npxwith zero config (REQ-CB-060) - Green checkmark + summary on valid (REQ-CB-061), exit code 0
- Red X + precise tamper report on invalid (REQ-CB-062), exit code 1
- Helpful error + exit code 2 on operational failure (REQ-CB-063)
- Zero runtime deps beyond
@chainblocks/core(REQ-CB-064)
Acceptance criteria:
test/integration/cli.test.ts— every exit code, every output formatexamples/verify-cli/— runnable example invoked by CI
ledger.on('append', (block) => {
console.log(`appended ${block.seq}: ${block.kind}`);
});Behaviors:
- Synchronous emission after successful append (REQ-CB-070)
- Listener errors propagate as
AppendListenerError, chain state already committed (REQ-CB-071) - Useful for live dashboards, replication tail-ers, and (future) anchoring plugins
Acceptance criteria:
test/unit/events.test.ts— listener called once per append, with sealed blocktest/unit/events-listener-error.test.ts— listener throw producesAppendListenerError, chain still consistent
Every chainblocks error extends ChainblocksError, has a stable name, includes structured context. See MODEL.md §8.
Behaviors:
LockHeldError(REQ-CB-005)LedgerMismatchError(genesis mismatch)MalformedBlockError(read of corrupted block)TornWriteError(REQ-CB-NF-011)ChainTamperedError(verify failure escalated as exception when caller prefers throw over return)AppendListenerError(REQ-CB-071)StoreClosedError(operation on closed ledger)
Acceptance criteria:
test/unit/errors.test.ts— every error class instantiable with context, name stable across versions
Not a feature the user invokes, but the foundation under everything.
Behaviors:
- Hash input is JCS-canonicalized JSON (REQ-CB-016)
- SHA-256 over the canonical bytes (REQ-CB-NF-020)
"sha256:"prefix on every hash value for forward compatibility- Deterministic across implementations (Go, Python, Rust verifiers possible) — see UC-5
Acceptance criteria:
test/unit/hash.test.ts— known-answer tests against a reference JCS implementationtest/requirements/REQ-CB-016.req.test.ts— round-trip canonicalization on a corpus of edge cases (Unicode, large numbers, deep nesting)
Not a feature the user invokes, but the cross-cutting bar the library is held to. These requirements do not belong to any single feature — they constrain the whole project. They are verified by CI, by the GitHub Published Projects playbook check, and by the SIG ingestion clean-run gate.
Performance budgets (REQ-CB-NF-001-003):
- Append p50 ≤5 ms on
FileStore(consumer SSD, payload ≤4 KB) - Verify throughput ≥10,000 blocks/sec
- Read iteration overhead ≤10% vs raw file scan
Reliability promises (REQ-CB-NF-010, REQ-CB-NF-012):
- Crash during
appendleaves the ledger in a valid state (full block or absent, never partial) - Stale lockfiles are reclaimed safely (PID-dead + 60s threshold)
Security guarantees (REQ-CB-NF-021-023):
- No network I/O of any kind
- No payload contents in logs or error messages
- No writes to any directory other than the ledger path + its lock
Compatibility commitments (REQ-CB-NF-030-033):
- Node.js LTS 20.x and 22.x (CI matrix)
- ESM-first with CommonJS compatibility
- Zero runtime deps beyond the JCS implementation
- On-disk JSONL format stable across patch and minor versions (major bump required for breaking changes)
Documentation gates (REQ-CB-NF-040-043):
- ≥90% of exported symbols have JSDoc
docs/API.mdlists every exported symbolREADME.mdquickstart compiles and runs (CI-executed)- ≥3 runnable examples in
examples/(CI-executed)
Quality gates (REQ-CB-NF-050-053):
- Every PR: tests + typecheck + lint + coverage pass; no overrides
- Coverage ≥80% (lines, branches, functions)
- JSDoc coverage ≥90%
- All examples run to completion as CI smoke tests
Acceptance criteria:
test/performance/budgets.bench.ts— budget verification benchmarkstest/integration/crash-recovery.test.ts— simulated crash leaves valid chaintest/integration/security-no-egress.test.ts— verify zero network calls during entire test suite.github/workflows/ci.yml— enforces all quality gates on every PRscripts/check-jsdoc-coverage.ts— enforces ≥90% JSDoc coverage
These are real features people will want. They are not part of v0.1.
| Deferred feature | Why deferred | Possible version |
|---|---|---|
| Repair tool for torn writes | v0.1 detects; recovery is manual | v0.2 |
| Public-blockchain anchoring | Optional, not core | v0.2 as plugin |
| Per-block signatures (Ed25519) | Optional, not core | v0.3 as plugin |
| Multi-writer with conflict detection | Single-writer is the design | (probably never) |
| SQLite-backed query projection | Build outside the library | (downstream pkg) |
| Compression of JSONL | Use gzip if you need it |
(not needed) |
| Indexes (by kind, by payload field) | Project to a database | (downstream pkg) |
| Backup / sync tooling | Use OS-level tools (rsync, S3) | (not needed) |
This list is the doctrine line. When someone asks "can chainblocks do X?", and X is here, the answer is "not in core; here's how to build it as a downstream package."
Rough effort estimate for the v0.1 build (sub-agent territory after bookends approval):
| Feature | LoC estimate | Test LoC estimate | Notes |
|---|---|---|---|
| FT-CB-01 Lifecycle | 80 | 120 | open/close mechanics |
| FT-CB-02 Append | 60 | 200 | The hot path; deserves heavy tests |
| FT-CB-03 Read | 30 | 80 | Trivial wrapper over Store.readAll |
| FT-CB-04 Filtered read | 40 | 120 | Kind-pattern matching is the trick |
| FT-CB-05 Head | 10 | 30 | Trivial |
| FT-CB-06 GetBySeq | 20 | 40 | Trivial |
| FT-CB-07 Verify | 80 | 250 | The integrity guarantee; most test surface |
| FT-CB-08 Incremental verify | 30 | 80 | Variant of FT-CB-07 |
| FT-CB-09 Store port (interface only) | 20 | 0 | Interface; no runtime |
| FT-CB-10 FileStore | 150 | 200 | Lock, fsync, JSONL parsing |
| FT-CB-11 MemoryStore | 50 | 80 | Trivial Map wrapper |
| FT-CB-12 CLI | 100 | 120 | Plus an examples/verify-cli/ |
| FT-CB-13 Events | 30 | 60 | EventEmitter wiring |
| FT-CB-14 Errors | 80 | 60 | One class per error type |
| FT-CB-15 JCS + SHA-256 | 30 | 200 | Wraps the canonicalize npm pkg |
| FT-CB-16 Project-wide quality bar | 0 | 200 | No core LoC; CI config + benches |
Totals: ~810 LoC core + ~1640 LoC tests + ~200 LoC examples + ~50 LoC CI + ~3000 LoC docs (already in flight).
A focused 1.5-day sub-agent build, assuming the bookends are stable.
This features document is the fifth and final file of the chainblocks left-bookend bundle. With it complete, the chainblocks story is fully specified:
STORY.md— the whyREQUIREMENTS.md— the what (REQ-CB-* IDs)MODEL.md— the data shapedocs/USE-CASES.md— the scenarios (UC-* IDs)docs/FEATURES.md— the features (FT-CB-* IDs)
A sub-agent build cycle (per the playbook of the same name) can now begin against these bookends with confidence. The next bookend will be the right-bookend Reflection after v0.1.0 ships: what we built vs. what we planned, what surprised us, what we'd do differently.
🖇️