Skip to main content

dbmd_core/
lib.rs

1//! `dbmd-core` — the reference library for **db.md**, the open database in
2//! plain files.
3//!
4//! db.md is one directory: raw evidence in `sources/`, atomic typed data plus
5//! curator-synthesized conclusions (`meta-type: conclusion`) in `records/`, and
6//! a single `DB.md` config file at the root. Records are markdown files with
7//! YAML frontmatter;
8//! relationships are wiki-links; the index is the derived, write-through
9//! `index.md` / `index.jsonl` catalog plus embedded ripgrep.
10//!
11//! This crate owns **all** toolkit logic. The `dbmd` binary (`dbmd-cli`) is a
12//! thin wrapper that parses args, calls into here, and formats output. Any
13//! Rust tool wanting to be db.md-aware can `cargo add dbmd-core` and get the
14//! full library — the same shape as ripgrep, where the `grep`/`ignore` libs do
15//! the work and `rg` is a thin CLI.
16//!
17//! # Hard invariants this crate is built to uphold
18//!
19//! - **Zero AI/LLM dependencies.** No provider SDKs, no API keys, no model
20//!   calls, no embeddings, no vectors, no ANN — anywhere, ever. The agent
21//!   driving `dbmd` is the semantic layer; `dbmd` is a deterministic tool.
22//! - **The interactive loop is O(changed), never O(store).** Loop ops
23//!   ([`graph::backlinks`], [`validate::validate_working_set`],
24//!   [`index::Index::on_write`], …) never call [`store::Store::walk`] on a
25//!   non-empty changed set. The one documented exception is
26//!   [`validate::validate_working_set`], which falls back to a full sweep only
27//!   when handed an empty changed set (the vacuous-pass guard). Whole-store
28//!   walks otherwise belong only to SWEEP ops ([`validate::validate_all`],
29//!   [`index::Index::rebuild_all`], [`stats`]).
30//! - **Wiki-links are full store-relative paths.** A short-form wiki-link is a
31//!   validation error ([`validate`] code `WIKI_LINK_SHORT_FORM`).
32//! - **Embedded ripgrep.** Free-text body search uses the `grep` + `ignore`
33//!   crates in-process; the toolkit never bundles or shells out to `rg`.
34//!   Structured loop reads ([`graph::backlinks`], [`query::Query`]) ride the
35//!   `index.jsonl` sidecars instead, never a frontmatter tree scan.
36
37pub mod assets;
38pub mod edit;
39pub mod emit;
40pub mod extract;
41pub mod fsx;
42pub mod graph;
43pub mod index;
44// The link.md CLIENT (feature `link`, default-on): the five interconnect
45// verbs — resolve / sync / grant / propose / subscribe — spoken against a
46// user-configured hub. One binary, two specs (the git precedent); the db.md
47// FORMAT is untouched — a store never needs link.md to be valid db.md, and a
48// format-only consumer drops this module (and its HTTP/TLS closure) with
49// `default-features = false`. Deliberately NOT re-exported at the crate root:
50// the root re-exports are the format toolkit's locked interface, and the wire
51// client reads best module-qualified (`linkmd::HubConfig`).
52// The embedded micro-harness (feature `harness`, off core's defaults; the
53// `dbmd` binary requests it): `ask` / `do` / `build` — a stateless
54// tool-calling loop running the USER'S OWN model endpoint against the store's
55// verb surface. Client for user-supplied intelligence only: three hand-rolled
56// wire protocols over the same `ureq` the link client uses (OpenAI-compatible
57// Chat Completions, Anthropic Messages, the ChatGPT backend's Responses), no
58// SDK crates, no default vendor. API keys come from the environment only;
59// the two subscription paths mint short-lived tokens instead, and neither
60// ever reads a credential out of a store. The db.md FORMAT is
61// untouched — a store never needs a model to be valid db.md, and the verbs
62// stay deterministic plumbing (AGENTS.md "Hard rules" carries the covenant).
63#[cfg(feature = "harness")]
64pub mod harness;
65#[cfg(feature = "link")]
66pub mod linkmd;
67#[cfg(feature = "link")]
68mod linkmd_sync_policy;
69#[cfg(feature = "link")]
70pub mod linkmd_v2;
71pub mod log;
72pub mod parser;
73pub mod projection;
74pub mod query;
75pub mod render;
76pub mod stats;
77pub mod store;
78pub mod summary;
79pub mod time;
80pub mod ulid;
81pub mod validate;
82pub mod watch;
83
84// ── Shared public types, re-exported at the crate root ──────────────────────
85//
86// These are the locked interface every other crate and module builds against.
87
88pub use assets::{AssetRecord, Declaration, ScanReport, StatusReport, VerifyReport};
89pub use extract::{ExtractError, Extracted, Format, MetaValue};
90pub use fsx::{write_atomic, write_atomic_new};
91pub use graph::ContextSlice;
92pub use index::{Index, IndexLevel, IndexRecord};
93pub use log::{Log, LogEntry, LogKind};
94pub use parser::{
95    Config, FieldSpec, Frontmatter, MarkdownLink, ParseError, Schema, Section, Shape, WikiLink,
96};
97pub use query::Query;
98pub use render::{Outline, Tree};
99pub use store::{infer_type_from_path, layer_for_type, Layer, NotAStore, Store, StoreError};
100pub use time::now;
101pub use validate::{Issue, Severity};
102
103/// Crate-wide result alias over [`Error`].
104pub type Result<T> = std::result::Result<T, Error>;
105
106/// Top-level error for `dbmd-core` operations.
107///
108/// Module-specific errors ([`ParseError`], [`StoreError`], [`NotAStore`])
109/// convert into this so a CLI command can bubble a single error type while
110/// preserving the structured variant for `--json` rendering.
111#[derive(Debug, thiserror::Error)]
112pub enum Error {
113    /// The path is not a db.md store (no `DB.md` at the root). Surfaced as the
114    /// machine-parseable code `NOT_A_STORE` with a non-zero exit.
115    #[error(transparent)]
116    NotAStore(#[from] NotAStore),
117
118    /// A store-level operation failed (walk, locate, shard, sidecar read).
119    #[error(transparent)]
120    Store(#[from] StoreError),
121
122    /// A markdown / frontmatter / `DB.md` parse failed.
123    #[error(transparent)]
124    Parse(#[from] ParseError),
125
126    /// A write was refused by a `DB.md ## Policies` rule (e.g. a frozen page).
127    /// Carries the structured validation code so the CLI can emit it verbatim.
128    #[error("write refused by policy ({code}): {message}")]
129    Policy {
130        /// The structured issue code, e.g. `"POLICY_FROZEN_PAGE"`.
131        code: &'static str,
132        /// Human-readable explanation.
133        message: String,
134    },
135
136    /// An underlying I/O failure.
137    #[error(transparent)]
138    Io(#[from] std::io::Error),
139}