ushman-ledger
v1.2.0
Published
Append-only workspace ledger library and CLI for Ushman v4.
Readme
ushman-ledger
Append-only workspace ledger library and CLI for ushman v4 workspaces.
What it owns
- Ledger entry schemas
- Serialized manifest-safe entry writes
- Content-hash idempotency
- Patch blob storage
- Retro / JSONL / timeline / dependency-graph rendering
- Change-log / workspace narrative markdown rendering
- Archive integrity output
- Doctor and coverage helpers
Install for local workspace development
bun install
bun run build
bun link
cd ~/workspace/ushman
bun link ushman-ledgerLibrary API
import { buildValidatorResultRecord, openLedger } from 'ushman-ledger';
const ledger = await openLedger('/path/to/workspace');
await ledger.record({
emitter: { tool: 'ushman-cli', version: '1.0.0' },
kind: 'tool-invocation',
phase: 'capture',
summary: 'capture started',
});
await ledger.record({
agent: { name: 'codex-cli' },
diffPath: '/tmp/change.patch',
emitter: { tool: 'ushman-cli', version: '1.0.0' },
kind: 'agent-patch',
phase: 'cleanup',
rationale: 'Track a manual patch.',
summary: 'Apply websocket shim',
});
await ledger.record(
buildValidatorResultRecord({
emitter: { tool: 'ushman-doctor', version: '1.0.0' },
phase: 'cleanup',
summary: 'coverage gate passed',
validator: 'doctor',
verdict: 'green',
}),
);
await ledger.render({ to: 'retro' });
await ledger.render({ to: 'migration-log-md' });
await ledger.archive('/tmp/ledger.tgz');CLI
ushman-ledger record --workspace=<ws> --kind=tool-invocation --phase=capture --summary="capture started"
ushman-ledger record --workspace=<ws> --kind=agent-patch --phase=cleanup --summary="capture git diff" --rationale="track working tree change" --diff-from-git=HEAD
ushman-ledger record --workspace=<ws> --kind=operator-decision --phase=cleanup --summary="manual override" --action=ledger-hand-edit --check-id=manual-review --rationale="operator edited the ledger after audit"
ushman-ledger record --workspace=<ws> --kind=change-log --subkind=semantic-cleanup --phase=cleanup --summary="split schema modules" --diff=/tmp/change.patch --hypothesis="smaller schema modules keep the public API stable" --commands=$'bun test\nbun run typecheck' --smoke-result=pass --parity-status=green --rollback-plan="revert the schema split"
ushman-ledger note regression --workspace=<ws> --phase=cleanup --summary="runtime drift" --body=/tmp/note.md
ushman-ledger note cleanup-wave --workspace=<ws> --phase=cleanup --summary="wave 1" --body=/tmp/narrative.md
ushman-ledger list --workspace=<ws> --json
ushman-ledger render --workspace=<ws> --to=retro
ushman-ledger render --workspace=<ws> --to=migration-log-md
ushman-ledger render --workspace=<ws> --to=workspace-narrative-md
ushman-ledger render --workspace=<ws> --to=jsonl --out=/tmp/ledger.jsonl
ushman-ledger render --workspace=<ws> --to=dependency-graph --out=/tmp/ledger.mmd
ushman-ledger archive --workspace=<ws> --out=/tmp/ledger.tgz
ushman-ledger doctor --workspace=<ws>Valid record kinds: tool-invocation, agent-patch, operator-patch, operator-decision, validator-result, runtime-event, note, correction, strip-decision-reverted, change-log
Valid phases: capture, intake, seed, vendor-extract, cleanup, parity, characterize, equiv, analyze, recover, ship, migration
Valid note subkinds: regression, automation, retro, operator, tooling-gap, cleanup-wave, verified-flow, open-issue, decomposition-wave, semantic-cleanup-summary
Valid render targets: retro, jsonl, timeline-html, dependency-graph, migration-log-md, workspace-narrative-md
Change-log records
change-log entries are append-only structured narrative records for migration and cleanup work. They accept:
subkind:pre-change-checkpoint,semantic-cleanup,vendor-extract,decomposition,rollback,hotfix,smokefilesChanged: explicit CSV paths via--files-changed, or automatic diffstat-style derivation from--diff/--diff-from-git- optional narrative fields:
hypothesis,commandsRun,smokeResult,smokeNotes,parityStatus,rollbackPlan rollsBack: required whensubkind=rollback
migration-log-md renders only change-log entries. workspace-narrative-md renders only the dedicated narrative note subkinds.
Workspace prerequisite
openLedger() and the CLI expect a valid ushman v4 workspace with .lab/lab.json already present.
Idempotency
- Without
idempotencyKey, records dedupe by their logical content hash. - With
idempotencyKey, the key is authoritative within a phase. Reusing the same key with different content in the same phase returns the first recorded entry unchanged. - Stored entries copy the idempotency key into
links.idempotencyKeyfor auditability.
Payload shapes
operator-decisionentries capture{ action, checkId?, rationale }inpayload.actionis one ofbypass-doctor,skip-check,override-strip-decision,override-ship-state,manual-parity-assertion,ledger-hand-edit,escalation.strip-decision-revertedentries capture{ stripDecisionId, rationale, invalidatedStages? }.correctionentries requirelinks.correctsLedgerIdpointing at the entry they correct.- Patch entries (
agent-patch,operator-patch) store adiffblob reference and arationale. Patch text is stored once under.lab/ledger/blobs/and shared by hash.
Links and coverage
links.affectedFilesshould contain normalized workspace-relative paths for files changed by anagent-patchoroperator-patch.- Use forward slashes, do not prefix paths with
./, and do not include trailing slashes or..segments. - Coverage only considers candidate workspace files modified after workspace initialization.
- A modified file is considered covered when any patch entry lists it in
links.affectedFiles. - Coverage is backed by a durable read index so repeated coverage runs do not rescan every patch entry.
Append chain and concurrency
- Each phase is an append-only linked list through
prevEntryId. - Manifest updates are serialized through a global ledger lock.
- Appends use a pending-commit journal so entry files, the manifest, and the durable read index can be replayed after crashes.
- Open, read, and append paths reconcile unfinished commits before serving ledger data.
- Corrupt or stale manifest locks are reclaimed automatically with compare-and-swap style quarantine semantics.
Crash recovery
- Pending commit journals live under
.lab/ledger/pending/. - Pending archive journals live under
.lab/ledger/pending-archives/. - Startup reconciliation replays journaled appends in manifest sequence order.
- Startup reconciliation also rebuilds a missing or stale
read-index.jsonfrom manifest sequence order. - Verified pending archives are adopted into the manifest on startup; corrupt or partial pending archives are deleted.
- Stale temp files are cleaned for phase entries, the manifest, blobs, the read index, and render outputs.
Recovery & Troubleshooting
.lab/ledger/pending/contains append journals that let the ledger recover incomplete writes after crashes. Do not edit these files by hand.- If startup or
doctorreports a pending commit mismatch, re-open the ledger or rerun the command first so reconciliation can replay or discard the journal safely. - If
doctorreports manifest or blob corruption, fix the underlying entry/blob mismatch first and rerundoctorbefore attemptingarchive.
Scan behavior
list, render, coverage, and doctor iterate entries in manifest sequence order.- Read paths use a durable
read-index.jsonsidecar instead of resortingmanifest.entryLocationson every scan. list --limit=<n>keeps bounded memory and returns the lastnmatching entries in append order.- Limited reads can filter by
kindandsincefrom the durable read index before reading entry files. - Coverage file stats and doctor blob hashing use bounded concurrency instead of unbounded
Promise.all.
Archive integrity
- Archives are created as
.tgzfiles using pure Node code. - Each archive includes
archive-manifest.jsonwith per-file SHA-256 hashes and an overallintegrityHash. archivewrites a pending archive journal before tar creation, verifies the final tarball after creation, and only then appends archive metadata to the manifest.archiverefuses to run whendoctorreports an unhealthy ledger.
Git diff capture
--diff-from-git=<ref>runsgit diff <ref>against the workspace working tree.- The CLI times out
git diffafter 30 seconds and reports a usage error instead of hanging indefinitely. - This is the only CLI feature that depends on an external binary.
gitmust be available inPATH.
Storage shape
<ws>/.lab/ledger/
.manifest.lock
manifest.json
read-index.json
pending/
pending-archives/
blobs/
render.md
render.migration-log.md
render.timeline.html
render.workspace-narrative.md
<phase>/
<timestamp>-<sequence>-<hash>.jsonValidation
bun run lint
bun run typecheck
bun test
bun run build
bun run bench:scaleScale benchmark
bun run bench:scalecreates a temporary workspace and benchmarks large-ledger paths for population, limited reads, repeated coverage, and repeated doctor runs.- Override the defaults with environment variables such as
LEDGER_BENCH_ENTRY_COUNT=100000andLEDGER_BENCH_CANDIDATE_FILE_COUNT=10000.
