Access was binary and org-wide: any org member got full read+write on every
project. Now each project carries four ordered levels, resolved by one
resolver and enforced at one choke point.
- `projectPerm` (perms.go) replaces `projectAllowed`; `proj(level, h)` in
server.go gates every per-project route by the level it declares at
registration, so no handler grows its own check.
- `Project` gains Creator/Default/Perms. `Default == ""` means write, so an
upgraded hub behaves identically until someone edits permissions.
- Creator becomes the first project admin; org owners are implicitly admin
everywhere in their org and a grant naming one is refused, not ignored; a
project always keeps at least one explicit admin.
- Default `none` makes a project invite-only. A `none` member is treated
exactly like a non-member, including on create-or-join by name.
- Rename/delete move from org-owner-only to project `admin`.
- Both metadata backends persist it: the file store rides along, the SQL
store gains `project_perms` plus an idempotent ALTER for the two new
columns (migrate() had only ever created tables).
Client side, a refusal stops looking like an outage: `remote.ErrForbidden`
plus `Result.ReadOnly` (push refused → pull-only) and `Result.NoAccess`
(pull refused → paused, working folder untouched). Neither sets Offline,
neither loses a local op, and re-granting self-heals on the next cycle.
`bdrive status`/`sync` and the daemon (once, on transition) say which.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Phase 3 of the read heatmap: `bdrive read-log` parses any platform's hook
event JSON from stdin and queues in-project file reads (mount-relative,
ignore/include-filtered) in a per-volume spool — no network on the hook
path. The sync cycle drains the spool best-effort to the hub's new
POST /api/p/<id>/reads via the remote ReadReporter capability (https
backend only); a failed report retries next cycle and never fails or
offlines the cycle. `bdrive hooks install` now registers a third hook per
platform on its read-tool matcher (claude Read, codex read_file
best-effort, gemini read_file|read_many_files, hermes read_file), each
idempotent on its own marker so sync-only configs upgrade in place. Agent
reads land as agent traffic in the heat view, actor = device id.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5cxPQdSGJnjXCYY9GeWXt
The web server (bdrive web) becomes a full sync hub, and client devices
get one-command onboarding — without ever seeing storage info or holding
cloud credentials:
- bdrive web -c config.json: server configurable from a JSON file
(remote/addr/upload/upload_ttl/projects_db); explicit flags win.
- Hub mode: pointing bdrive web at a storage root hosts many projects,
each under <root>/<project-id>/ (remote.Prefixed). Projects live in a
file-backed registry (projects.json — loaded at open, rewritten
atomically per change) with create-or-join-by-name semantics.
- Per-project APIs: /api/projects (list/create/get) and
/api/p/<id>/{tree,file,render,download,upload/*,store/*}. The web UI
grows a project list with per-project browsing and hash deep links.
- Browser uploads and a store proxy for syncing devices: presigned
direct-to-storage PUTs when the backend can sign (S3 presign, GCS V4
signed URLs; expiring, credential-free), relayed through the server
otherwise. Journals are never presigned — only immutable blobs.
Blobs-before-journal and one-writer-per-journal invariants hold.
- https:// remote backend: a device syncs one hub project through
/api/p/<id>/store/* — mnt/sync/daemon/log all work unchanged.
- bdrive login <url>: verify a hub and remember it as the device default
(settings.json). bdrive init: create-or-join a project named after the
folder (--name/--project override), write .bdrive, seed a starter
.bdriveignore, mount, and start the daemon — one command per project.
- Hard-break rename: .beardrive->.bdrive, .beardriveignore->.bdriveignore,
~/.beardrive->~/.bdrive, BEARDRIVE_HOME->BDRIVE_HOME, temp/conflict
prefixes; old names are no longer read.
- Tests: presigning, project registry persistence, store API validation
and gating, project isolation over live HTTP, browser upload flows, and
two-device convergence through a hub (incl. read-only pull-only mode).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7Q9ZKSZRTdvrSJkYLUmYs
Product and project are BearDrive; the CLI binary is bdrive (bdrive mnt,
bdrive sync, ...), the web viewer is bdrive-web. All conventions follow
the full name: .beardrive settings file, .beardriveignore, ~/.beardrive
home, BEARDRIVE_HOME, .beardrive-conflict-* / .beardrive-tmp-* files.
Plugin/skill/marketplace renamed to beardrive (/beardrive:mount).
Module path is now github.com/runbear-io/beardrive — merge only after
renaming the GitHub repo, or go install breaks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHEUaYfFHhmDvqLYw74Ehz