Append-only decision/session/lesson ledger — the queryable form of the Gate 1.5 consensus log: decisions with alternatives-rejected/rationale/tiebreaker, supersession chains (cycle-refusing, no deletes, historical fields immutable), session and lesson records.
| Tier | operations |
| Role | unclassified (baselined) |
| Path | crates/operations/decision-ledger |
| Edition | 2021 |
| Targets | operations_decision_ledger |
| Public items | 34 across 5 modules |
| Tests | 12 |
What it is for
operations-decision-ledger — append-only decision/session/lesson ledger (Sprint 3.4 T4). The queryable form of the Gate 1.5 consensus log.
Invariants (enforced in code, proven by tests):
- Append-only: the surface exposes inserts, reads, and exactly one
mutation — DecisionLedger::supersede's one-shot active → superseded status transition. No updates to historical fields, no deletes.
- Atomic supersession: status +
superseded_byare set in one guarded
UPDATE ... WHERE status = 'active' inside a transaction holding row locks on both decisions.
- No supersession cycles: the replacement's chain is walked inside the
transaction and the transition refused if it leads back (supersede::detect_cycle).
Standalone: does NOT depend on infrastructure-knowledge-index; federation of ledger rows into the search index is the CLI's job (Sprint 3.4 T6).
Capabilities
crate root
operations-decision-ledger — append-only decision/session/lesson ledger
| Item |
|---|
async fn ensure_schema(pool : & sqlx::PgPool) -> Result <(), LedgerError> |
LedgerError
Structured errors for the decision ledger.
| Item |
|---|
pub enum LedgerError |
models (other)
Row models + new-record inputs + boundary validation.
| Item |
|---|
pub const NIL_TENANT: Uuid |
fn validate_alternatives(value : & serde_json::Value) -> Result <(), LedgerError> |
Decision
Row models + new-record inputs + boundary validation.
| Item |
|---|
pub struct Decision |
Decision :: fn status_enum(& self) -> Result <DecisionStatus, LedgerError> |
DecisionFilter
Row models + new-record inputs + boundary validation.
| Item |
|---|
pub struct DecisionFilter |
DecisionStatus
Row models + new-record inputs + boundary validation.
| Item |
|---|
pub enum DecisionStatus |
DecisionStatus :: fn as_str(self) -> & 'static str |
DecisionStatus :: fn parse(s : & str) -> Result <Self, LedgerError> |
Lesson
Row models + new-record inputs + boundary validation.
| Item |
|---|
pub struct Lesson |
LessonKind
Row models + new-record inputs + boundary validation.
| Item |
|---|
pub enum LessonKind |
LessonKind :: fn as_str(self) -> & 'static str |
LessonKind :: fn parse(s : & str) -> Result <Self, LedgerError> |
NewDecision
Row models + new-record inputs + boundary validation.
| Item |
|---|
pub struct NewDecision |
NewDecision :: fn new(title : & str, context : & str, chosen : & str, rationale : & str) -> Self |
NewDecision :: fn validate(& self) -> Result <(), LedgerError> |
NewLesson
Row models + new-record inputs + boundary validation.
| Item |
|---|
pub struct NewLesson |
NewLesson :: fn validate(& self) -> Result <(), LedgerError> |
NewSession
Row models + new-record inputs + boundary validation.
| Item |
|---|
pub struct NewSession |
NewSession :: fn validate(& self) -> Result <(), LedgerError> |
SessionRecord
Row models + new-record inputs + boundary validation.
| Item |
|---|
pub struct SessionRecord |
DecisionLedger
Public ledger API. Static async fns over a pool, matching the
| Item |
|---|
pub struct DecisionLedger |
DecisionLedger :: async fn record_decision(pool : & PgPool, input : & NewDecision,) -> Result <Decision, LedgerError> |
DecisionLedger :: async fn supersede(pool : & PgPool, tenant : Uuid, old_id : Uuid, new_id : Uuid,) -> Result <Decision, LedgerError> |
DecisionLedger :: async fn record_session(pool : & PgPool, input : & NewSession,) -> Result <SessionRecord, LedgerError> |
DecisionLedger :: async fn record_lesson(pool : & PgPool, input : & NewLesson) -> Result <Lesson, LedgerError> |
DecisionLedger :: async fn get_decision(pool : & PgPool, tenant : Uuid, id : Uuid,) -> Result <Option <Decision>, LedgerError> |
DecisionLedger :: async fn list_decisions(pool : & PgPool, tenant : Uuid, filter : & DecisionFilter,) -> Result <Vec <Decision>, LedgerError> |
DecisionLedger :: async fn list_sessions(pool : & PgPool, tenant : Uuid, project : Option <& str>,) -> Result <Vec <SessionRecord>, LedgerError> |
DecisionLedger :: async fn list_lessons(pool : & PgPool, tenant : Uuid, project : Option <& str>, kind : Option <LessonKind>,) -> Result <Vec <Lesson>, LedgerError> |
supersede (other)
Supersession-chain cycle detection — the pure, DB-free core of the
| Item |
|---|
pub const MAX_CHAIN_DEPTH: usize |
fn detect_cycle(old : Uuid, new : Uuid, chain_from_new : & Uuid) -> Result <(), LedgerError> |
fn walk_chain <F>(start : Uuid, mut next : F) -> Vec <Uuid> where F : FnMut(Uuid) -> Option <Uuid>, |
How to use it
No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.
Module structure
operations_decision_ledger
errormodelsreposervicesupersede
flowchart TD n_operations_decision_ledger["operations_decision_ledger"] n_operations_decision_ledger --> n_error["error"] n_operations_decision_ledger --> n_models["models"] n_operations_decision_ledger --> n_repo["repo"] n_operations_decision_ledger --> n_service["service"] n_operations_decision_ledger --> n_supersede["supersede"]
Public surface
`crate root`
| Item | What it is |
|---|---|
async fn ensure_schema(pool : & sqlx::PgPool) -> Result <(), LedgerError> | Apply the crate's schema (idempotent IF NOT EXISTS migration) to pool |
`error`
| Item | What it is |
|---|---|
pub enum LedgerError | Everything the ledger can refuse to do, and why. |
`models`
| Item | What it is |
|---|---|
pub const NIL_TENANT: Uuid | The nil tenant used by embedded/single-tenant deployments (schema default). |
pub enum DecisionStatus | Lifecycle status of a decision |
DecisionStatus :: fn as_str(self) -> & 'static str | The exact string stored in the status column. |
DecisionStatus :: fn parse(s : & str) -> Result <Self, LedgerError> | Parse the column string back into the enum. |
pub enum LessonKind | Category of a lesson row (mirrors the migration CHECK). |
LessonKind :: fn as_str(self) -> & 'static str | The exact string stored in the kind column. |
LessonKind :: fn parse(s : & str) -> Result <Self, LedgerError> | Parse the column string back into the enum. |
pub struct Decision | A decision row as stored |
Decision :: fn status_enum(& self) -> Result <DecisionStatus, LedgerError> | Typed view of the status column. |
pub struct SessionRecord | A session row as stored (summary of one working session). |
pub struct Lesson | A lesson row as stored. |
pub struct NewDecision | Input for crate::DecisionLedger::record_decision. |
NewDecision :: fn new(title : & str, context : & str, chosen : & str, rationale : & str) -> Self | Minimal constructor with the four required fields; everything else defaults (nil tenant, 'default' project, empty alternatives). |
NewDecision :: fn validate(& self) -> Result <(), LedgerError> | Boundary validation (pure, no DB): required text fields non-blank, alternatives_rejected a JSON array of {"option","reason"} objects with non-blank string values. |
pub struct NewSession | Input for crate::DecisionLedger::record_session. |
NewSession :: fn validate(& self) -> Result <(), LedgerError> | Boundary validation (pure, no DB). |
pub struct NewLesson | Input for crate::DecisionLedger::record_lesson. |
NewLesson :: fn validate(& self) -> Result <(), LedgerError> | Boundary validation (pure, no DB). |
pub struct DecisionFilter | Filter for crate::DecisionLedger::list_decisions |
fn validate_alternatives(value : & serde_json::Value) -> Result <(), LedgerError> | Validate the alternatives_rejected shape: {"option": <non-blank string>, "reason": <non-blank string>}, .... |
`service`
| Item | What it is |
|---|---|
pub struct DecisionLedger | Append-only decision/session/lesson ledger operations |
DecisionLedger :: async fn record_decision(pool : & PgPool, input : & NewDecision,) -> Result <Decision, LedgerError> | Validate and append a decision row. |
DecisionLedger :: async fn supersede(pool : & PgPool, tenant : Uuid, old_id : Uuid, new_id : Uuid,) -> Result <Decision, LedgerError> | Atomically mark old_id superseded by new_id (both must exist for tenant; old_id must be active; the supersession chain from new_id must not lead back to old_id) |
DecisionLedger :: async fn record_session(pool : & PgPool, input : & NewSession,) -> Result <SessionRecord, LedgerError> | Validate and append a session row. |
DecisionLedger :: async fn record_lesson(pool : & PgPool, input : & NewLesson) -> Result <Lesson, LedgerError> | Validate and append a lesson row (FK links checked by the DB). |
DecisionLedger :: async fn get_decision(pool : & PgPool, tenant : Uuid, id : Uuid,) -> Result <Option <Decision>, LedgerError> | Fetch one decision by id (tenant-scoped). |
DecisionLedger :: async fn list_decisions(pool : & PgPool, tenant : Uuid, filter : & DecisionFilter,) -> Result <Vec <Decision>, LedgerError> | List decisions newest-first, filtered by project / sprint / status. |
DecisionLedger :: async fn list_sessions(pool : & PgPool, tenant : Uuid, project : Option <& str>,) -> Result <Vec <SessionRecord>, LedgerError> | List sessions newest-first, optionally scoped to a project. |
DecisionLedger :: async fn list_lessons(pool : & PgPool, tenant : Uuid, project : Option <& str>, kind : Option <LessonKind>,) -> Result <Vec <Lesson>, LedgerError> | List lessons newest-first, optionally scoped to project and/or kind. |
`supersede`
| Item | What it is |
|---|---|
pub const MAX_CHAIN_DEPTH: usize | Sanity bound on supersession-chain length |
fn detect_cycle(old : Uuid, new : Uuid, chain_from_new : & Uuid) -> Result <(), LedgerError> | Decide whether superseding old by new is acyclic |
fn walk_chain <F>(start : Uuid, mut next : F) -> Vec <Uuid> where F : FnMut(Uuid) -> Option <Uuid>, | Walk a supersession chain from start using next (superseded_by lookup), returning the visited ids start, ... in order |
Re-exports. Exported here, defined elsewhere.
| Export | Defined in |
|---|---|
DecisionLedger | service::DecisionLedger |
LedgerError | error::LedgerError |
{detect_cycle,walk_chain,MAX_CHAIN_DEPTH} | supersede::{detect_cycle,walk_chain,MAX_CHAIN_DEPTH} |
{validate_alternatives,Decision,DecisionFilter,DecisionStatus,Lesson,LessonKind,NewDecision,NewLesson,NewSession,SessionRecord,NIL_TENANT,} | models::{validate_alternatives,Decision,DecisionFilter,DecisionStatus,Lesson,LessonKind,NewDecision,NewLesson,NewSession,SessionRecord,NIL_TENANT,} |
Boundary
Depends on no other workspace tier.
Shares tier operations with 40 other crates: operations-approval-workflow, operations-assessments, operations-block-imaging, operations-boot-media, operations-browser-agent-worker, operations-camera-discovery, operations-camera-liveview, operations-camera-registry, … (40 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | operations |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/operations/decision-ledger |
| Vocabulary in force (lexicon) | current |
Dependencies
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
chrono | ^0.4 | serde | no | always |
serde | ^1 | derive, derive | no | always |
serde_json | ^1 | — | no | always |
sqlx | ^0.8 | runtime-tokio, postgres, chrono, uuid, json | no | always |
thiserror | ^2 | — | no | always |
uuid | ^1 | v4, v7, serde, js, v4, serde | no | always |
Development, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
tokio | ^1 | full, macros, rt-multi-thread | no | always |
Build. None.
Depended on by. 2 workspace crates.
Signal flow — what reaches this crate, and what it reaches.
flowchart LR n_application_wiki["application-wiki"] -->|uses| SELF n_tools_corpus["tools-corpus"] -->|uses| SELF SELF["operations-decision-ledger"] classDef self fill:#1f883d,stroke:#1f883d,color:#fff; class SELF self;
Feature flags
No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.
Targets
| Kind | Name | Source |
|---|---|---|
| lib | operations_decision_ledger | `src/lib.rs` |
Error model
| Error type | Named by |
|---|---|
LedgerError | detect_cycle, ensure_schema, validate_alternatives |
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | yes |
| async runtime | none detected |
| database access | yes |
| network I/O | none detected |
| unsafe code | none detected |
| environment variables | yes |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
| Variable | Read in |
|---|---|
DECISION_LEDGER_TEST_DB | src/lib.rs |
Related capabilities
2 workspace crates depend on this one: application-wiki, tools-corpus.
Verification
| Kind | Count |
|---|---|
| Unit tests | 12 |
| Integration tests | 0 |
| Examples | 0 |
| Doctests | 0 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
crate root | 1 | 0 | 1 |
error | 1 | 0 | 1 |
models | 11 | 0 | 12 |
service | 1 | 0 | 1 |
supersede | 3 | 0 | 0 |
What the tests establish, by name:
ledger_end_to_end—src/lib.rsalternatives_must_be_array_of_option_reason—src/models.rsblank_required_fields_refused—src/models.rslesson_validation_and_kind_roundtrip—src/models.rssession_validation—src/models.rsstatus_roundtrip—src/models.rsvalid_decision_passes—src/models.rsacyclic_supersession_allowed—src/supersede.rsdirect_two_node_cycle_refused—src/supersede.rsself_supersession_is_a_cycle—src/supersede.rstransitive_cycle_refused—src/supersede.rswalk_stops_on_preexisting_loop_and_depth_bound—src/supersede.rs
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 34 | 34 |
Public modules with a //! block | 5 | 5 |
pie showData
title Public items with rustdoc
"Documented" : 34
"No rustdoc detected" : 0
Metrics
| Metric | Value |
|---|---|
| Rust source files | 6 |
| Source lines | 1434 |
| Code lines | 1087 |
| Public API items | 34 |
| Public modules | 5 |
| Tests | 12 |
| Examples | 0 |
| Cargo features | 0 |
| Direct runtime dependencies | 6 |
| Workspace reverse dependencies | 2 |
pie showData
title Public API by kind
"constant" : 2
"enum" : 3
"function" : 4
"method" : 17
"struct" : 8
pie showData
title Rust source composition
"Code" : 1087
"Blank or comment" : 347
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.