Skip to main content

prov/
lib.rs

1//! # prov
2//!
3//! A *self-describing plaintext workspace*: a set of documents whose structure
4//! lives in the documents' own embedded metadata (frontmatter), not in the
5//! filesystem layout or an app-private sidecar folder.
6//!
7//! The name is the point. A *prov* is the note in which a book describes its
8//! own making — the type, the paper, the press. A prov workspace is one you
9//! can hand to any tool and it explains itself: follow the links in the metadata
10//! and the whole structure unfolds, with a distinguished root that describes the
11//! whole.
12//!
13//! ## The shape of the abstraction
14//!
15//! - **Documents** are plaintext files with an embedded metadata block
16//!   ([`document::Document`]).
17//! - **Relations** are named links declared in that metadata
18//!   ([`relation::RelationSet`]). *Which* fields are links is configurable
19//!   (`contents`/`part_of`, `links`, or your own vocabulary); the mechanism is
20//!   not. Exactly one relation may be marked **spanning** — the single-parent
21//!   tree that gives the workspace its self-describing discovery spine. Every
22//!   other relation may be many-to-many, so the tree is a backbone, never a
23//!   ceiling.
24//! - **Identity** is a strictly-additive layer ([`identity`], [`index`]). The
25//!   graph, traversal, and (eventually) mutation operate on *paths* and never
26//!   require an ID. Turn identity off and it compiles out; turn it on and IDs
27//!   are minted only when something durably refers to a document.
28//!
29//! ## Status
30//!
31//! Early extraction from `diaryx_core`. The pure layers — embedded-metadata
32//! parsing ([`meta`]), document splitting, and relation extraction — are real
33//! and tested. The filesystem-driven scan/traversal/mutation engine ports next;
34//! its seams ([`workspace::Workspace`], [`identity::IdentityPolicy`],
35//! [`index::IndexStore`]) are staked out here so nothing diaryx-specific leaks
36//! into the eventual public API.
37
38// At least one embedded-metadata format backend must be compiled in, otherwise
39// prov can neither parse nor serialize any metadata. The format features
40// (`yaml`, `json`, `toml`, `fig-lang`) forward to the matching `fig` parser —
41// see `Cargo.toml`.
42#[cfg(not(any(
43    feature = "yaml",
44    feature = "json",
45    feature = "toml",
46    feature = "fig-lang"
47)))]
48compile_error!(
49    "prov needs at least one metadata-format feature enabled: \
50     `yaml` (the default), `json`, `toml`, or `fig-lang`. \
51     You have disabled the default feature without selecting a replacement."
52);
53
54pub mod about;
55pub mod attach;
56pub use prov_config as config;
57pub mod discovery;
58/// Content fixity — the coverage policy, the digest, and the predicates that
59/// separate a verified hash from an unverifiable one.
60pub use prov_graph::fixity;
61#[cfg(test)]
62mod fs_faults;
63pub mod intake;
64pub mod manifest;
65pub mod mutate;
66pub mod remedy;
67pub mod route;
68pub mod validate;
69pub use prov_config::vocabulary;
70pub mod workspace;
71
72/// The read core, re-exported whole.
73///
74/// `prov` is `prov-graph` plus the verbs. A consumer that needs both should
75/// depend on `prov` alone and reach everything through here; a consumer that
76/// only traverses can depend on `prov-graph` directly and link none of the
77/// mutation or config machinery.
78pub use prov_graph;
79/// Identity — the id type, its check-character verification, and the
80/// registration/minting policy.
81///
82/// All of it lives in the read core: none of it touches storage, so none of it
83/// needs to sit above the read boundary. The *write* that consumes a mint,
84/// [`Workspace::register`](workspace::Workspace::register), is in
85/// [`workspace`].
86pub use prov_graph::identity;
87/// The body-prose parser, re-exported whole — `prov-graph`'s, forwarded here
88/// so a consumer that depends on `prov` alone reaches the same twig the bodies
89/// were parsed with rather than pinning a second one. See
90/// [`prov_graph::twig`] for why the coupling is accepted.
91pub use prov_graph::twig;
92pub use prov_graph::{
93    Addressing, Backlink, Body, BodyLink, Cardinality, CensusEntry, Collision, ContentFormat,
94    DirEntry, Document, Edge, EmbedStyle, EmbedType, Error, ExtKind, FileType, Format, Graph, Id,
95    IdIndex, IdStorage, Link, LinkSite, LinkStyle, Manifest, ManifestEntry, Mapping, MetaCarrier,
96    Metadata, NoIndex, NoPeers, Node, NodeKind, Notation, PathStyle, PeerLocation, PeerLookup,
97    PeerResolver, ReadScope, ReadSettings, ReadStorage, ReferenceStyle, Relation, RelationSet,
98    Resolution, Result, StdFs, StructuralFact, Target, TitleIndex, TitleMatch, TreeOptions,
99    Unconfirmed, Value, Walk, Wikilink, Wrapper, block_on, code_spans, embed_carrier,
100    embed_style_of, escapes_root, format_link, is_opaque_payload, path_to_title, reachable_set,
101    render_html, require_whole_file,
102};
103/// The read core's modules, re-exported at their original paths so `prov`'s
104/// public API is exactly what it was before the split.
105pub use prov_graph::{
106    content, document, error, exec, graph, link, memo, meta, peer, relation, title,
107};
108/// Metadata editing, at the path it had before the write surface moved out of
109/// the read core into `prov-store`.
110pub use prov_store::edit;
111pub use prov_store::{
112    Capabilities, Durability, FileIndex, InMemoryFs, InMemoryIndex, IndexStore, Rebase, Storage,
113    SyncGuarantee,
114};
115
116/// The filesystem port — both halves.
117///
118/// The read surface ([`ReadStorage`](prov_graph::fs::ReadStorage) and the types
119/// it answers with) is `prov-graph`'s; the write surface
120/// ([`Storage`](prov_store::fs::Storage) and the durability vocabulary) is
121/// `prov-store`'s. They live in separate crates so a read-only consumer can
122/// depend on the first without linking the second, and are rejoined here
123/// because `prov` is the layer that does both.
124pub mod fs {
125    pub use prov_graph::fs::{DirEntry, FileType, Metadata, ReadStorage, StdFs};
126    pub use prov_store::fs::{
127        Capabilities, Durability, InMemoryFs, Storage, SyncGuarantee, memory,
128    };
129}
130
131/// The ID index — both halves, split across two crates for the same reason
132/// [`fs`] is.
133pub mod index {
134    pub use prov_graph::index::{Collision, IdIndex, NoIndex};
135    pub use prov_store::index::{FileIndex, InMemoryIndex, IndexStore, Rebase};
136}
137
138pub use about::AboutContext;
139/// Transaction primitives, retained at their original paths for compatibility.
140pub mod change {
141    pub use fs_transaction::change::{ChangeSet, FileOp};
142}
143/// Journal recovery, retained at its original path for compatibility.
144pub mod journal {
145    use prov_graph::error::Result;
146    use prov_store::fs::Storage;
147    use std::path::Path;
148
149    pub use fs_transaction::journal::{Journal, Recovered, decode, encode};
150
151    /// The name of prov's write-ahead journal: a single transient dotfile at
152    /// the workspace root, present only between a change set's commit point
153    /// and its completion.
154    pub const JOURNAL_NAME: &str = ".prov-journal";
155
156    /// prov's write-ahead journal — [`JOURNAL_NAME`] at the workspace root.
157    ///
158    /// Named rather than [`Journal::default()`], for two reasons that point the
159    /// same way. The name is part of prov's documented on-disk shape, so a user
160    /// who finds it in their workspace can look it up; and it predates the
161    /// extraction of `fs-transaction`, so a workspace that a crash
162    /// interrupted before an upgrade is carrying its journal under exactly this
163    /// name — and recovery has to still find it, or that change is stranded
164    /// half-applied with no record of how to finish.
165    ///
166    /// Every prov apply and every prov recovery goes through this one value, so
167    /// the two cannot disagree about where to look.
168    pub fn workspace_journal() -> Journal {
169        Journal::named(JOURNAL_NAME).expect("JOURNAL_NAME is a single path component")
170    }
171
172    /// Finish any change set a crash left journaled at `root`, rolling the
173    /// workspace forward to the fully-applied state, then remove the journal.
174    ///
175    /// A no-op when no journal is present, so it is cheap to call
176    /// unconditionally — `prov check` runs it before it reads anything, so an
177    /// interrupted mutation heals before it is diagnosed.
178    pub async fn recover<FS: Storage>(fs: &FS, root: &Path) -> Result<Recovered> {
179        Ok(workspace_journal().recover(fs, root).await?)
180    }
181}
182pub use config::{
183    About, ConfigIssue, ConfigIssueKind, FIELD_TYPES, FieldSpec, Fixity, OpenClosed, RelationDef,
184    RelationStyleConfig, WorkspaceConfig, diagnose, field_type_as_config_str,
185    field_type_from_config_str, is_valid_scope_path, is_valid_workspace_id,
186    metadata_format_from_str, metadata_format_str, spec_ahead,
187};
188pub use discovery::{Discovered, Discovery, discover};
189/// Declarative views over the workspace — the `views:` config axis, the
190/// traversal that selects the documents one covers, and the pure grouping over
191/// what it selected.
192///
193/// Re-exported at prov's own path so a consumer that already depends on prov
194/// need not add a second crate to read the views its config carries, and so the
195/// two cannot resolve to different versions of `ViewSpec`. A consumer that
196/// wants *only* views — a renderer, a browser view — should depend on
197/// `prov-views` directly instead: it reaches nothing that can write.
198pub mod views {
199    pub use prov_views::{
200        CONDITION_KEYS, Condition, Error, Grain, Group, Grouping, Row, RowSet, Selection,
201        VIEW_KEYS, VIEWS_KEY, ViewIssue, ViewIssueKind, ViewSpec, diagnose_view, diagnose_views,
202        group, select, views_from,
203    };
204}
205/// Named, closed-by-default document sets that may leave the workspace — the
206/// `exports:` config axis, and the plan that composes a gate with a view.
207///
208/// Re-exported at prov's own path for the same reasons [`views`] is. prov
209/// itself never consumes a plan: what an [`ExportPlan`](exports::ExportPlan)
210/// feeds — a publish step, a copy-out, an OCFL export — lives downstream, and
211/// the invariant (an export is a subset of what its gate admits) lives in
212/// `prov-exports` with the planner.
213pub mod exports {
214    pub use prov_exports::{
215        EXPORT_KEYS, EXPORTS_KEY, Error, ExportDoc, ExportIssue, ExportIssueKind, ExportPlan,
216        ExportSpec, GATE_KEYS, Gate, Withheld, compose, diagnose_export, diagnose_exports,
217        exports_from, plan,
218    };
219}
220/// The field-type vocabulary a `fields.<name>.type` declaration is spelled in,
221/// re-exported so a consumer can name types without depending on `fig-schema`
222/// (or, for [`ExtKind`], on `fig`) directly — and so neither can drift to a
223/// different version than the one prov resolves against.
224pub use fig_schema::FieldType;
225pub use fs_transaction::{ChangeSet, FileOp};
226pub use identity::{
227    IdentityPolicy, Minter, NoIdentity, Registration, Trigger, WORKSPACE_NAME_LEN,
228    mint_workspace_id,
229};
230pub use intake::{Adoption, PlanOutcome, StructurePlan, SynthNode};
231pub use journal::{Recovered, recover};
232pub use manifest::{ManifestStatus, ManifestUpdate};
233pub use mutate::{ContentState, Created, Diagnosis, Reparented};
234pub use prov_exports::ExportSpec;
235pub use prov_views::ViewSpec;
236pub use remedy::{Fix, Remedy, RemedyKind, Warrant};
237pub use route::{Layout, RoutePlan};
238pub use validate::{CheckDiff, Finding};
239pub use vocabulary::{Term, Vocabulary};
240pub use workspace::{Ignore, IgnoreList, Reason, Settings, Workspace, WorkspaceBuilder};