CLAUDE.md@crates/orbit-store · git:20260910.4f29985 · 2026-09-10 · sha256 1c08cdfce2c196f0
CLAUDE.md@crates/orbit-store git:20260910.4f29985A
Immutable. This exact content is served forever at /api/v1/blob/1c08cdfce2c196f0.
# orbit-store Project instructions for the persistence crate. ## One job All durable Orbit state that is not a search index: task bundles, the coordination task registry, audit and invocation events, job runs, friction, routines, reservations and workspace claims, session logs, skills, and definition files. It depends only on `orbit-types` and `orbit-common`, and it knows nothing about runtimes, commands, or transports. Not here: domain policy and authorization (`orbit-core`), the semantic vector schema (`orbit-search::vector`, which owns its own `rusqlite::Connection`), and anything that needs to *decide* rather than persist. ## Internal direction is enforced, not conventional This is one crate with a directional internal graph — the arrows in [`ARCHITECTURE.md`](../../ARCHITECTURE.md) §"orbit-store internal direction" are checked by [`scripts/check-dependency-direction.sh`](../../scripts/check-dependency-direction.sh) on every `make ci-fast`: | Layer | Owns | May not import | |---|---|---| | [`contracts`](src/contracts) | every consumer-visible trait, param, filter, and projection | any implementation, and `rusqlite` at all | | [`fs`](src/fs) | advisory locking, path safety, atomic writes, YAML | drivers, repositories, workflows | | [`driver/file`](src/driver/file) | one persistence technology: files | `driver/sqlite`, `Store`/`StoreTx`, repositories, workflows | | [`driver/sqlite`](src/driver/sqlite) | one persistence technology: SQLite | `driver/file`, repositories, workflows | | [`repository`](src/repository) | live invariants that *join* drivers | — | | [`workflow`](src/workflow) | explicit one-shot import/export/reindex/repair/upgrade | — | | [`compose`](src/compose) | construction; returns contract-facing types | — | The two drivers never call each other. When a live write spans both — a task commit is a canonical bundle write plus registry allocation/index rows — the join belongs in [`repository/task`](src/repository/task), never in a driver. A one-shot data movement is a `workflow`, not a hidden side effect of opening a store. `compose::workspace_friction_store` runs the idempotent, transactional Markdown import *before* opening the live repository, precisely so construction stays honest. The guardrail also fails if a retired ownership path (`src/backend`, `src/file`, `src/sqlite`, `src/state_io`, `src/task_migration`) reappears. Do not recreate one. ## Where a change goes - New consumer-visible capability → a trait/param/projection in `contracts`, one implementation in exactly one driver, a constructor in `compose`. - Shared atomic-write / lock / path-safety / YAML mechanics → `fs`, never a backend-shaped utility module next to a driver. - Ordinary application code consumes the contract traits. Concrete construction and migration access stay in composition, bootstrap, and maintenance adapters; [`maintenance`](src/lib.rs) is deliberately named as operator-only. ## Migrations are append-only [`driver/sqlite/migration/ledger.rs`](src/driver/sqlite/migration/ledger.rs) holds a stable ordered `MIGRATIONS` registry. **Never renumber or edit an entry that has shipped**, including across reverts and history rewrites — append a new version instead. Each migration runs in one transaction together with its ledger insert, and a database recorded newer than `SUPPORTED_SCHEMA_VERSION` is refused rather than downgraded. Feature crates get namespaced registries via [`migration/feature.rs`](src/driver/sqlite/migration/feature.rs): the feature owns its callbacks and calls `Store::apply_feature_migrations` before exposing its API; its versions are independent of the global schema version. Use that seam rather than adding a feature's tables to the global ledger. ## Tests Sibling `tests/` directories throughout ([`test_layout.md`](../../docs/design-patterns/test_layout.md)); the direction guardrail excludes `**/tests/**`, so a test may legitimately reach across layers to build a fixture while production code may not. There is no crate-root `tests/` directory — a change that seems to need one is usually a change that belongs behind `contracts` and can be tested through `compose`.