fix: treat bare jd:/jdrive: destinations as the root directory #55

Merged
ober merged 4 commits from fix/bare-jd-remote-destination into main 2026-09-17 14:06:01 -04:00
Owner

Problem

Since the v2 stateless storage rewrite (commit 93b51a3), a bare jd: destination no longer behaves as the root directory — it is classified as a literal root file:

$ jd cp ./check_updates.py jd:
{"...","Destination":"/", ...}
$ jd ls
[]

The upload succeeded but wrote object key jdrive/v2, which the v2 listing filter (storage-list-prefix → jdrive/v2/…) deliberately never shows — silent data-invisibility. The same command worked for months on the pre-rewrite manifest-based storage, where a bare jd: normalized to / and the manifest catalog round-tripped the file. This is a regression from the rewrite, not user error.

Also inconsistent today: jd ls jd: works (root), while jd cp ./x jd: silently places the file at the root as a file.

Root cause

path-directory-marker? (jdrive/s3/syncv2.ss) only treats a destination as a directory when the locator-stripped path is "/" or ends with "/". A bare jd: strips to "", so it fails the directory test. Sync scope, local→remote put, and remote→remote copy all share this single predicate.

Fix

Treat a bare locator (jd:, jdrive:) — empty after strip-locator — as the root directory, matching the documented rule that jd:photos ≡ jd:/photos and restoring v1 semantics:

  • jd cp ./x jd: → /x (identical to jd cp ./x jd:/)
  • jd ls jd: → unchanged (already worked)
  • sync/check scope with bare jd: → root

Verification

  • New unit tests jdrive-s3-v2-test-path-directory-marker? cover jd:, jdrive:, /, jd:/ as directories and jd:/notes.txt, /notes.txt as files.
  • make test — all tests pass (25 PASS).
  • make binary + make binary-smoke — clean build, installed-bundle smoke passed.
  • make security — pass.
  • Live end-to-end against S3 (bucket uap21, profile ober):
    • s3 cp /tmp/barejd-test.txt jd: now reports "Destination":"/barejd-test.txt" (was "/").
    • s3 ls jd: lists it; s3 get jd:/barejd-test.txt round-trips byte-identical. Test object cleaned up.

Version

VERSION 2.0.9 → 2.0.10; cli.ss user-visible version and jpkg.sexp synced to 2.0.10.

## Problem Since the v2 stateless storage rewrite (commit `93b51a3`), a **bare `jd:` destination no longer behaves as the root directory** — it is classified as a literal root *file*: ``` $ jd cp ./check_updates.py jd: {"...","Destination":"/", ...} $ jd ls [] ``` The upload *succeeded* but wrote object key `jdrive/v2`, which the v2 listing filter (`storage-list-prefix` → `jdrive/v2/…`) deliberately never shows — silent data-invisibility. The same command worked for months on the pre-rewrite manifest-based storage, where a bare `jd:` normalized to `/` and the manifest catalog round-tripped the file. This is a regression from the rewrite, not user error. Also inconsistent today: `jd ls jd:` works (root), while `jd cp ./x jd:` silently places the file *at* the root as a file. ## Root cause `path-directory-marker?` (jdrive/s3/syncv2.ss) only treats a destination as a directory when the locator-stripped path is `"/"` or ends with `"/"`. A bare `jd:` strips to `""`, so it fails the directory test. Sync scope, local→remote put, and remote→remote copy all share this single predicate. ## Fix Treat a bare locator (`jd:`, `jdrive:`) — empty after `strip-locator` — as the root directory, matching the documented rule that `jd:photos` ≡ `jd:/photos` and restoring v1 semantics: - `jd cp ./x jd:` → `/x` (identical to `jd cp ./x jd:/`) - `jd ls jd:` → unchanged (already worked) - sync/check scope with bare `jd:` → root ## Verification - New unit tests `jdrive-s3-v2-test-path-directory-marker?` cover `jd:`, `jdrive:`, `/`, `jd:/` as directories and `jd:/notes.txt`, `/notes.txt` as files. - `make test` — all tests pass (25 PASS). - `make binary` + `make binary-smoke` — clean build, installed-bundle smoke passed. - `make security` — pass. - Live end-to-end against S3 (bucket `uap21`, profile `ober`): - `s3 cp /tmp/barejd-test.txt jd:` now reports `"Destination":"/barejd-test.txt"` (was `"/"`). - `s3 ls jd:` lists it; `s3 get jd:/barejd-test.txt` round-trips byte-identical. Test object cleaned up. ## Version `VERSION` 2.0.9 → 2.0.10; `cli.ss` user-visible version and `jpkg.sexp` synced to 2.0.10.
fix: treat bare jd:/jdrive: destinations as the root directory
Some checks failed
required-ci / required (pull_request) Failing after 3m42s
version-policy / required (pull_request) Failing after 3m43s
ab347e6bf8
Commit 93b51a3 (v2 stateless storage rewrite) introduced path-directory-
marker?, which classified a remote destination as a directory only when
the stripped locator was "/" or ended with "/". A bare "jd:"
stripped to an empty string, so it became a literal root FILE: cp
uploaded to object key jdrive/v2, which the listing filter never shows.
The v1 manifest storage treated a bare "jd:" as the root directory, so
this is a regression from the rewrite.

Restore v1 semantics: bare locators ("jd:", "jdrive:") are root
directories, so 's3 cp ./x jd:' now places x at /x exactly like
'jd:/x'. Fixes sync scope, put, and remote copy, which all share
path-directory-marker?.

Adds a test-only export and unit coverage for the classification.
ci: re-trigger workflows after runner infra failure
Some checks failed
required-ci / required (pull_request) Failing after 3m51s
version-policy / required (pull_request) Failing after 3m53s
ec96c90d43
ci: re-run after restoring offline freebsd-amd64 runner pool
Some checks failed
version-policy / required (pull_request) Failing after 3m42s
required-ci / required (pull_request) Failing after 4m3s
da7dbf63a8
ci: pin JERBOA_VERSION to v0.2.9
All checks were successful
version-policy / required (pull_request) Successful in 4m17s
required-ci / required (pull_request) Successful in 8m25s
9e77dcdfb7
The v0.2.8 release no longer exists on git.jerboa.sh (ober/jerboa),
so ensure-jerboa-tools fetches a 404 and every CI job dies in the
'test' target. v0.2.9 is the current release and ships the
freebsd-amd64 tarball used by the CI runner. Verified end-to-end by
running .forgejo/ci-required.sh on the freebsd-amd64 runner host:
security, test, doctor, and binary all pass.
ober scheduled this pull request to auto merge when all checks succeed 2026-09-17 14:05:30 -04:00
ober merged commit 8ad03800b0 into main 2026-09-17 14:06:01 -04:00
ober referenced this pull request from a commit 2026-09-17 14:06:03 -04:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ober/jerboa-drive!55
No description provided.