pub struct Store {
pub root: PathBuf,
pub config: Config,
}Expand description
An opened db.md store: its root path plus the parsed DB.md Config.
Construct via Store::open; that is the only path in, and it validates
the DB.md marker so downstream code can assume a real store.
Fields§
§root: PathBufThe store root (the directory containing DB.md).
config: ConfigThe parsed DB.md config (agent instructions, policies, schemas).
Implementations§
Source§impl Store
impl Store
Sourcepub fn is_db_md_store(path: &Path) -> bool
pub fn is_db_md_store(path: &Path) -> bool
True if path is a db.md store root: an uppercase DB.md file exists
at path. On case-sensitive filesystems a lowercase db.md must NOT
count (the lowercase name refers to the project/spec, not the marker).
Sourcepub fn open(path: &Path) -> Result<Store, NotAStore>
pub fn open(path: &Path) -> Result<Store, NotAStore>
Open path as a db.md store: confirm the DB.md marker (else
NotAStore) and parse the DB.md config. Every store-walking
subcommand opens through here.
Sourcepub fn walk(&self) -> Result<Vec<PathBuf>, StoreError>
pub fn walk(&self) -> Result<Vec<PathBuf>, StoreError>
SWEEP. Recursively iterate every .md content file across
sources/, records/, and wiki/, skipping hidden dirs and log/.
Used only by validate --all, index rebuild, and stats — never on
the interactive loop.
Sourcepub fn walk_layer(&self, layer: Layer) -> Result<Vec<PathBuf>, StoreError>
pub fn walk_layer(&self, layer: Layer) -> Result<Vec<PathBuf>, StoreError>
SWEEP. Like Store::walk but scoped to a single layer.
Sourcepub fn walk_type_folder(
&self,
type_folder: &Path,
) -> Result<Vec<PathBuf>, StoreError>
pub fn walk_type_folder( &self, type_folder: &Path, ) -> Result<Vec<PathBuf>, StoreError>
Enumerate every .md file in a single type-folder, recursing through
its date-shards (sources/emails/**/*.md). The unit the index builder
and per-folder rebuild operate on. SWEEP-class (scoped to one folder).
Sourcepub fn recent_in_type_folder(
&self,
type_folder: &Path,
n: usize,
) -> Result<Vec<PathBuf>, StoreError>
pub fn recent_in_type_folder( &self, type_folder: &Path, n: usize, ) -> Result<Vec<PathBuf>, StoreError>
The ≤n most-recent files in a type-folder by frontmatter updated
(descending), ties broken by store-relative path (ascending) — a total
order, so write-through and rebuild never disagree on #500 vs #501.
Reads updated across the folder’s shards — a SWEEP cost absorbed into
index rebuild. The write-through path never calls this. The
cap-selection primitive for the 500-entry index.md browse view.
Sourcepub fn type_shards(&self, type_: &str) -> bool
pub fn type_shards(&self, type_: &str) -> bool
The shard/flat predicate: true if the type date-shards, false if it
stays flat. True for source types and event record types
(expense/invoice/meeting + custom order/ticket/transaction),
or when DB.md ## Schemas declares shard: by-date. False for
dedup-bounded entity types (contact/company/decision) and wiki/.
Sourcepub fn shard_path_for(
&self,
type_: &str,
frontmatter: &Frontmatter,
name: &str,
) -> Result<PathBuf, StoreError>
pub fn shard_path_for( &self, type_: &str, frontmatter: &Frontmatter, name: &str, ) -> Result<PathBuf, StoreError>
Compute the canonical write path for a new file. For a sharding type
(per Store::type_shards) insert <YYYY>/<MM>/ from the type’s
primary date field (email.date, expense.date, … fallback created)
under the type folder; flat types and wiki/ get no shard segment.
Deterministic + stable: same input → same path, so a record never moves
once written.
Sourcepub fn shard_path_in(
&self,
folder: &Path,
type_: &str,
frontmatter: &Frontmatter,
name: &str,
) -> Result<PathBuf, StoreError>
pub fn shard_path_in( &self, folder: &Path, type_: &str, frontmatter: &Frontmatter, name: &str, ) -> Result<PathBuf, StoreError>
Like Store::shard_path_for, but compute the path under an explicit,
caller-resolved type-folder rather than the canonical default. This lets a
write surface honour an agent-supplied conforming sub-folder — e.g.
wiki/projects/, wiki/people/, wiki/synthesis/ (the SPEC files a
wiki-page under wiki/<topic>/, i.e. ANY topic sub-folder, not only the
wiki/topics default) — while still applying date-sharding for sharding
types. The folder must be a conforming <layer>/<type-folder> (2
components, recognized layer); the caller is responsible for that (see the
CLI’s resolve_write_path), so it is taken as given here.
Sharding is still a property of the type: a sharding type gets the
<YYYY>/<MM> segment under folder; a flat type lands directly in it.
Sourcepub fn find_links_to(&self, target: &Path) -> Result<Vec<PathBuf>, StoreError>
pub fn find_links_to(&self, target: &Path) -> Result<Vec<PathBuf>, StoreError>
Find files with an incoming wiki-link to target, via embedded
ripgrep for [[target]] across all layers. Loop-fast; no whole-graph
build. Returns store-relative paths.
Sourcepub fn find_links_to_any(
&self,
targets: &[PathBuf],
) -> Result<Vec<PathBuf>, StoreError>
pub fn find_links_to_any( &self, targets: &[PathBuf], ) -> Result<Vec<PathBuf>, StoreError>
Find every file with an incoming wiki-link to any of targets, in a
single embedded-ripgrep pass over the store (one .md walk, one
presence-only scan per file). This is the batch incoming-linker finder the
working-set crate::validate::validate_working_set sits on: it must find
the linkers for the whole changed set without paying a full store read
per changed object. Cost is therefore one store scan (O(store)), NOT
targets.len() × store — calling find_links_to
in a loop would reread every .md once per target and is the exact
O(changed × store) blow-up this method exists to prevent. Returns
store-relative paths (deduped, sorted).
Why content scan and not the sidecar links field: the sidecar projects
only the frontmatter links: array, so it misses edges written in the
body or in typed fields (company: [[…]]). Finding an incoming link to an
arbitrary path therefore requires reading file content — the same reason
the single-target finder uses ripgrep.
Sourcepub fn find_by_type(&self, type_: &str) -> Result<Vec<IndexRecord>, StoreError>
pub fn find_by_type(&self, type_: &str) -> Result<Vec<IndexRecord>, StoreError>
Candidate set for a type query: read the relevant type-folder
index.jsonl sidecar(s) and return their records. Complete and
cold-cache-proof — NOT a walk-and-parse or a frontmatter ripgrep scan,
and never a store-wide read. The common path is one sequential read
of the canonical type-folder sidecar (O(entities)); when that sidecar is
absent the read is bounded to the type’s single layer subtree
(O(entities-in-layer)), so a --type proposal query before that folder
has been indexed still stays inside the interactive loop’s O(entities)
contract instead of fanning out across every sidecar in the store.
Sourcepub fn find_by_where(
&self,
key: &str,
value: &str,
) -> Result<Vec<IndexRecord>, StoreError>
pub fn find_by_where( &self, key: &str, value: &str, ) -> Result<Vec<IndexRecord>, StoreError>
Candidate set for a key=value frontmatter query, store-wide: read
every type-folder index.jsonl sidecar and filter their records. The
unscoped pre-write dedup primitive; prefer Store::find_by_where_in
with a layer scope to stay O(entities-in-layer) on the interactive loop.
Sourcepub fn find_by_where_in(
&self,
key: &str,
value: &str,
layer: Option<Layer>,
) -> Result<Vec<IndexRecord>, StoreError>
pub fn find_by_where_in( &self, key: &str, value: &str, layer: Option<Layer>, ) -> Result<Vec<IndexRecord>, StoreError>
Candidate set for a key=value frontmatter query, scoped to one
layer when layer is Some: the sidecar walk is confined to that
layer’s subtree (<root>/<layer>/), so the I/O is O(entities-in-layer),
not O(store records). None keeps the store-wide read.
This is what makes --in <layer> an I/O scope, not just a result
filter: a --where-only query (no --type) used to read every sidecar
in the store and narrow by layer in memory, breaking the O(entities)
contract the interactive loop depends on. With a layer in hand we walk
only that layer’s sidecars.
Sourcepub fn sidecar_records(
&self,
layer: Option<Layer>,
) -> Result<Vec<IndexRecord>, StoreError>
pub fn sidecar_records( &self, layer: Option<Layer>, ) -> Result<Vec<IndexRecord>, StoreError>
Every record across the type-folder index.jsonl sidecars, scoped to one
layer when layer is Some (the walk is confined to <root>/<layer>/)
else store-wide. Sequential, complete sidecar reads — never a
walk-and-parse of the content tree.
This is the unfiltered sidecar-enumeration primitive the relationship
loop sits on: crate::graph::backlinks_filtered uses it to bound its
candidate set to the relevant layer (or the whole store) without opening
the content tree, then confirms each candidate’s edge by parsing the file.
Sourcepub fn read_type_index(
&self,
index_jsonl: &Path,
) -> Result<Vec<IndexRecord>, StoreError>
pub fn read_type_index( &self, index_jsonl: &Path, ) -> Result<Vec<IndexRecord>, StoreError>
Parse a type-folder’s index.jsonl into IndexRecords, applying
last-write-wins by path over any un-compacted lines. The sidecar-read
primitive every structured query sits on.