Skip to content

feat(cloud): first-party OreCloud client — pip install lodedb[cloud], no separate package#90

Merged
Davidobot merged 8 commits into
Egoist-Machines:mainfrom
idas0:feat/cloud-extra
Jul 18, 2026
Merged

feat(cloud): first-party OreCloud client — pip install lodedb[cloud], no separate package#90
Davidobot merged 8 commits into
Egoist-Machines:mainfrom
idas0:feat/cloud-extra

Conversation

@idas0

@idas0 idas0 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Adds the OreCloud client as first-party code. pip install "lodedb[cloud]" installs lodedb plus httpx and pynacl — the extra is a dependency list, not a second package.

from lodedb import LodeDB

db = LodeDB("./notes")          # local, as always
db = LodeDB.cloud("user-42")    # managed cloud, same verbs

Changes

  • src/lodedb/cloud/ — the Python client: Client (one credential, one bound org/environment), connect/CloudStore (duck-types the local verb surface), transfer glue, login config, agent scaffolds, and the lodedb cloud CLI. The package __init__ resolves every name lazily (PEP 562), so a plain import lodedb never loads httpx.
  • crates/lodedb-cloud-core — the native transfer plane (artifact stores over directories and S3-compatible object storage, generation inventory/diff, managed plan/materialize, redaction policy). Joins the workspace with a path dep on lodedb-core instead of a pinned git rev. The pin bump surfaced one real gap: the 1.3.2 tvvf rescore sidecar was unknown to the transfer inventory (fail-closed refusal on push) — tvvf now ships like tvim, with a regression test.
  • lodedb._turbovec.cloud — the PyO3 binding compiled into the existing bundled extension as a submodule, so push/pull/sync ship in the same wheel as the engine.
  • Entry pointsLodeDB.cloud(target, **options) (bare store id, org/environment/store triple, or orecloud:// URL; options mirror connect() via inspect.signature, local-only kwargs get a targeted error); LodeDB("orecloud://…") constructor dispatch for config-driven code; from lodedb.cloud import Client; lodedb cloud …. All funnel through lodedb.cloud.open_cloud_target and raise a pip install "lodedb[cloud]" hint when the deps are absent.
  • [cloud] extra — now ["httpx>=0.27", "pynacl>=1.5"]; the import-boundary probe forbids httpx/nacl on plain import and imports lodedb.cloud itself in the check. Deliberately not in [all].
  • Tests — the client suites move in from the OreCloud repo (real-engine push/status/verify/pull round trips, sync lineage incl. divergence + force flags, CLI JSON contract, connect target grammar, CloudStore verb wiring, the LodeDB-surface parity audit, env-file and scaffold rules), plus reworked constructor/import-root/trampoline suites.

Verification

  • 75 cloud tests + import-boundary suite green; core local suites green; ruff clean.
  • cargo test -p lodedb-cloud-core (60+ tests incl. the new tvvf coverage) and clippy green.
  • Live smoke: LodeDB.cloud(…) and LodeDB("orecloud://…") return lodedb.cloud.serving.CloudStore; a local store pushed to a directory remote, pulled into a fresh dir, reopened, and get() returned the original text — all through the bundled native core.

Follow-up (OreCloud repo, after merge + release)

The server switches its orecloud._core imports to lodedb._turbovec.cloud, the repo drops python/ + crates/, and the orecloud 0.1.0 distribution on PyPI is retired. Egoist-Machines/OreCloud#3 was superseded by this PR and closed.

idas0 added 2 commits July 17, 2026 15:53
…dd-on

pip install "lodedb[cloud]" installs the proprietary orecloud client;
lodedb cloud <args> forwards untouched to its CLI, printing the install
hint (exit 1) when the extra is absent. The import lives only inside the
trampoline: a plain `import lodedb` stays network-free, now guarded by the
import-boundary test (orecloud joins the forbidden-on-import list). The
extra is deliberately NOT part of [all] — a commercial client should be an
explicit choice, never a side effect.

Sequencing: this cannot merge before orecloud 0.1.0 exists on PyPI — the
extra is unresolvable until then (uv lock/CI fail closed on it).
@idas0
idas0 marked this pull request as ready for review July 17, 2026 23:44
idas0 added 3 commits July 17, 2026 17:15
LodeDB("orecloud://org/environment/store") now returns the cloud
companion's store handle (same add/search/get/remove verbs over HTTPS)
instead of requiring a separate orecloud entry point, making LodeDB(...)
the single front door for local and managed stores. The companion import
stays inside the cloud branch (install hint when absent), local-only
construction options are rejected with a targeted error, and the accepted
option set is derived from orecloud.connect's own signature so the two
cannot drift.
The settled API shape: LodeDB.cloud("user-42") is the human-facing entry
point (bare store id — org/environment resolve from the credential; the
org/environment/store triple and orecloud:// URLs work too), joining the
open_readonly/open_vector_store alternate-constructor family. The
orecloud:// dispatch in LodeDB(...) stays for config-driven cases where one
string field must express either a local path or a cloud store; both entry
points funnel through lodedb.cloud.open_cloud_target (lazy import, install
hint, connect-signature-derived option allowlist). lodedb/cloud.py is also
a PEP 562 proxy so `from lodedb.cloud import Client` gives docs a single
import root. A plain `import lodedb` still never loads the companion.
… deps-only extra

Management call: no separate cloud package. The client is now first-party,
following exactly the [mcp] pattern (code in the wheel, the extra installs
only third-party deps):

- crates/lodedb-cloud-core (was OreCloud's orecloud-core): the native
  transfer plane — ArtifactStore over local dirs and object storage,
  generation inventory/diff, managed plan/materialize, redaction policy.
  Its lodedb-core git-pin becomes a path dep, and building against 1.3.2
  surfaced a real gap the pin had hidden: the new tvvf rescore sidecar was
  unknown to the inventory (fail-closed refusal), so tvvf joins STORE_KINDS
  and ships like tvim — rescore stores now transfer completely.
- The PyO3 binding grafts into the bundled extension as the
  lodedb._turbovec.cloud submodule — push/pull/sync ride the same wheel as
  the engine; no second distribution, no version dance.
- src/lodedb/cloud/ (was python/orecloud/): Client, CloudStore/connect,
  the transfer glue (transfer.py), the full CLI, login config, and the
  agent scaffolds — the lazy proxy module becomes the real package, with a
  PEP 562 __init__ so a plain `import lodedb` never touches httpx/pynacl
  (import-boundary probe now forbids those instead of orecloud). The
  `lodedb cloud` trampoline forwards to lodedb.cloud.cli.
- cloud = ["httpx>=0.27", "pynacl>=1.5"]; orecloud leaves the lockfile.
- The client test suites move in (transfer round trips through the real
  engine, sync lineage, CLI, connect targets, CloudStore verb wiring, the
  LodeDB-surface parity audit, env-file and scaffold rules) alongside the
  reworked seam suites — 75 cloud tests plus the crate's Rust suite.
@idas0 idas0 changed the title feat(cli): [cloud] extra + lodedb cloud — OreCloud as an add-on feat(cloud): first-party OreCloud client — pip install lodedb[cloud], no separate package Jul 18, 2026
Inventory correctness:
- inventory tvvf by the sidecar's own epoch: the engine writes
  vf<vf_epoch>.tvvf (tvvf_store::base_path), not g<base_epoch>.tvvf, so the
  derived name failed every transfer verb on real rescore stores and the
  regression test had fabricated the wrong shape; journal reconstruction now
  uses the same naming, a real-engine rescore round trip pins it end to end,
  and tvvf maps to the "vector" wire kind (unknown engine kinds now fail
  closed instead of shipping as "state")
- validate every manifest digest as 64 lowercase hex at the inventory:
  digests become staging file names and object keys, so a path-shaped
  "digest" in a remote body could otherwise escape the pull staging
  directory (Path(staging) / sha swallows absolute paths)

Restore safety:
- local restores (pull, sync pull, managed materialize) run under the
  engine's single-writer lock (lodedb-core newly exports
  engine::acquire_dir_writer_lock), so a live writer and a restore can no
  longer interleave and lose either side's commit
- restores refuse when the destination WAL holds acknowledged records (new
  PendingWal error); sync --force-pull explicitly truncates them after the
  swap; the WAL probe path is confined to the store root (the index key can
  arrive from CLI or remote input)
- the candidate generation is verify-opened from a hardlinked scratch layout
  BEFORE the pointer swap (stage/publish split of export_generation_pinned),
  so a checksum-consistent but semantically unopenable generation never
  replaces a working one; journal manifests are rebuilt strictly after the
  swap, and the remaining narrow crash windows are documented on
  restore_staged
- managed materialization pins the classified local snapshot
  (expected_local_snapshot_id; "" pins classified-as-absent): a local commit
  landing between classification and materialization refuses with a sync
  conflict instead of being overwritten, matching the dumb sync's pinned CAS;
  the pull-branch WAL check is a lazy FFI call (local_wal_ops), so push and
  status planning never pay an O(WAL-bytes) scan

Client and CLI:
- native sync refusals (diverged lineage, pending WAL) exit 5 like their
  managed 409 twins, via the new lodedb._turbovec.cloud.SyncConflictError
- push aborts its server-side session on failure instead of leaving it to
  expire; connect(warm=True) closes the pool on transport errors;
  unprovisioned search_many_by_vector keeps per-query cardinality;
  orecloud:// targets with empty segments are rejected instead of silently
  reinterpreted; token writes into an existing .env always end owner-only
  (0600) through a unique O_EXCL scratch file; the generated agent docs
  describe the SDK's actual write semantics; the README quickstart links the
  directory and lists keys before syncing

Testing:
- cargo test -p lodedb-cloud-core: 15 suites green, with new coverage for
  the real tvvf shape, malformed digests, writer-lock contention, the
  pending-WAL refusal and force-pull discard, stale-classification refusal,
  WAL path containment, and rejection of a semantically invalid generation
- .venv/bin/python -m pytest tests/: cloud + import-boundary + core local
  suites green (the only failures in a slim venv are the missing embeddings
  extras, identical on main); repo-wide ruff clean; clippy clean
idas0 added 2 commits July 18, 2026 15:13
… it)

The moved client suites failed CI collection with ModuleNotFoundError:
nacl — the test job never installs the [cloud] extra. Two-sided fix,
matching how the other optional extras are handled (mcp/PIL/mem0/cognee):
the CI sync gains --extra cloud so the suites actually run there, and
every suite whose module-level imports pull httpx/pynacl guards itself
with pytest.importorskip so an extra-less checkout skips cleanly (the
native transfer verbs and the pure-filesystem suites still run — 18 pass,
8 skip without the extra; 75 pass with it). AGENTS.md's dev sync command
gains the extra too.
The Windows CI leg failed the scaffold suite: the written-paths list is
built with relative_to(), which renders backslashes there, and those
strings land in the CLI's JSON output — a cross-platform contract, so
normalize with as_posix() rather than loosening the assertions.
@Davidobot
Davidobot merged commit d2ba7ce into Egoist-Machines:main Jul 18, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants