Skip to content

Latest commit

 

History

History
442 lines (327 loc) · 17.2 KB

File metadata and controls

442 lines (327 loc) · 17.2 KB

Chainblocks Features

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 → Requirement → Use Case Matrix

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.


Feature Detail

FT-CB-01: Open/Close Lifecycle

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 name and writer (REQ-CB-002)
  • close releases the lock and any file handles (REQ-CB-003)
  • A closed ledger throws StoreClosedError on 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 matches
  • test/integration/concurrent-open.test.ts — second opener gets LockHeldError

FT-CB-02: Block Append

The hot path. One method:

const block = await ledger.append({
  kind: 'app.event.recorded',
  payload: { eventId: 'e-a8c4', from: 'pending', to: 'confirmed' },
});

Behaviors:

  • seq assigned by ledger, monotonic (REQ-CB-011)
  • ts assigned by ledger at append time, ISO 8601 UTC ms (REQ-CB-012)
  • prev set to current head's hash (REQ-CB-013)
  • hash computed 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 structure
  • test/integration/concurrent-append.test.ts — 1,000 parallel append calls produce a valid chain
  • test/integration/append-throughput.test.ts — p50 ≤5ms per REQ-CB-NF-001

FT-CB-03: Sequential Read

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 order
  • test/integration/read-during-append.test.ts — interleaved reads and appends produce no torn or partial views

FT-CB-04: Filtered Read

for await (const block of ledger.read({ kind: 'app.event.*', fromSeq: 1000 })) {
  /* ... */
}

Behaviors:

  • fromSeq and toSeq clamp to valid range (REQ-CB-031)
  • kind supports exact match and trailing wildcard foo.* (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

FT-CB-05: Head Query

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

FT-CB-06: Random Access by Seq

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.md that this is not a constant-time op on FileStore

FT-CB-07: Full-Chain Verification

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=true
  • test/unit/verify-tampered.test.ts — every failure reason has a dedicated case
  • test/requirements/REQ-CB-041.req.test.ts — exhaustive failure-mode coverage

FT-CB-08: Incremental Verification

// Trust everything up to seq=5000, verify from there forward
const result = await ledger.verify({ fromSeq: 5001 });

Behaviors:

  • Reads block at fromSeq - 1 as trust anchor (REQ-CB-042)
  • Verifies forward from fromSeq to 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

FT-CB-09: Store Port

The pluggability contract. See MODEL.md §5 for the full interface.

Behaviors:

  • Any object satisfying Store is a valid backend (REQ-CB-050)
  • Store.append must be atomic (REQ-CB-051)
  • Store.readAll returns blocks in seq order (REQ-CB-052)
  • Chainblocks ships FileStore and MemoryStore (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

FT-CB-10: FileStore (Default)

JSONL on disk with a .lock sibling.

Behaviors:

  • One block per line, JCS-canonical (REQ-CB-055)
  • fsync after each append (REQ-CB-057)
  • O_EXCL lockfile 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

FT-CB-11: MemoryStore (Default)

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/

FT-CB-12: Verifier CLI

$ npx chainblocks-verify ./audit.ledger

Behaviors:

  • Installable via npx with 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 format
  • examples/verify-cli/ — runnable example invoked by CI

FT-CB-13: Append Events

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 block
  • test/unit/events-listener-error.test.ts — listener throw produces AppendListenerError, chain still consistent

FT-CB-14: Typed Error Taxonomy

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

FT-CB-15: RFC 8785 Canonicalization

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 implementation
  • test/requirements/REQ-CB-016.req.test.ts — round-trip canonicalization on a corpus of edge cases (Unicode, large numbers, deep nesting)

FT-CB-16: Project-Wide Quality Bar

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 append leaves 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.md lists every exported symbol
  • README.md quickstart 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 benchmarks
  • test/integration/crash-recovery.test.ts — simulated crash leaves valid chain
  • test/integration/security-no-egress.test.ts — verify zero network calls during entire test suite
  • .github/workflows/ci.yml — enforces all quality gates on every PR
  • scripts/check-jsdoc-coverage.ts — enforces ≥90% JSDoc coverage

Out-of-Scope Features (deferred to post-v0.1)

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."


Feature Sizing

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.


Provenance

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 why
  • REQUIREMENTS.md — the what (REQ-CB-* IDs)
  • MODEL.md — the data shape
  • docs/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.

🖇️