feat(cli): dimos data — resumable cloud uploads - #3548
Conversation
Codecov Report❌ Patch coverage is
@@ Coverage Diff @@
## main #3548 +/- ##
==========================================
+ Coverage 76.13% 76.41% +0.27%
==========================================
Files 1228 1247 +19
Lines 119174 120941 +1767
Branches 10684 10933 +249
==========================================
+ Hits 90731 92413 +1682
- Misses 25342 25419 +77
- Partials 3101 3109 +8
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 51 files with indirect coverage changes 🚀 New features to boost your workflow:
|
…t, stdlib streaming (file_digest/copyfileobj), collapse config overrides
|
Addressed the review + a ponytail audit pass, all in
Audit cuts in the same commit: |
|
mem2 uploads are ideally psql databases btw, there was a person on trial specing it out |
Greptile SummaryThis change adds resumable cloud upload and verified pull workflows for recordings and files. Two failures remain: a server-controlled LZ4 filename can produce a destination outside the recordings directory after suffix removal, and a recording removed during upload discovery causes the command to terminate with a traceback. Confidence Score: 2/5The pull destination is not fully confined after LZ4 suffix transformation, and upload discovery does not tolerate recordings disappearing during filesystem enumeration. Two independently reproduced failures remain, including one that allows a server-controlled filename to target a path outside the recordings directory. Files Needing Attention: dimos/cloud/data.py; dimos/cli/dimos.py
|
…LI catches OSError per file
|
@greptile review and update rating as needed |
… parts before /complete
| name = Path(d["filename"]).name | ||
| if not name or name in (".", ".."): | ||
| raise RuntimeError(f"server returned an invalid filename: {d['filename']!r}") | ||
| out = dest or RECORDINGS_DIR / name.removesuffix(".lz4") |
There was a problem hiding this comment.
Suffix transformation escapes confinement
A server filename of ...lz4 passes the basename check, but name.removesuffix(".lz4") becomes ... With the default destination, this constructs RECORDINGS_DIR / "..", which resolves outside the recordings directory before the staged extraction is replaced. Validate the filename after removing the LZ4 suffix and reject empty, . and .. output names before constructing out.
Artifacts
Focused CloudData pull reproduction source
- Mocks CloudData request and download behavior, then executes ordinary and `...lz4` LZ4 pulls against an isolated recordings directory; it reaches the vulnerable destination construction and replace path.
CloudData pull reproduction output
- Captured command output shows a normal confined pull succeeding and `...lz4` producing `recordings/..`, which resolves outside recordings and fails during replace.
| if since: | ||
| targets = recordings_since(_parse_since(since)) | ||
| if not targets: | ||
| typer.echo(f"no recordings in the last {since}") | ||
| raise typer.Exit(0) | ||
| else: | ||
| target = path or latest_recording() |
There was a problem hiding this comment.
Discovery errors bypass handling
If a recording disappears after it is found by glob() but before recordings_since() calls stat(), the resulting FileNotFoundError escapes before the per-file try/except block. dimos data upload --since therefore terminates with a traceback rather than the command's concise failure handling. Skip vanished files during discovery or handle discovery errors at the command boundary.
Artifacts
Focused upload discovery-to-stat race reproduction source
- The script deletes a discovered recording just before stat and calls the actual upload command function; takeaway: it deterministically exercises the claimed race.
Upload command output when a discovered recording vanishes
- Captured execution exited 1 with FileNotFoundError from recordings_since through upload before the guarded loop; takeaway: the CLI emits a traceback instead of concise failure handling.
Adds
dimos data— resumable uploads of mem2 recordings (and any file) to Dimensional cloud, over the/v1/datamultipart API._streamstable (open schema, transports pass through untouched); live sessions (recently-written .db) are skipped with a clear message.--no-compressfor disk-tight robots.dimos loginend to end — endpoint fromGlobalConfig.dimos_cloud_url, key from the stored credential; 401 surfaces as "rundimos login", no preflight round-trips.dimos_upload_retries/dimos_upload_compresson GlobalConfig; per-call overrides viaCloudDataConfig.Tested: unit suite with an in-memory contract fake (compress/manifest round-trip, resume-only-missing-parts, live-session guard, pull sha-verify+decompress, auth errors) + live against production: upload→ls→quota→idempotent re-send→pull byte-exact→kill-mid-upload→resume→complete, generic
--kind log, bad-key handling.Streaming ingest (continuous WebTransport uplink) is deliberately out of scope — lands with Paul's transport; the client here is the post-run half.